Files
the-custodian/runtime
tegwick 2fdbcb5d7a 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>
2026-03-12 22:36:24 +01:00
..
2026-02-24 12:01:37 +01:00
2026-02-24 12:01:37 +01:00
2026-02-24 12:01:37 +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
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

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.

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() 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