Architecture

Chirp's three-layer architecture and startup contract compiler

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

This page is the mental map of how Chirp is built — the three layers a request passes through, where each module lives, and how a template becomes rendered HTML. It's for contributors and anyone evaluating the design; you never need to read it to use Chirp.

Chirp is a Hypermedia framework: the server sends HTML with controls and links, and the client swaps fragments instead of owning application state.

If you just want to build something, start with the quickstart.

Three Layers

Chirp is organized into three layers, each with a clear responsibility:

flowchart TD subgraph Surface["Surface Layer — What developers touch"] S1["App"] S2["@app.route()"] S3["AppConfig"] S4["Template"] S5["Fragment"] S6["Stream"] S7["EventStream"] S8["Response"] S9["Redirect"] end subgraph Core["Core Layer — Typed, immutable where honest"] C1["Request (frozen, slots)"] C2["Router (compiled)"] C3["Response (.with_*() chain)"] C4["Middleware (Protocol)"] C5["Headers (immutable)"] C6["Route (frozen)"] end subgraph Engine["Engine Layer — ASGI + integrations"] E1["ASGI handler"] E2["Kida environment"] E3["bengal-pounce server"] E4["SSE handler"] E5["anyio runtime"] end Surface --> Core --> Engine
Layer Responsibility Do you touch it?
Surface The API you write against:@app.route() decorators, return types (Template, Fragment, Stream), and the frozen AppConfig. Yes — this is the whole developer surface.
Core Typed, immutable data:Request is @dataclass(frozen=True, slots=True), Response chains .with_*()transforms, the router compiles to an immutable trie, middleware is a Protocol (not a base class). Rarely — you read aRequest, return a Response, and write middleware to the Protocol.
Engine The ASGI handler bridging raw scope/messages to typed abstractions, the Kida environment, and thebengal-pounceASGI server. No — Chirp drives it for you.

The frozen/slots design and theContextVarrequest state are what make Chirp safe under free-threading. See free-threading and frozen state for why.

The contract compiler boundary

The three layers are connected at application freeze. Chirp compiles route declarations, template and block metadata, htmx targets, registries, and declared transitions into one immutable internalHypermediaProgram. That program is not a public graph API; it is the shared internal model used by the first graph-backedapp.check()rules, runtime transition traces, DevTools, and transition-testing helpers.

flowchart LR Inputs["Routes + typed returns + template blocks + registries"] Program["HypermediaProgram (internal, immutable)"] Runtime["Live ASGI runtime"] Checks["app.check()"] Evidence["DevTools + transition tests"] Export["Optional static export"] Inputs --> Program Program --> Runtime Program --> Checks Runtime --> Evidence Program --> Evidence Program --> Export

This is a contract compiler, not a static-site-first deployment model. The primary output is the live ASGI application: SQL, mutations, validation, sessions, streaming, and SSE continue to run at request time.chirp freeze is an optional projection for compatible routes from that same application.

The tested Full-Application Journey walks through the complete feedback loop: typed return values, startup checks, route-smoke and transition evidence, DevTools, and a deliberately bounded static export.

Key Terms (2)
DocCatalog
The in-memory documentation graph built from markdown undersite/content/, with one DocNodeper page.
Hypermedia
A style of application where the server sends HTML that includes controls and links, and the client swaps fragments instead of owning application state.

Module Layout

Request Flow

A request flows through the system like this:

  1. 1

    ASGI handler receives scope and messages

    Raw ASGI scope and message stream enter the engine layer.

  2. 2

    Request construction

    Frozen dataclass created from ASGI scope.

  3. 3

    Middleware pipeline

    Each middleware wraps the next; request passes through the stack.

  4. 4

    Router matches path

    Trie lookup matches path to handler.

  5. 5

    Handler invocation

    Signature introspection injects Request + path params.

  6. 6

    Return value

    Handler returns a value (Template, Fragment, etc.).

  7. 7

    Content negotiation

    Return type determines how to render the response.

  8. 8

    Response sending

    ASGI messages sent back to the server.

Template Rendering Flow

Chirp uses Kida's AST metadata for OOB discovery and block validation:

flowchart LR subgraph Kida["Kida"] T[Template] M[TemplateMetadata] T --> M end subgraph Chirp["Chirp"] BC[build_layout_contract] LC[LayoutContract] RP[RenderPlan] M --> BC --> LC LC --> RP end

Dependencies

Chirp owns the developer interface and delegates commodity infrastructure:

flowchart TD chirp["chirp (the framework)"] chirp --> kida["kida-templates — Template engine\n(same author, same ecosystem)"] chirp --> anyio["anyio — Async runtime\n(not worth rewriting)"] chirp --> pounce["bengal-pounce — ASGI server\n(same ecosystem)"]

Optional extras add focused capabilities without bloating the core. SQLite needs no extra — Chirp uses the stdlibsqlite3.

chirp[forms]      → python-multipart  (form/multipart parsing)
chirp[sessions]   → itsdangerous      (signed session cookies)
chirp[auth]       → argon2-cffi       (password hashing)
chirp[testing]    → httpx             (test client)
chirp[data-pg]    → in-tree pelt      (pure-Python PostgreSQL driver)
chirp[markdown]   → patitas[syntax]   (markdown rendering)
chirp[ai]         → httpx             (LLM streaming)
chirp[all]        → everything above

See installation and extras for the full list and install commands.

Next Steps