feat(CUST-WP-0001): implement Custodian Agent Runtime bootstrap

T2 complete: OODA loop skeleton with LLM integration, bounded actions,
and 32 offline unit tests.

Deliverables:
- runtime/agent.py     — CLI entry point (--domain/--all/--dry-run/--llm)
- runtime/context.py   — Observe: fetch_state + build_context
- runtime/actions.py   — Act: parse_plan + execute (3 sanctioned writes)
- runtime/README.md    — usage guide and architecture overview
- runtime/tests/       — 32 tests, fully offline
- runtime/pyproject.toml — standalone package with llm-connect dep
- canon/architecture/adr-002-custodian-agent-runtime-design.md

Key design decisions (ADR-002):
- Lives in runtime/ (not a new repo) — tight canon/state-hub coupling
- ClaudeCodeAdapter by default (local-first, no API key)
- Single-pass synchronous OODA for v0.1 simplicity
- Exactly 3 sanctioned write ops: add_progress_event, update_task_status, flag_for_human
- LLM returns JSON block in markdown for structured+auditable output

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:36:24 +01:00
parent 5358d417ec
commit 2fdbcb5d7a
11 changed files with 1227 additions and 3 deletions

109
runtime/README.md Normal file
View File

@@ -0,0 +1,109 @@
# 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