# Custodian Agent Runtime — v0.1 Single-pass OODA agent loop. Observes project state via the State Hub, reasons about it with an LLM, and executes bounded write operations. ## Architecture ``` agent.py CLI entry point + OODA orchestrator context.py Observe: fetch state-hub data + build LLM context prompt actions.py Act: execute sanctioned write operations prompts/ System prompt templates (future) policies/ Agent-level policies (future) tool_adapters/ Additional MCP/API tool adapters (future) tests/ Unit tests (offline, no live API required) ``` See `canon/architecture/adr-002-custodian-agent-runtime-design.md` for all architectural decisions. ## OODA Loop ``` Observe → fetch_state(domain) # HTTP GET /state/summary or /state/domain/{slug} Orient → load_constitution() # reads canon/constitution/ build_context(state, const) # assembles LLM prompt Decide → LLM call via llm-connect # returns markdown + JSON action plan Act → parse_plan(response) # extract JSON block execute(plan) # run sanctioned writes ``` ## Sanctioned Write Operations (ADR-002 D4) Only three operations may be executed without human approval: | Operation | State-hub endpoint | Reversible | |---|---|---| | `add_progress_event` | `POST /progress/` | Yes (append-only log) | | `update_task_status` | `PATCH /tasks/{id}/` | Yes | | `flag_for_human` | `PATCH /tasks/{id}/` | Yes (clear with `clear_human_flag`) | ## Prerequisites - State-hub running: `cd state-hub && make api` - LLM available: `claude` CLI in PATH (for default `claude-code` provider) or set `OPENROUTER_API_KEY` / `GEMINI_API_KEY` for other providers ## Install ```bash cd runtime uv sync ``` ## Usage ```bash cd runtime # Focus on custodian domain (cheaper — ~10% of full summary tokens) uv run python agent.py --domain custodian # Full cross-domain view uv run python agent.py --all # Preview actions without executing uv run python agent.py --domain custodian --dry-run # Use a different LLM provider uv run python agent.py --domain custodian --llm gemini uv run python agent.py --domain custodian --llm openrouter # Custom state-hub URL uv run python agent.py --domain custodian --api-base http://10.0.0.5:8000 ``` ## Output The agent prints a trace to stdout: ``` [custodian-agent] 2026-03-12T20:00:00 scope=domain=custodian [observe] fetching state from state-hub… [orient] loading constitution and building context… [decide] calling LLM via provider='claude-code'… [act] executing plan (live): 1 events, 0 task updates, 0 flags ✓ add_progress_event: 'Reviewed custodian domain: 2 active workstreams…' [custodian-agent] done — 1 actions. ``` The LLM's reasoning trace is saved to `memory/working/agent-session-{ts}-{scope}.md`. ## Tests ```bash cd runtime uv run pytest -v ``` All 32 tests run offline (no live state-hub, no LLM API key required). ## Extending - **New observations**: extend `build_context()` in `context.py` - **New actions**: add to `actions.py` and update `SANCTIONED_ACTIONS` — but any expansion of the action surface requires a new ADR and human approval (see constitution §2–§4) - **Tool adapters**: add to `tool_adapters/` following the llm-connect `LLMAdapter` pattern