ADR 0031: One master agent, one harness per run, enforced from the run row; lens reviews read-only; no external MCP servers
- Status
- Accepted
- Date
- Deciders
- Founder
Context
The founder asked for a master agent that impersonates each role through a harness and behaves differently per role. Fact (verified in source): run_tokens_for(run) reads the run row and mints once per run with no allowlists, so every token carries allowlist_hash(()) and the server accepts either the role hash or EMPTY_ALLOWLIST_HASH; run.status is already read from the database on every call. A harness enforced by a prompt, or by a hash that is never re-minted, is not enforced.
Decision
HarnessSpec(key, personas, prompt_ref, toolset, skills, memory_lenses, rubric_key, plan_templates, refusal_rules, status_template, record_types);HARNESS_VERSION = "harness_v1",PROMPT_VERSION = "console_v2";HarnessSpec.toolsetis the only tool-to-harness relation (noToolSpec.harnesses).agent_runs.harness_keyis the authority. The API sets it when the run is created;run_tokens_forcopies it into aharnessrun-token claim;load_call_staterefuses401 harness_mismatchon a difference;tools/list = tools_for_role(role) ∩ HARNESSES[run.harness_key].toolset;check_allowed(..., harness)returnsharness_denied, audited likepolicy_denied. A harness never changes inside a run; no token is re-minted; the allowlist hash is unchanged. The worker’s bootstrap envelope carriesharness_key,harness_version,harness_personasandtwin.workbench_role_keyso the container wears the row’s harness;tools/listrefuses401 run_not_foundwhen the run row is missing and never falls back to the claim (recorded 2026-09-16).- Lens reviews (R5): one
AgentRun(kind='lens_review')of sequential graph nodes under a read-only approved plan, on a person’s explicit request only, at most four lenses; no verdict word, no aggregate, no ranking; “A lens is not a review. Only a<role>reviews.”; a scorecard is never a stage-gaterequired_record, a check input or an approvals attachment; a lens requested by the proposer cannot attach to the proposer’s decision record. - External MCP servers are not proxied in any release: rejected while a REST API exists; revisit only for a source with no REST API; if ever added, wrap named upstream tools as static
ToolSpecs and never forward an upstreamtools/list. The structural test “no external server mounted” stays.
Consequences
agent_runsgainsharness_key, harness_version, rubric_version; the audit envelope recordsrun_id, harness_key, harness_version, rubric_key, rubric_version, act.- Untrusted reads advertise
_meta.trovensa.untrusted_textand the container drops them for every non-reader agent; structured reads carry a requiredoutput_schemaenforced in_call_read(the 35 phase-2/R3 reads inLEGACY_READS_WITHOUT_OUTPUT_SCHEMAare exempt until R5; the set is pinned so it can only shrink; recorded 2026-09-16). - Twin runs keep
persona_keyas the authority for duties.
Alternatives considered
- Re-minting tokens with a per-harness allowlist hash: the mechanism does not exist and would add a second authority; rejected.
- One child run per lens: cost and a parent-child protocol; rejected for sequential nodes in one run.
- Proxying vendor MCP servers: an upstream
tools/listwould bypass the closed catalogue; rejected.
Follow-ups
- Measure lens runs before raising
max_lenses_per_runabove four. - Harness prompts for the remaining workbench roles (R5).