CUST-ADR-002 accepted · accepted-1 the-custodian reviewed 2026-03-12generated from canonical source — do not edit

Custodian Agent Runtime — v0.1 Bootstrap Design

Source: the-custodian · canon/architecture/adr-002-custodian-agent-runtime-design.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb

Review due: 2026-09-12

Status

Accepted.

Context

CUST-WP-0001 requires a first working skeleton of the Custodian as an acting agent: a loop that observes project state, reasons about it, and executes bounded write operations — without human interaction for each step.

The dependencies (llm-connect, operational Railiance infra) are now resolved. This ADR captures the five key architectural decisions for the v0.1 bootstrap.

Decisions

D1 — Location: runtime/ inside the-custodian (not a new repo)

Decision: The runtime lives under the-custodian/runtime/ as a standalone Python package (pyproject.toml, own venv) rather than a new repository.

Rationale: The runtime is tightly coupled to canon (reads constitution, memory) and the state-hub (its primary coordination layer). A separate repo adds friction with no v0.1 benefit. The existing runtime/ scaffold confirms the original intent. Extraction to its own repo is deferred to when the runtime has stable boundaries and multiple consumers.

D2 — OODA loop: single-pass synchronous CLI

Decision: One run() call = one complete Observe → Orient → Decide → Act cycle. Entry point is a CLI (agent.py) invoked manually or by a cron job.

Observe  — HTTP GET to state-hub: state summary or domain summary
Orient   — Load constitution + build structured LLM context prompt
Decide   — Single LLM call (via llm-connect) returns a JSON action plan
Act      — Execute only sanctioned write operations from the plan

Rationale: Async event loops and daemons add operational complexity that v0.1 doesn't need. A single-pass CLI is testable, debuggable, and can be scheduled externally. The transition to an event-driven loop is Phase 2.

D3 — LLM backend: ClaudeCodeAdapter by default

Decision: The runtime uses llm_connect.ClaudeCodeAdapter as its default LLM backend (shells out to claude --print). Provider is configurable via --llm flag to support gemini, openrouter, or openai.

Rationale: ClaudeCodeAdapter requires no API key and honours the Local-First value (V2). All current deployments have Claude Code available. The llm-connect abstraction means switching providers is a one-line change.

D4 — Action constraint: three sanctioned write operations only

Decision: The agent may execute exactly three state-hub write operations without human approval:

  1. add_progress_event — append an observation to the event log
  2. update_task_status — mark a task done/in_progress (reversible)
  3. flag_for_human — raise an intervention flag (escalation, not action)

All other operations (create workstream, record decision, resolve decision, write to canon) require human approval before execution.

Rationale: Constitution §3/§4 require bounded agency. The three operations are either append-only (progress events), reversible (task status), or explicitly escalating (flag). They cannot produce irreversible harm.

D5 — LLM response format: JSON block in markdown

Decision: The LLM is prompted to return a Markdown response with a fenced ```json block containing the structured action plan:

{
  "observations": ["..."],
  "progress_events": [
    {"summary": "...", "workstream_id": "...", "event_type": "note"}
  ],
  "tasks_to_update": [
    {"task_id": "...", "status": "done"}
  ],
  "tasks_to_flag": [
    {"task_id": "...", "note": "..."}
  ]
}

The surrounding Markdown is preserved as a human-readable reasoning trace and written to memory/working/ as a session note.

Rationale: JSON blocks are robust to extraction (delimited), LLMs produce them reliably with clear instructions, and the surrounding prose gives Bernd an auditable reasoning trace without requiring a separate reasoning step.

Consequences

  • runtime/ becomes a standalone Python package; make agent-run DOMAIN=x invokes it.
  • The runtime has no DB schema changes and no new API endpoints — it is a pure client of the existing state-hub HTTP API.
  • Autonomous actions are limited to append-only writes and escalations. Any expansion of the action surface requires a new ADR and human approval.
  • The v0.1 loop is single-user (Bernd). Multi-agent expansion is Phase 2+.

Deferred

  • Async event loop / daemon mode (Phase 2)
  • RAG over canon (Phase 1 roadmap item)
  • Tool adapters beyond state-hub HTTP (planned in runtime/tool_adapters/)
  • Deployment on Railiance k3s as a scheduled CronJob