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:
| 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.
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 under
site/content/, with oneDocNodeper 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
Full module map
The tree below is the package layout as it ships in src/chirp/. It's reference detail — you can build anything in Chirp without it.
chirp/
├── __init__.py # Public API exports (lazy imports)
├── app/ # App class and setup
├── config.py # AppConfig frozen dataclass
├── context.py # Request-scoped context (ContextVar, g)
├── contracts/ # app.check() rule set (checker + rules_*.py)
├── errors.py # Error hierarchy
├── sources.py # Template source loading
├── domains.py # Domain/host routing
├── freeze.py # The freeze transition (mutable → immutable)
├── plugin.py # Plugin registration
├── health.py # Health-check endpoints
├── resilience.py # Timeouts, retries, circuit breaking
│
├── _internal/ # ASGI type definitions (not public)
├── http/ # Request, Response, Headers, Cookies, Query, Forms
├── routing/ # Router, Route, path parameters
├── middleware/ # Protocol, CORS, StaticFiles, Sessions, Auth, CSRF
├── templating/ # Kida integration, return types, filters, streaming
├── pages/
│ └── reactive/ # ReactiveBus, DependencyIndex, reactive_stream
├── realtime/ # SSE protocol and EventStream
├── server/ # ASGI handler, dev server, content negotiation
├── data/ # Database access, row mapping
├── security/ # Decorators, password hashing
├── validation/ # Form validation rules and results
├── cache/ # Response and fragment caching
├── i18n/ # Internationalization
├── markdown/ # Markdown rendering (patitas)
├── cli/ # chirp CLI (new, run, check, freeze)
├── docs/ # In-framework docs tooling
├── ext/ # Extensions (e.g. chirp-ui integration)
├── testing/ # TestClient, assertions, SSE testing
├── tools/ # MCP tool registry and handler
└── ai/ # LLM integration (optional)
Request Flow
A request flows through the system like this:
- 1
ASGI handler receives scope and messages
Raw ASGI scope and message stream enter the engine layer.
- 2
Request construction
Frozen dataclass created from ASGI scope.
- 3
Middleware pipeline
Each middleware wraps the next; request passes through the stack.
- 4
Router matches path
Trie lookup matches path to handler.
- 5
Handler invocation
Signature introspection injects Request + path params.
- 6
Return value
Handler returns a value (Template, Fragment, etc.).
- 7
Content negotiation
Return type determines how to render the response.
- 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:
Dependencies
Chirp owns the developer interface and delegates commodity infrastructure:
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
- Philosophy -- Design principles
- Thread Safety -- Free-threading patterns
- App Lifecycle -- The freeze transition