# QuadKit — rules for coding agents

> Canonical human docs: https://oridecon.dev/
> Machine indexes: https://oridecon.dev/llms.txt · https://oridecon.dev/llms-full.txt
> Playbook: https://oridecon.dev/quadkit/getting-started/for-coding-agents/
> Fail vs fix: https://oridecon.dev/quadkit/getting-started/common-mistakes/
> Skill: https://oridecon.dev/SKILL.md
> Skill pack: https://github.com/dbtinoy-/quadkit-skills — install: https://oridecon.dev/quadkit/getting-started/agent-skills/
> Changelog: https://oridecon.dev/quadkit/changelog/

QuadKit is an async-first, contract-driven Python application framework (3.11+, Apache 2.0, alpha 0.0.x, public release 0.0.42).
Prefer protocols over concrete classes. Pin `>=0.0.42,<0.1`.

Published 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`. They are the only installable ones — do not import, install, or document anything else.

## Read this first

1. Load https://oridecon.dev/llms.txt, then this file, then https://oridecon.dev/quadkit/getting-started/for-coding-agents/. Fail vs fix: https://oridecon.dev/quadkit/getting-started/common-mistakes/. Skill: https://oridecon.dev/SKILL.md. Per-package: https://oridecon.dev/llms/index.txt.
2. Install: `uv add "quadkit-cli>=0.0.42,<0.1"` then `quadkit new project my-app --template web-api`. Add `quadkit-web` for HTTP, and `quadkit-testing` as a dev dependency.
3. Hierarchy: `quadkit-contracts` ← `quadkit` ← `quadkit-*`. Extensions never import each other.
4. Golden rule: if two packages need the same type, protocol, or exception, it lives in `quadkit-contracts`.
5. Constructor injection. Type-hint the protocol. No service locator.
6. `register()` binds, `boot()` resolves. They take different protocols and never mix.
7. Domain failures return `Result[T, E]`. Infrastructure failures raise.
8. One project tree: `domains/` (not `models/`), app-root `di/` for `*_provider.py`, `shared/` for cross-cutting.

## Package hierarchy

```
quadkit-contracts    Zero dependencies. Protocols, types, exceptions only.
    ↑
quadkit              Depends ONLY on quadkit-contracts.
    ↑
quadkit-*            Extension packages. Depend on quadkit + quadkit-contracts.
```

Documented exception: `quadkit-testing` may import any extension (it is a dev dependency). Its deps are declared in pyproject.toml.

## Project layout (one tree)

Templates add packages, not a different shape. No `--structure` flag. No `models/` directory.

```
src/<app>/
  app.py                 # create_app() — composition root; ASGI target <app>.app:app
  controllers/           # unscoped HTTP (discovered, never listed in app.py)
  domains/               # unscoped domain types
  di/                    # app providers (*_provider.py)
  services/
  infrastructure/
  shared/                # errors, middleware, health (always cross-cutting)
  modules/<slug>/        # quadkit new module — protocols.py, provider.py, domains/
