# Oridecon — rules for coding agents

> Distilled from the framework AGENTS.md (v3.1). Canonical human docs: https://docs.oridecon.dev/
> Machine indexes: https://docs.oridecon.dev/llms.txt · https://docs.oridecon.dev/llms-full.txt
> Playbook: https://docs.oridecon.dev/getting-started/for-coding-agents/
> Fail vs fix: https://docs.oridecon.dev/getting-started/common-mistakes/
> Skill: https://docs.oridecon.dev/SKILL.md
> Skill pack: https://github.com/dbtinoy-/oridecon-skills — install: https://docs.oridecon.dev/getting-started/agent-skills/
> Changelog (0.1.1): https://docs.oridecon.dev/changelog/
> Full ruleset in the repo: https://github.com/dbtinoy-/oridecon/blob/dev/AGENTS.md

Oridecon is an async-first, contract-driven Python application framework (3.11+, MIT, alpha 0.1.x, public release 0.1.1).
Prefer protocols over concrete classes. Pin `>=0.1,<0.2`.

## Read this first

1. Load https://docs.oridecon.dev/llms.txt, then this file, then https://docs.oridecon.dev/getting-started/for-coding-agents/. Fail vs fix: https://docs.oridecon.dev/getting-started/common-mistakes/. Skill: https://docs.oridecon.dev/SKILL.md. Per-package: https://docs.oridecon.dev/llms/index.txt.
2. Install: `uv add "oridecon-cli>=0.1,<0.2"` then `oridecon new project my-app --template web-api`. Add `oridecon-web`, `oridecon-sql`, `oridecon-ai-llm` as needed.
3. Hierarchy: `oridecon-contracts` ← `oridecon` ← `oridecon-*`. Extensions never import each other.
4. Golden rule: if two packages need the same type, protocol, or exception, it lives in `oridecon-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. Do not import across `oridecon-ai-*` packages.

## Package hierarchy

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

Documented exceptions (same-subsystem only): `oridecon-admin` may import auth/cache/features/resilience/ui;
`oridecon-ai` (orchestrator) may import `oridecon-ai-*` and `oridecon-vector`;
`oridecon-multimedia` may import `oridecon-multimedia-*`;
`oridecon-testing` may import any extension. All of those deps must be 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/        # db, cache, events
  shared/                # errors, middleware, health (always cross-cutting)
  modules/<slug>/        # oridecon new module — protocols.py, provider.py, domains/
```

`oridecon gen error` / middleware always land in `shared/`. `oridecon 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. Living examples such as `support-agent` may pass `controllers=[...]` instead — copy that only when matching the example.

Human docs: https://docs.oridecon.dev/getting-started/project-structure/
CLI map: https://docs.oridecon.dev/experimental/oridecon-cli/docs/PROJECT_LAYOUT/

## Repo map (branch `dev`)

- `AGENTS.md` — canonical rules
- `examples/<slug>/` — runnable apps + `example-hub`; copy these
- `core/oridecon`, `core/oridecon-contracts` — foundation
- `packages/oridecon-*` — web, sql, auth, queue, …
- `experimental/ai/oridecon-ai*`, `experimental/multimedia/oridecon-multimedia*`
- `experimental/apps/oridecon-{cli,admin,ui}`

Docs site: getting-started, fundamentals, guides, ecosystem, examples, and blog are authored.
Package pages under packages/, platform/, experimental/ are copies from the framework (sidebar hrefs in packages.json). Do not hand-edit them.

## 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 oridecon.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 in the monorepo.
- 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, expected skill failure).
Raise when the failure should propagate (database down, serialization bug, container resolution failed, missing API key).

```python
from oridecon.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 CacheProvider(Provider):
    name = "cache"
    priority = ProviderPriority.INFRASTRUCTURE

    async def register(self, container: ContainerRegistrarProtocol) -> None:
        container.singleton(CacheBackend, RedisCacheBackend)

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

    async def shutdown(self) -> None:
        await self._cache.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 oridecon.primitives import clock` → `clock.now()`
- `from oridecon.identity import ambient as identity` → `identity.new_uuid()`
- `from oridecon.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 "oridecon-cli>=0.1,<0.2"
oridecon new project my-app --template web-api
cd my-app && oridecon run
oridecon gen controller users
oridecon gen service greetings
oridecon new module auth
oridecon 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"]),
])
```

Copy an example instead of inventing a tree. Catalog: https://docs.oridecon.dev/examples/

```bash
PYTHONPATH=examples/<slug>/src uv run python -m <module>
make test-examples
```

The hub (`examples/example-hub`, module `example_hub`, port 7000) mounts every web example under `/examples/<slug>/`.

## When writing code

- Cite package names with the `oridecon-` prefix.
- Link to https://docs.oridecon.dev/ pages from the catalog in /llms.txt.
- Prefer the protocol in `oridecon-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://docs.oridecon.dev/guides/migrating-from-fastapi/.
