Development record
abcd-runs-a-delegated-agent-through-a-command-line-model-run
specclosedspc-2609221533057881
Summary
The design record for itd-2609201916056194: one runner interface, a route per role, a configured fallback and the fallback as recorded intel.
Scope
- The interface (
internal/core/runner):Run(role, brief, contract) (answer, transcript, err); the host implementation is the existing sub-agent dispatch, so the loop calls one thing (criteria 1, 2). - The adapters:
claude(print mode, bare, structured output, allowed tools from the role's contract) andopencode(run mode, or the server's session and prompt endpoints where a server is already up); each parses the harness's structured events into the one answer shape and writes the transcript to abcd's store (criteria 1, 6, 7). - The route:
roles.<role>.runnerthrough the layered resolver; unset is host (criterion 2). - The fallback: one place decides it (runner error, absent binary, non-zero exit, unparsable answer); it calls the host implementation, or
runner.fallback_hostwhen no host session is present, and appends a receipt to the run's state; the summary counts them (criteria 3, 4). - The allowlist resolved before launch (adr-2609221009491186) (criterion 5).
- Review: security reviewer on the lane (criterion 8).
Out of scope
- The tier's choice; installing a harness; a served console.
Approach
The runner interface is the same shape the validator stage already defines for reviewers, so a role's route is one lookup; the fallback is one branch with one receipt writer, which is what makes the count trustworthy.
Footprint
- packages: internal/core/runner, internal/core/implement, internal/surface/cli
- tests: each adapter against a fake harness binary; the fallback receipt on each failure kind; the count in the summary; the allowlist refusal; the bare flag asserted in the launch
How the criteria are satisfied
| Criterion | Where |
|---|---|
| 1 same brief, contract, store | scope 1, 2 |
| 2 unset unchanged | scope 3 |
| 3 fallback and receipt | scope 4 |
| 4 counts in the summary | scope 4 |
| 5 allowlist | scope 5 |
| 6 bare flag | scope 2 |
| 7 receipts differ only in route | scope 1, 2 |
| 8 security review | scope 6 |
Progress
The spec stays open: the live proof is owed (iss-2609301519558538) and no ruling yet lets the intent close on fake harnesses.
- Landed: scopes 1 to 5 in the core (
internal/core/runner): the runner interface and the one dispatcher, the claude adapter (print mode,--bare, stream-json,--permission-mode dontAsk, the role's tools allowed) and the opencode adapter (run mode, JSON events,--pure), the route through the layered resolver, the fallback with one receipt writer andTally, and the allowlist admitted at the read and again before a launch; a model a provider lists is admitted by its allowlist alone and refused only by a configuredoracle.denylistentry (adr-2609300107513982). - Landed: the loop wiring (spc-2609202134338445 piece 3):
implement stepdrives throughloop.Drive, which starts a routed role through the dispatcher with the brief and receipt path the host would get, validates its answer with the stage's own receipt verifier, stores its transcript in abcd's history store, and stamps the verified receipt or recorded return with the route that ran it (criteria 1 and 7, structurally); an unset role leaves the step and the state byte-identical (criterion 2); a fallback is recorded in the state'sfallbacksand the record (criterion 3) and counted per runner and per role byimplement statusandimplement record(criterion 4);buildandsteprefuse a runner configuration fault, a model off its allowlist included, before anything is created or launched (criterion 5). The bare flag is asserted in the launch (criterion 6). - Not built: the no-host path at the surface.
runner.fallback_hostworks in the core, but a surface that runs without a host session is the process driver's reversal of the host-delegated boundary, which waits on itd-2609201916151817's decision-6 ADR. - Owed: the live proof of criterion 7 and the phase-1 unverified points (iss-2609301519558538), and criterion 8, the security review, which the lane's report lists point by point for the reviewer.
Frontmatter
| id | spc-2609221533057881 |
|---|---|
| type | spec |
| lifecycle | closed |
.abcd/development/specs/closed/spc-2609221533057881-abcd-runs-a-delegated-agent-through-a.md
open on GitHub ↗ · commit history ↗
References1
- itd-2609201916056194A role runs through the command-line harness the operator chose, and every fall back to the host is recordedimplements
mentions7
- adr-2609221009491186A provider adapter serves only the models it lists, under a vendor denylist no listing overrides; everything else runs on the host
- adr-2609300107513982A provider adapter's allowlist alone decides which models it serves, and abcd bundles no vendor denylist
- iss-2609240227236354itd-6 passes its readiness gate (intent ready itd-6: READY, all seven checks ok) but cannot be built from today's tree: its spec spc-2609211950427074 stands on four things that do not exist. (1) The build loop's validator stage the adapter implements, itd-2609201916151817 (planned, spec spc-2609202134338445 open; there is no build verb). (2) The layered oracle.review resolver the model-tier and pacing intents share, itd-2609170822093401 and itd-2609201925079472 (both planned, specs spc-2609180535002478 and spc-2609202134341288 open). (3) The command-line runner the adapters chapter entry sits beside, itd-2609201916056194 (planned, spec spc-2609221533057881 open). (4) An MCP client, which the spec's Approach names as a new dependency subject to the new-dependency sign-off; go.mod has none. itd-6 declares builds_on [itd-2] only, so the gate has no edge to refuse on and READY reads as buildable to a run that picks it.
- iss-2609301519558538Deferred, owed to a person: the live proof of the command-line runner (itd-2609201916056194). Phase 1 and 2 prove every criterion on fake harness binaries only; no ruling admits a live check of the runner (ruling L names the site setup, the API adapter and the decision adapter; H8 lists the three paid-service checks and itd-6's RepoPrompt run), so the intent cannot close. What a person must run: (1) the intent's first proof, criterion 7 live: a ruthless review of a real lane routed roles.ruthless-reviewer.runner=opencode, from a lane the host drives, its return recorded beside a host-run review's and differing only in the route; (2) the claude runner under --bare with --permission-mode dontAsk and --allowedTools, confirming a nested claude does not refuse under an inherited CLAUDECODE, and noting that the headless page (read 2026-09-30) says bare mode never reads OAuth or the keychain, so the claude runner needs ANTHROPIC_API_KEY, a paid API key rather than the person's subscription; (3) opencode run --format json --pure: the event shape (the CLI page does not document it; the adapter assumes step_start/text/step_finish with a sessionID and a part) and whether run mode prompts for a permission, which the timeout bounds and records as a fallback.
- itd-6RepoPrompt over MCP is one opt-in reviewer route, beside the host's own agents
- itd-2609201916151817One verb takes a READY intent to delivered, and refuses while a decision is open
- spc-2609202134338445one-verb-takes-a-single-intent-from-ready-to-delivered-witho