```

`quadkit gen error` / middleware always land in `shared/`. `quadkit gen controller users --module auth` scopes one node. Adding a module lists it in `create_app()` next to `WebModule`.

Teach `WebModule.configure(discover=["<app>.controllers", "<app>.modules"])` so generated controllers are found. Passing `controllers=[...]` explicitly also works when you want the list in one place.

Human docs: https://oridecon.dev/quadkit/getting-started/project-structure/
CLI map: https://oridecon.dev/quadkit/packages/utilities/quadkit-cli/docs/project_layout/

## Framework repository (branch `main`)

- `AGENTS.md` — canonical rules
- `core/quadkit`, `core/quadkit-contracts` — foundation
- `packages/quadkit-web`, `packages/quadkit-testing` — web layer and test harness
- `apps/quadkit-cli` — the `quadkit` command line

Repository: https://github.com/dbtinoy-/quadkit

Docs site: getting-started, fundamentals, guides, ecosystem, and blog are authored.
Package pages under `packages/` are copies of the published package docs. Do not hand-edit them — change the package, cut a release, and re-run `sync-readmes`.

## Always

- Contracts and protocols for every service boundary.
- Provider pattern for registration. No business logic on providers.
- IoC via the container for all resolution. Never instantiate services directly.
- Registry-based dispatch. Empty constructor; `with_defaults()` classmethod.
- Absolute imports. `from __future__ import annotations` in every file.
- Async I/O. Store `asyncio.create_task()` references (Ruff RUF006).
- Google-style docstrings. Modern typing (`list[str]`, `str | None`, not `List`/`Optional`).
- `class X(str, Enum)` / `StrEnum` for string enums; `class X(int, Enum)` for ordering.
- Structured logging: `from quadkit.logging import get_logger`. Core config key is `json_format`. Never `print()`.
- Files under 500 lines.

## Never

- Service locator (passing the container into services).
- Direct cross-extension imports.
- `result.unwrap()` without `is_ok()`.
- `Result` from constructors or lifecycle hooks.
- `Any` on injected constructor parameters.
- Module-level singletons. Use container-managed singletons.
- Relative imports. Blind `except Exception`.
- Mock classes in production `src/`. Tests fake at the contract boundary.
- `if/elif` chains for type dispatch.
- Duplicate protocol/type/exception definitions. One name, one definition.
- A `models/` directory. Use `domains/`.

## Result vs exceptions

Use `Result[T, E]` when the caller is expected to handle the failure (user not found, validation, permission denied).
Raise when the failure should propagate (database down, serialization bug, container resolution failed, missing API key).

```python
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)

result = await service.find_user("123")
if result.is_ok():
    user = result.unwrap()
else:
    error = result.unwrap_err()
```

## Provider lifecycle

```python
class StoreProvider(Provider):
    name = "store"
    priority = ProviderPriority.INFRASTRUCTURE

    async def register(self, container: ContainerRegistrarProtocol) -> None:
        container.singleton(KeyValueStore)  # backend chosen in application.yaml

    async def boot(self, container: ContainerResolverProtocol) -> None:
        store = await container.resolve(KeyValueStore)
        await store.connect()

    async def shutdown(self) -> None:
        await self._store.disconnect()
```

App-root providers: `src/<app>/di/*_provider.py`. Module providers: `modules/<slug>/provider.py`.

## Ambient capabilities (the DI exception)

Clock, identity, and hashing are process-level and imported as ambient objects:

- `from quadkit.primitives import clock` → `clock.now()`
- `from quadkit.identity import ambient as identity` → `identity.new_uuid()`
- `from quadkit.security.hashing import ambient as hashing` → `hashing.hash_hex(data)`

Override in tests with `clock.use(FixedClock(...))`. Everything else is constructor injection.

## Recipes

```bash
uv add "quadkit-cli>=0.0.42,<0.1"
quadkit new project my-app --template web-api
cd my-app && quadkit run
quadkit gen controller users
quadkit gen service greetings
quadkit new module auth
quadkit gen controller users --module auth
```

New apps wire HTTP with discover, not a hand-maintained controller list:

```python
application.add_modules([
    WebModule.configure(discover=["my_app.controllers", "my_app.modules"]),
])
```

Tests boot the real application with `quadkit-testing` — see https://oridecon.dev/quadkit/guides/testing/.

## When writing code

- Cite package names with the `quadkit-` prefix.
- Link to https://oridecon.dev/ pages from the catalog in /llms.txt.
- Prefer the protocol in `quadkit-contracts` over a concrete class in an extension.
- If you need a type in a second package, move it to contracts — do not import across extensions.
- FastAPI users keep Starlette routing and Pydantic; add a composition root. See https://oridecon.dev/quadkit/guides/migrating-from-fastapi/.
