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 and recurring review prompts
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:
claudeCLI in PATH (for defaultclaude-codeprovider) or setOPENROUTER_API_KEY/GEMINI_API_KEYfor other providers
Install
cd runtime
uv sync
Usage
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.
Recurring review prompts:
prompts/daily_statehub_wsgi_triage.md- daily State Hub WSJF triage used by thedaily-state-hub-wsjf-triageautomation and the activity-core handoff definition in../activity-definitions/.
Tests
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()incontext.py - New actions: add to
actions.pyand updateSANCTIONED_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-connectLLMAdapterpattern