Skip to content

For coding agents

This is the operating manual for generating QuadKit code. Humans can skip it and start at Your First App.

  1. /llms.txt — identity, install, rules, every docs URL.
  2. /agents.md — distilled framework AGENTS.md: hierarchy, Result vs exceptions, provider lifecycle, the never-list.
  3. /SKILL.md — drop-in skill for Claude Code, Cursor, OpenCode (same rules, fetchable without another clone).
  4. /llms-full.txt — the same catalog plus architecture notes. Per-package: /llms/index.txt (example /llms/quadkit-web.txt).
  5. This page — repo map, the one project tree, recipes. Then Common mistakes for fail vs fix.
  6. The generator output you just scaffolded with quadkit new project.
  7. The protocol in quadkit-contracts, not a concrete class in an extension.

Installable pack: quadkit-skills (Claude Code, Cursor, OpenCode). One-file fetch: /SKILL.md. Install commands: Agent skills. Do not re-derive the rules from blog posts. If a packed skill and this site disagree on layout, this site wins (domains/, app-root di/).

Canonical ruleset lives in this repo at AGENTS.md.

quadkit-contracts Zero dependencies. Protocols, types, exceptions only.
↑
quadkit Depends ONLY on quadkit-contracts.
↑
quadkit-* Extension packages. Never import each other.

If two or more packages need the same type, protocol, or exception, it lives in quadkit-contracts. No exceptions.

Thirteen packages are published: 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. 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.

Documented exceptions (deps must be in pyproject.toml):

PackageMay import
quadkit-cliquadkit, quadkit-contracts
quadkit-testingany extension (optional extras)

An import linter enforces this. A cross-extension import that type-checks will still fail CI.

There is one layout. Templates (minimal, api, web-api, graphql, worker, full, fullstack) add packages and application.yaml sections — not a second shape. There is no --structure flag and no models/ directory.

KindWhere it lives
Composition rootsrc/<app>/app.py — create_app(), ASGI target <app>.app:app
Unscoped HTTPsrc/<app>/controllers/
Unscoped domain typessrc/<app>/domains/
App providerssrc/<app>/di/*_provider.py
Module providersrc/<app>/modules/<slug>/provider.py
Cross-cuttingsrc/<app>/shared/ (errors, middleware, health, …)
Bounded contextsrc/<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)”
PathWhat it is
AGENTS.mdCanonical agent rules
core/quadkitApplication, container, providers, modules, Result, config
core/quadkit-contractsProtocols, types, exceptions — zero deps
packages/quadkit-webASGI, controllers, middleware, OpenAPI
packages/quadkit-testingTest harnesses, fakes, fixtures
apps/quadkit-cliScaffolding, generators, dev server

This docs site (oridecon-website):

Authored (edit here)Generated (do not hand-edit)
getting-started/, fundamentals/, guides/, ecosystem/, blog/Package trees under packages/

Package pages are copies from the framework. Destinations follow src/data/packages.json hrefs (the same URLs as the sidebar). Re-run scripts/sync-readmes.py / scripts/generate-api.py — do not invent hub paths.

Install and scaffold:

Terminal window
uv add "quadkit-cli>=0.0.42,<0.1"
quadkit new project my-app --template web-api
cd my-app
quadkit run

Add surface area with the CLI, then fill in behavior:

Terminal window
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 auth

A provider binds in register() and resolves in boot(). They take different protocols:

from quadkit.di.provider import Provider
from quadkit.contracts.core import ProviderPriority
from quadkit.contracts.core.di import (
ContainerRegistrarProtocol,
ContainerResolverProtocol,
)
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: ContainerResolverProtocol) -> 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.

quadkit new project boots the real Application — real DI, real providers. Read what it writes before writing anything else:

Terminal window
quadkit new project my-app --template fullstack
find my-app/src -name '*.py' | sort
NeedStart from
A route with request validationquadkit gen controller <name>
A service behind a protocolquadkit gen service <name>
A typed domain errorquadkit gen error <name>
A bounded context with its own protocolsquadkit new module <slug>
A test that boots the appquadkit-testing’s TestEnvironment — 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.

  • 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.42,<0.1 (current release 0.0.42). Alpha means the surface can move; the architecture laws will not. Changelog: 0.0.4.
  • 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.