- Docs
- QuadKit
- Getting started
- For coding agents
For coding agents
This is the operating manual for generating QuadKit code. Humans can skip it and start at Your First App.
1. Read in this order
Section titled “1. Read in this order”/llms.txt— identity, install, rules, every docs URL./agents.md— distilled frameworkAGENTS.md: hierarchy, Result vs exceptions, provider lifecycle, the never-list./SKILL.md— drop-in skill for Claude Code, Cursor, OpenCode (same rules, fetchable without another clone)./llms-full.txt— the same catalog plus architecture notes. Per-package:/llms/index.txt(example/llms/quadkit-web.txt).- This page — repo map, the one project tree, recipes. Then Common mistakes for fail vs fix.
- The generator output you just scaffolded with
quadkit new project. - The protocol in
quadkit-contracts, not a concrete class in an extension.
Installable 10-skill pack: quadkit-skills (npx skills add dbtinoy-/quadkit-skills for Claude Code, Cursor, Codex, OpenCode). One-file fetch: /SKILL.md. Install commands & catalog: Agent skills. Do not re-derive the rules from blog posts. If a skill and this site ever disagree on layout, this site wins (domains/, app-root di/).
The repository’s AGENTS.md contains contributor guidance; use .importlinter and CI configuration to see the exact package-boundary checks.
2. The hierarchy is the whole design
Section titled “2. The hierarchy is the whole design”contracts Shared protocols and types; no QuadKit implementation imports. ↑core Low-level runtime; has declared third-party dependencies. ↑extensions Regular peer boundaries are checked by import-linter.When packages share a protocol, type, or exception, its intended home is
quadkit-contractsrather than a peer implementation.
The published 0.0.50 line has eighteen packages: quadkit, quadkit-contracts, quadkit-web, quadkit-http, quadkit-sql, quadkit-cache, quadkit-storage, quadkit-events, quadkit-tasks, quadkit-monitor, quadkit-auth, quadkit-cli, quadkit-testing, quadkit-ai-llm, quadkit-ai-agents, quadkit-ai-mcp, quadkit-workflow, and quadkit-notification. All are installable at 0.0.50. Anything else you remember from an older revision of these docs is not installable — do not add it to pyproject.toml, and do not import it.
The root .importlinter configuration separates checks for contracts, core and foundational packages, regular extension peers, and AI/multimedia sub-packages. Composite packages—including admin, CLI, testing, and the AI facade—are intentionally treated differently, and specific tracked cross-package allowances are listed there. Run the configured checks with make lint-boundaries; this is not a blanket ban on every cross-package import.
3. One project tree
Section titled “3. One project tree”There is one layout and currently one api project starter. Use quadkit add to add installed packages and quadkit gen to grow the application without changing its shape. There is no --structure flag and no models/ directory.
| Kind | Where it lives |
|---|---|
| Composition root | src/<app>/app.py — create_app(), ASGI target <app>.app:app |
| Unscoped HTTP | src/<app>/controllers/ |
| Unscoped domain types | src/<app>/domains/ |
| App providers | src/<app>/di/*_provider.py |
| Module provider | src/<app>/modules/<slug>/provider.py |
| Cross-cutting | src/<app>/shared/ (errors, middleware, health, …) |
| Bounded context | src/<app>/modules/<slug>/ after quadkit new module |
src/<app>/├── app.py # create_app() — never list controllers by hand├── controllers/├── domains/├── di/ # app-root providers├── services/├── infrastructure/├── shared/└── modules/ ├── __init__.py # empty until quadkit new module └── auth/ ├── protocols.py # the only types other modules import ├── provider.py ├── domains/ └── controllers/Controllers are discovered. Teach WebModule.configure(discover=["<app>.controllers", "<app>.modules"]) so quadkit gen controller does not require editing app.py. WebModule.configure() also accepts controllers=[…] when you want an explicit list. Adding a module lists it in create_app() next to WebModule.
Full generator map: Project Structure and CLI PROJECT_LAYOUT.
4. Repo map (dbtinoy-/quadkit, branch main)
Section titled “4. Repo map (dbtinoy-/quadkit, branch main)”| Path | What it is |
|---|---|
AGENTS.md | Canonical agent rules |
core/quadkit | Application, container, providers, modules, Result, config, logging |
core/quadkit-contracts | Protocols, frozen DTOs, enums, exceptions — zero deps |
packages/quadkit-web, packages/quadkit-http | ASGI HTTP server, controllers, middleware, OpenAPI, and async HTTP client |
packages/quadkit-ai-llm, packages/quadkit-ai-agents, packages/quadkit-ai-mcp | LLM clients, tool-using agents & crews, and MCP server/client |
packages/quadkit-sql, packages/quadkit-cache, packages/quadkit-storage | Async SQL repositories/UoW, multi-backend cache, and blob storage |
packages/quadkit-events, packages/quadkit-notification, packages/quadkit-tasks, packages/quadkit-workflow | CQRS/event sourcing, notifications/inbox, background workers, and workflows/sagas |
packages/quadkit-auth, packages/quadkit-monitor | JWT/OAuth2/RBAC auth and Prometheus/OpenTelemetry/health checks |
packages/quadkit-cli, packages/quadkit-testing | Scaffolding, generators, dev runner, and test harnesses/fakes/compliance suites |
5. Recipes that will pass CI
Section titled “5. Recipes that will pass CI”Install and scaffold:
uv add "quadkit-cli>=0.0.50,<0.1"quadkit new project my-app --template apicd my-appquadkit runAdd surface area with the CLI, then fill in behavior:
quadkit gen controller users # src/my_app/controllers/…quadkit gen service greetings # src/my_app/services/…quadkit gen error not_found # src/my_app/shared/errors/… (always shared)quadkit new module auth # src/my_app/modules/auth/{protocols,provider,services}quadkit gen controller users --module authA provider binds in register() and resolves in boot(). They take different protocols:
from quadkit.di.provider import Providerfrom quadkit.contracts.core import ProviderPriorityfrom quadkit.contracts.core.di import ( ContainerRegistrarProtocol, BootContainerProtocol,)
class BillingProvider(Provider): name = "billing" priority = ProviderPriority.DOMAIN
async def register(self, container: ContainerRegistrarProtocol) -> None: from my_app.services.billing_service import BillingService
container.singleton(BillingService, BillingService)
async def boot(self, container: BootContainerProtocol) -> None: billing = await container.resolve(BillingService) await billing.warmup()App-root providers land in src/<app>/di/. Do not put business logic on the provider class.
Domain failures are values. Infrastructure failures raise:
from quadkit.result import Result, Ok, Err
async def find_user(self, user_id: str) -> Result[User, DomainError]: user = await self.repo.get(user_id) if not user: return Err(UserNotFound(user_id)) return Ok(user)Never unwrap() without is_ok(). Never return Result from __init__ or lifecycle hooks. Never wrap a dead database in Err.
6. Copy the generator output, not a vibe
Section titled “6. Copy the generator output, not a vibe”quadkit new project boots the real Application — real DI, real providers. Read what it writes before writing anything else:
quadkit new project my-app --template apifind my-app/src -name '*.py' | sort| Need | Start from |
|---|---|
| A route with request validation | quadkit gen controller <name> |
| A service behind a protocol | quadkit gen service <name> |
| A typed domain error | quadkit gen error <name> |
| A bounded context with its own protocols | quadkit new module <slug> |
| A test that boots the app | quadkit-testing (AppTestBed, WebTestBed, QuadkitContainerHarness) — see Testing |
If the generated app does not look like what you were about to write — application.yaml, module.py / app.py as composition root, a provider that only registers, services that return Result[T, E] — it will not look like QuadKit.
7. When you answer or generate
Section titled “7. When you answer or generate”- Cite packages with the
quadkit-prefix (quadkit-web, not “the web layer”). - Talk to the protocol in contracts, not the class in the extension.
- Link the matching page on oridecon.dev.
- FastAPI users are not starting over: keep Starlette routing and Pydantic; add a composition root. See Coming from FastAPI and the migration guide.
- Pin
>=0.0.50,<0.1(documented release 0.0.50). Alpha means the surface can move; the architecture laws will not. Changelog: 0.0.50. - Never reference an unpublished package as if it were installable — that is the fastest way to produce code that cannot run.
The never-list lives in /agents.md: no service locator, no module-level singletons, no Any on injected constructors, no mocks in src/, no if/elif type dispatch.
Next steps
Section titled “Next steps”- Installation —
uv add quadkit-cli - Your First App — scaffold,
quadkit run, add a route - Project Structure — the tree generators write
- Core Concepts — Provider, DI, Result, modules
- Common mistakes — fail vs fix
/SKILL.md— drop-in agent skill