Development record
Bundled dependencies become pluggable adapters over a native default
Context
abcd bundled five external tools and treated them as load-bearing: specstory
(transcript capture), RepoPrompt and codex (the review/oracle backends),
flow-next (spec and task orchestration), and Ralph (the autonomous run loop).
Each hard dependency forced abcd to carry machinery whose only job was to make
someone else's tool safe to live inside abcd: the overlay manifest and
dispatcher that survived a /flow-next:ralph-init re-vendor, the argv-sentinel
session mirror, the ahoy doctor bypass probes, the abstraction boundary that
warned an operator they had reached past the wrapped surface. That subsystem
existed only because the tools were mandatory and re-vendored themselves on
top of abcd's own state.
The rebuild removes the premise. No external tool is a required dependency.
Decision
Every previously-bundled tool becomes an opt-in adapter over a native default. abcd works with none of them installed; each is a capability an operator can plug in, never a floor abcd stands on:
- specstory → native local redacted transcript store (ADR-29) is the default; specstory is an optional capture source.
- RepoPrompt / codex → host-delegated LLM (ADR-25) is the default oracle; native/CLI/API/MCP backends are opt-in adapters.
- flow-next → native minimal spec/task store
(ADR-26) is the default; the companion
harness
ccpmis the primary deeper backend. flow-next is not built. - Ralph → the autonomous run is a pluggable seam (ADR-27), not a Ralph port.
Because no tool re-vendors itself onto abcd's state, the entire overlay/dispatcher/session-mirror/boundary subsystem ceases to exist. This ADR therefore supersedes the three decisions that built and tuned it — and itd-47, the intent that rested on the same bundled cascade:
- ADR-14 — the degraded-fallback guard over the required overlay manifest.
- ADR-15 — the warn-not-block abstraction boundary, the argv sentinel, and the artifact-only doctor probes.
- ADR-17 — the
rp chat-sendoverride machinery layered onto the bundled RP path.
Alternatives Considered
- Keep the tools as hard deps, keep the overlay. Preserves the shipped integration work. Rejected: the overlay is pure tax — its every component exists to contain a mandatory foreign tool, and it re-accrues on every upstream release. Mandatory bundling is the thing the rebuild is removing.
- Drop the tools entirely, no adapters. Simplest. Rejected: RP/codex/ccpm are genuinely valuable when present; discarding the ability to plug them in throws away capability the adapter seam keeps cheap.
- Chosen: native default + opt-in adapter per tool. abcd stands on its own code; a present tool is an upgrade, never a prerequisite.
Consequences
- The overlay manifest,
scripts/ralph/flowctldispatcher,ralph_mirror.pysession shim, the--abcd-drivensentinel, and the doctor bypass probes are all removed./abcd:ralph-up,--fresh, and the re-vendor mandate go with them — there is nothing to re-apply because nothing is re-vendored. - The abstraction boundary as a reach-past warner is retired: with native defaults there is no wrapped foreign surface to reach past. abcd's guarantees live in its own core, not in a warning that an operator left it.
- Each adapter carries its own thin capability contract (interface + native fallback), so a missing or misbehaving tool degrades to the native path rather than breaking abcd.
- The doctor's job narrows from policing foreign bypass artifacts to checking abcd's own health and which adapters are wired.
- The first declared exception (proposed 2026-10-16, signed off by the
product thinker on 2026-10-22, dcn-2610222041417934). The fence around the agents abcd's runner launches
(adr-2610161621415708,
spc-2610161751056096) is Anthropic's sandbox-runtime (
srt), an external program abcd does not ship. On the runner route alone it is a requirement, not an adapter that degrades: a fence has no native default to degrade to, since abcd writes no fence of its own and an unfenced launch is what the fence exists to remove. A machine without it refuses a runner launch, and the person's per-machine off switch (runner.sandbox.offin~/.abcd.noindex/config.json) takes the place of degradation. Every other route still needs no external tool. Wheresrtis missing, abcd offers to install it and installs only after the person's explicit yes (dcn-2610222041424027).
Frontmatter
| id | adr-22 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-07-06 |
.abcd/development/decisions/adrs/0022-bundled-deps-as-pluggable-adapters.md
open on GitHub ↗ · commit history ↗
References9
- adr-23A transport-agnostic Go core behind thin front doorsrelated
- adr-25The LLM is host-delegated by default; oracles are opt-in adaptersrelated
- adr-26A native minimal spec layer with the companion harness `ccpm` as the primary deeper backendrelated
- adr-27The autonomous run is a pluggable seam, not a Ralph portrelated
- adr-29A native local redacted transcript corpusrelated
- itd-47Oracle-Backed Gates Pass Honestly Without A Human In The Loopsupersedes
- adr-14not in the treesupersedes
- adr-15not in the treesupersedes
- adr-17not in the treesupersedes
Referenced by10
- adr-21Rebuild abcd as a Go binaryrelated
- adr-24the companion harness is a peer integrated via conventions and MCP, not a code dependencyrelated
- adr-38Implicit checks are disk-only — the network answers only an explicit askrelated
- adr-2609090717039680The transcript corpus is a sibling store that creates itself, and the per-repo location is an opt-in pullrelated
- adr-2609091248201071The transcript corpus is a sibling store that creates itself through the canonical directory primitiverelated
- adr-2611011813195749abcd never runs a foreign executable under its own name, and no code that writes records is loaded at run timerelated
- itd-63The Amateur Coder Is Told What Is Being Installed And Why, Not Just Asked To Run A Commandrelated
- itd-2609292108373494The grill hands the interview to an installed interviewing skill, and runs its own when there is nonerelated
- itd-2609292109005937An opt-in local model checks every prompt for secrets and personal data before it leaves the machinerelated
- itd-2609292109214516Rule injection is a seam: the native loader stays the default, and an opt-in CARL back end can take it overrelated
mentions26
- adr-2610161621415708Unattended runs abcd launches execute inside an OS sandbox
- dcn-26072400000000042026-07-24 — itd-28 gitleaks sign-off: Stage 2 runs through the scanner
- dcn-26080200000000042026-08-02 — iss-39: record-lint gains `record_schema`, the first rule that reasons ACROSS the record's stores (ADRs, intents, specs, the issue ledger) rather than inside one, and the `superseded/` exemption narrows for the intent tree: both rules that read it (`intent_lifecycle`, `intent_impact_valid`) now run there too, while the spec-store rules (`spec_lifecycle`, `spec_id_unique`) still honour it as before and the cross-store `record_schema` never consulted it at all. Four invariants, one scan: a cross-reference field names a record the corpus has; a supersession is declared from BOTH sides; a filename and the id inside it agree; and every lifecycle directory is enumerated, so an undeclared bucket is a finding rather than a state no rule reads. Scope is deliberate on two axes. (1) Cross-references are checked in the machine-readable frontmatter fields (`supersedes`, `related_adrs`, `related_intents`, `builds_on`, `blocked_by`), not in bare prose: a frontmatter handle is a claim that the record exists and is a live input, whereas prose legitimately narrates ids that do not resolve ("itd-38's id was released, not reserved"; a test fixture's `itd-9999`; adr-29's account of what ADR-6 settled) — arming prose would have cost ten false positives against one true one, the same false-positive budget that scopes GL002. (2) A handle resolves either to a file or to a `supersedes:` declaration elsewhere, because the ADR lifecycle PRUNES a superseded record and keeps the trace in its successor — that is what distinguishes adr-4/adr-8/adr-14..18 (pruned, accounted for) from adr-6 (cited, never accounted for). The exemption narrowing is the root cause of the corpus defect it exposes: `intent_lifecycle` is a schema rule, but the `superseded/` bucket exempted its own records from the one rule that checks a supersession is recorded, so itd-47 and itd-49 sat there for months with a prose blockquote and no `superseded_by` field. Being historical excuses a record from how it is WRITTEN, never from being well-formed. `superseded_by` also widens from `^itd-\d+` to `^(itd|adr)-\d+`: an ADR that redecides the question an intent rested on retires that intent as surely as a successor intent does, and refusing to spell that forced those two records to say nothing at all. Corpus repairs in the same change: adr-6 is dropped from the two `related_adrs` lists and the two brief pages that cited it re-point at adr-29 (which records the two-stage redaction model and states in its own body that adr-6's decision stands — adr-29 does not supersede it, so claiming it does would have written a falsehood to clear a finding); adr-12 and adr-32 gain the bidirectional pair the ledger-location redecision always implied, adr-12's status moving to `superseded` and the index row with it; itd-47/itd-49 gain `superseded_by: adr-22`/`adr-26` with `kind_at_supersession`, and those ADRs name them back; and the pre-existing one-way links itd-32→itd-31 and itd-31→itd-48 gain their reverse halves. CHANGELOG impact `additive`, matching iss-38's disposition on the same axis: the rule ships in the lint engine `abcd docs lint` runs, and any abcd-managed repo gains it by declaring its own `record_stores`.
- dcn-26080200000000062026-08-02 — iss-39 (merge-gate pass): four more `record_schema` corrections, all of the same family as the previous round — a check that was right for one shape and absent for its sibling. (1) The undeclared-directory finding was implemented for store ROOTS only; a directory inside a declared bucket (`intents/shipped/archive/`) was skipped with no finding at all, so the previous round's flat-store fix closed the escape for one store of four. Both cases are now reported from the one place that reads a directory of records, because a flat store and a declared bucket hold records the same way and a directory in either is a lifecycle nobody declared. (2) `spc` was missing from the handle pattern although the spec store is indexed and armed: a bidirectional spec supersession tripped a false blocker whose message named the very prefixes it omitted, while the reverse direction parsed to zero handles and was never checked. The alternation now covers every store the rule indexes, and the message is composed from one constant so it cannot drift from the pattern again. (3) `superseded_by: []` was a false blocker: the empty flow sequence is this record's house spelling for an empty list (`related_rfcs: []` across six ADRs), so it means absence. Fixed with a local `isAbsentValue` rather than by widening the shared `isNull`, which also judges scalar fields (kind, impact, slug) where a list literal is a wrong value rather than an unset one. (4) The filename/id check compared strings, so `id: adr-0012` in `0012-*.md` was reported as a mismatch — contradicting the rule's own stated design that the padded and bare spellings are one handle; it now parses both sides and compares numerically, as every other comparison in the rule already did. Record-side: iss-179 now cites the recoverable evidence for what adr-6 was (`.abcd/work/reviews/2026-07-06-plan-consistency/05-link-structure.md` lines 47-49 name the original file, `03-cross-corpus-consistency.md` line 42 records it as voided by DECISIONS.md lines 8-9 together with adr-8 and adr-17 — the only other two ADRs that decision voids, both since pruned by a successor that names them (adr-25, adr-22), which is what leaves adr-6 alone — and observes that the unused `deprecated` status is the state it fits) so the disposition is a decision with evidence rather than an open question; the two brief re-pointings drop a reflexive attribution that had adr-29 reusing a model from itself; and adr-22, adr-26, and adr-32 gain one body clause each naming what their newly-declared `supersedes` handles retire, so the frontmatter and the prose say the same thing.
- dcn-26082200000000012026-08-22 — Bug-hunt round 7, branch bughunt-b/round-7. Baseline (make preflight
- dcn-26101618092596232026-10-16 — The fence for the agents abcd's runner launches (itd-2610161621364075 part A) is Anthropic's sandbox-runtime (srt, Apache-2.0, npm @anthropic-ai/sandbox-runtime, tested against 0.0.79, which is not an exact pin: an untested version is warned about and a failed trial launch refuses) for both the claude and the opencode runner, on macOS and Linux, run by abcd at its one launch point as srt --settings <per-launch file> -- <harness>, behind a fence seam (internal/core/fence: a fence-neutral Policy and a Fence interface with Check, Wrap and Refusals) whose one adapter, internal/core/fence/srt, runner.sandbox.backend selects with srt as its default. Grounds: ruling 6 names Anthropic's own fence while abcd launches Claude and rules out a fence of abcd's own; srt wraps any process, so one fence and one policy derivation serve both runners (one-canonical-primitive), which settles how opencode launches are fenced; it is the code Claude Code's own sandbox is built on and the vendor's guide names it for a whole-process boundary. Weighed and not picked: nono (Apache-2.0, Seatbelt and Landlock, pre-1.0, not Anthropic's; the first alternative if the srt probes fail), landrun and go-landlock (Linux only, and abcd would write the policy layer), bubblewrap alone (a building block, Linux only), nsjail (Linux only, built for jailing services), firejail (setuid root, CVE-2022-31214), a sandbox-exec profile of abcd's own (a fence of abcd's own, and the command is deprecated), Codex's sandbox (ships inside another vendor's agent; reconsidered when abcd launches Codex). An adversarial fit-challenge kept the pick and changed the design: the main git directory is narrowed to what a lane's commit writes with a ref check after each launch, srt's interpreter and package are admitted as well as its script, srt's placeholders are cleared after a killed launch, opencode's state is seeded rather than shared, and reads of credential stores a launch never needs are denied. srt is a new required program on the runner route and brings a network host list with it, since it refuses '*'; both wait for the product thinker's sign-off before anything is built. An advisor's fit review the same day kept the pick and changed the design again: GOPATH moves into the run's fence folder with the Go caches, since the checksum-database cache lives under it, and the hosts GOPRIVATE and GONOPROXY name join the host list; lane commits are proposed unsigned inside the fence and signed by abcd at land, so no signing key is opened to the agent; automatic gc is sealed off for each launch; the approver is abcd's own executable, admitted as srt is; srt is framed as adr-22's first declared exception, scoped to the runner route, with the per-machine off switch in place of degradation; and the probes now come before the sign-off. Five questions go to the product thinker with the sign-off, listed in spc-2610161751056096.
- dcn-26102220414179342026-10-22 — Anthropic's sandbox (srt) is required for the agents abcd starts on its own (product thinker's ruling of 2026-10-22, spc-2610161751056096 step 2 question a, itd-2610161621364075): every agent the runner launches runs inside srt, on macOS and Linux, and on a machine without it no such agent starts; this is adr-22's first declared exception, scoped to the runner route alone, and the per-machine off switch runner.sandbox.off stays the person's way out. The proposed marks on adr-22's consequence and on the dependencies chapter's paragraph come off
- dcn-26102220414240272026-10-22 — When srt is missing, abcd offers to install it and installs only after the person's explicit yes (product thinker's ruling of 2026-10-22, spc-2610161751056096 step 2 question b): the refusal before a run names the install it would make and asks; a yes runs the install with everything the person's account can reach, which the product thinker accepted; anything short of a yes installs nothing, and the run stays refused with the install command and the off switch named
- iss-64Make rule-injection a seam: keep the native Go loader (itd-3) as the floor, add an opt-in 'carl' backend so a user with CARL installed can route to it. Per adr-22 native-default + easy-onboard-a-better-external. Config rules.backend: native | carl.
- iss-179adr-29 and adr-25 still narrate adr-6 in prose, but adr-6 is in no store and is absent from all git history (the ADR set landed in one import commit; 0004/0006/0008/0014-0018 were never migrated, and every one but 0006 is accounted for by a successor's supersedes). adr-29 states in its own body that adr-6's decision stands and that adr-29 does not supersede it, so the supersession vocabulary cannot record it and neither ADR body should be rewritten to paper over it. iss-39 closed the machine-readable half (the two related_adrs entries, and the two brief pages that cited it). The surviving prose sites are adr-29 lines 24, 39, 43, 56, 60, 63, 71 and adr-25 lines 51, 80; adr-29's line 43 in particular still says it "links to ADR-6" now that the frontmatter link is gone, which is the residual made legible rather than hidden.
- iss-2608220150157497principles/sota-per-intent.md links `[adr-22](../decisions)` and `[adr-26](../decisions)` to the decisions directory instead of the ADR files; links_resolve passes because the directory exists; the only two ID-labelled links in the record whose target does not match the id
- iss-2608220150157498Eight typed cross-references point at targets absent from the tree — adr-22 supersedes adr-14, adr-15 and adr-17; adr-25 supersedes adr-8; adr-27 supersedes adr-16; adr-28 supersedes adr-18; adr-35 supersedes adr-4 (all retired under retire-the-name); itd-3 names spec_id spc-1 which has no file — and nothing checks supersedes targets today. The 2026-08-21 site investigation counted six; the in-session grep found eight. The planned site build arms the detector via the .abcd/site-baseline.json ratchet, seeded with what the build finds, and the tombstones-or-stubs question (itd-136/itd-137) decides how they render
- iss-2610262031545672itd-2610161733316518 turns off the host fallback inside a matrix cell; invariant 2 says a missing or failing adapter degrades to the native path, and the brief declares no exception for this.
- itd-6RepoPrompt over MCP is one opt-in reviewer route, beside the host's own agents
- itd-23Lifeboats Speak To External Tools
- itd-28Spec-Tied Reviews Live Next To The Spec They Reviewed
- itd-62abcd Routes The Invisible Risks An Amateur Cannot See To A Real, Fail-Closed Gate That Wraps A Trusted Scanner
- itd-114Two Agents Can Mint At The Same Instant And Never Collide
- itd-160Eight typed cross-references point at targets absent from the tree — adr-22 supersedes adr-14, adr-15 and adr-17; adr-25 supersedes adr-8; adr-27 supersedes adr-16; adr-28 supersedes adr-18; adr-35 supersedes adr-4 (all retired under retire-the-name); itd-3 names spec_id spc-1 which has no file — and nothing checks supersedes targets today. The 2026-08-21 site investigation counted six; the in-session grep found eight. The planned site build arms the detector via the .abcd/site-baseline.json ratchet, seeded with what the build finds, and the tombstones-or-stubs question (itd-136/itd-137) decides how they render
- itd-2610161621364075The agents abcd launches on its own work inside a sandbox the operating system enforces
- itd-2610171228577260A product thinker settles a visual design in rounds that abcd runs end to end
- sota-per-intentSOTA Per Intent
- spc-38alice-opens-the-record-explorer-and-reads-the-whole-developm
- spc-52dangling-supersedes-and-spec-targets-nothing-checks-them
- spc-2609211955339422setup-wizard-explains-installs
- spc-2610161751056096a-person-who-leaves-abcd-to-run-on-its