2026-03-12 22:36:24 +01:00
|
|
|
|
# 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
|
2026-05-17 23:10:57 +02:00
|
|
|
|
prompts/ System prompt templates and recurring review prompts
|
2026-03-12 22:36:24 +01:00
|
|
|
|
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`.
|
|
|
|
|
|
|
2026-05-17 23:10:57 +02:00
|
|
|
|
Recurring review prompts:
|
|
|
|
|
|
|
|
|
|
|
|
- `prompts/daily_statehub_wsgi_triage.md` - daily State Hub WSJF triage used by
|
|
|
|
|
|
the `daily-state-hub-wsjf-triage` automation and the activity-core handoff
|
|
|
|
|
|
definition in `../activity-definitions/`.
|
|
|
|
|
|
|
2026-03-12 22:36:24 +01:00
|
|
|
|
## 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
|