Development record
A native local redacted transcript corpus
Context
abcd captured session transcripts through specstory, redirected to an external history store keyed on the repo's root-commit SHA. The rebuild drops specstory as a hard dependency (ADR-22), so abcd needs its own transcript capture — and transcripts are worth keeping for their own sake: they are the research and benchmark corpus that lets abcd study how its own flows behave. abcd already owns a proven redaction design — the two-stage write-time-sanitise-then-verify model of ADR-6 — built for exactly this class of content.
Decision
abcd captures transcripts into a native local redacted store as the specstory replacement and as a research/benchmark corpus:
- Local and native — no external tool required; capture is abcd's own code.
- Root-SHA-keyed — transcripts are keyed on the repo's root-commit SHA, so a repo's sessions group stably across clones and renames.
- Gitignored — the store is never committed; it is local working data.
- Redacted on capture — redaction runs at write time, before anything lands on disk.
Redaction reuses ADR-6's two-stage model — the write-time sanitiser rewrites high-confidence secrets and home-directory paths, and the pre-commit-class verifier catches the rest — so this store and the review store share one redaction discipline. This ADR links to ADR-6, it does not supersede it: ADR-6's decision stands; this ADR applies its redaction model to a new corpus.
A private companion / cloud store is optional — an operator may sync the local corpus to a private companion for cross-machine research, but the local redacted store is the default and works standalone.
Alternatives Considered
- Keep specstory + the external history store. Preserves the shipped capture path. Rejected: it hard-requires an external tool ADR-22 makes optional and puts the corpus outside abcd's control.
- Capture raw, redact later. Simplest capture. Rejected: raw transcripts on disk are an exposure window; ADR-6 already settled that redaction is write-time, and a research corpus is exactly where un-redacted secrets must never persist.
- A new redaction model for transcripts. Rejected: it would fork a security-critical discipline into two implementations that drift; ADR-6's two-stage model already covers this content class.
- Chosen: native, root-SHA-keyed, gitignored, redacted-on-capture, reusing ADR-6's two-stage model, optional private companion. One redaction discipline, no external dependency, a corpus abcd owns.
Consequences
- Transcript capture works with no external tool; the corpus is abcd's own local data, redacted before it touches disk.
- The redaction implementation is shared with the review store, so a fix to the sanitiser patterns hardens both surfaces at once — the reuse ADR-6's model makes possible.
- The corpus is available for research and benchmarking (studying abcd's own flows) without any content leaving the machine by default.
- An optional private companion/cloud sync is a separate, opt-in path over the same redacted store — never a prerequisite, never a place raw transcripts go.
Frontmatter
| id | adr-29 |
|---|---|
| type | adr |
| status | superseded |
| date | 2026-07-06 |
.abcd/development/decisions/adrs/0029-native-transcript-corpus.md
open on GitHub ↗ · commit history ↗
Referenced by11
- adr-22Bundled dependencies become pluggable adapters over a native defaultrelated
- adr-35The lifeboat is a coverage experiment — read-only, out-of-tree, and proven before it is packed (supersedes adr-4)related
- adr-2609090636172016Sub-agent transcript lineage is carried by explicit record fields, not a composite session idrelated
- adr-2609090717039680The transcript corpus is a sibling store that creates itself, and the per-repo location is an opt-in pullrelated
- adr-2609090717039680The transcript corpus is a sibling store that creates itself, and the per-repo location is an opt-in pullsuperseded by
- adr-2609091248201071The transcript corpus is a sibling store that creates itself through the canonical directory primitiverelated
- itd-59Every Autonomous Run Pass Leaves the Same Durable, Queryable Transcript an Interactive Session Doesrelated
- itd-89Capture the Session You Just Had, Because You Cannot Capture It Laterrelated
- itd-2609090559376002Every Sub-Agent a Session Spawns Leaves the Same Redacted Record Its Main Thread Doesrelated
- itd-2609091718566731Transcripts Already on Disk Are Recovered into the Right Repository's Corpus, Under That Repository's Own Redactionrelated
- itd-2609091718595846Any Captured Session Can Be Handed to an Agent as One Self-Contained Artefact, With a Telemetry File Saying What It Costrelated
mentions17
- adr-25The LLM is host-delegated by default; oracles are opt-in adapters
- adr-2610031751066232The transcript store is spelled under the renamed home, `~/.abcd.noindex/transcripts/<root-sha>/`, and the rest of its decision is unchanged
- dcn-26073000000000072026-07-30 — a cross-agent transcript-capture source is the current default SOTA upgrade for the history corpus, triggered when compatibility beyond Claude Code becomes a concern; adopted import-only: its output enters through the pre-declared `specstory-import` seam over the native store, so the fail-closed redaction applies to imported material, and the native floor stays the default with capture degrading to it when the tool is absent. The candidate tool, target harness, and provider-coverage caveats live in research/notes/2026-07-30-session-recording-sota.md (§6 addendum) — commitment records name no external tool before adoption is decided. The SOTA verdict is re-verified at the adopting intent; a move from optional to required tool is a sota-per-intent path-1 hard stop (new-dependency approval). Rejected: adopting the tool's in-repo history directory as a store (adr-29 already rejected external stores; transcripts stay out of the repo) and bespoke per-host parsers (a permanent maintenance burden the external tool already carries). See iss-217. (Recorded 2026-08-15; decided 2026-07-30, parked across the attribution epoch.)
- 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-26080200000000052026-08-02 — iss-39 (review pass): four correctness repairs to `record_schema`, each found by mutating the rule rather than reading it. (1) The "no lifecycle state escapes" invariant was implemented for bucketed stores only — a FLAT store (the ADRs) declares no buckets, so the walk read its files and never looked at its directories, and a whole `adrs/archive/` of records with mismatched ids and phantom successors was invisible to every check in the rule. A flat store declares zero buckets, so any subdirectory of it is undeclared by definition, and now says so. (2) The retirement escape hatch was self-attesting: `retired` was built from every record's `supersedes` before the resolution loop ran, so one record naming `adr-9999` there made that id resolve in every other record's `related_adrs` — reopening the exact phantom-reference class the rule exists to close. It is now bounded by the store's allocation high-water mark (an id above it was never issued, so nothing can have pruned it) and the declaration itself is reported; `supersedes` also leaves `recordRefFields`, where it could never fail its own check and read as coverage it did not provide. (3) The shared frontmatter scanner is a same-line scanner, so a block sequence (`supersedes:` then indented `- adr-12`) read as empty — and an empty read here did not merely under-report, it made the bidirectional check assert that ANOTHER file omitted a link that file plainly carried. Other rules degrade to "field missing on this file"; this one degraded to a confident false claim about a different file, which is worse, so the block spelling is folded in at the one place the rule parses handles. Local to `record_schema` rather than in the shared scanner: the canonical scanner has many consumers whose keys are scalars, and widening it for one rule's list fields would change what every other rule reads. (4) A dot-directory (`.obsidian`, `.vscode`) tripped a blocker as an undeclared bucket; tooling state is not a lifecycle the record authored. Also disclosed rather than fixed: widening `superseded_by` to `^(itd|adr)-` makes `intent_lifecycle` looser ALONE — it reads only the intent tree and cannot resolve an `adr-` target — so the CHANGELOG and the pattern's own comment now say the two rules are armed together and `record_schema` is what resolves a cross-store target. The surviving adr-6 prose citations are captured as iss-179 rather than papered over: 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.
- 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.
- 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-217add a cross-agent session-capture source as an optional plugin
- iss-2608280824478819The canonical credit surface is incomplete: design-shaping inspirations fully credited in the committed record never propagated to ACKNOWLEDGEMENTS.md. Backfill set from the 2026-08-28 attribution review: add Inspirations entries for CARL and PAUL (both Kahler, MIT; mechanisms adopted per itd-3 and itd-1), SpecStory (adr-29 seam, specstory-import ships in CLI help), the Karpathy LLM-wiki gist (memory-substrate pattern), gitleaks and TruffleHog (integrated scanners, one self-declared adapted regex), and Dieter Rams; complete the Diataxis entry with the author and CC-BY-SA 4.0; widen the mattpocock entry (ADR three-clause test, PRD shape); add Dell'Acqua et al. 2023 to the CSL references; add source URLs to the Horn and Weng credits and the arXiv id to the Iacob citation; record the skills' authorship in the socratic/first-principles research note; fix the dead to-prd deep link in itd-27.
- iss-2609090715154147The convention is that a decision record is never amended in place: when a decision changes, a new record is minted and the old one is marked superseded by it, so the decision history stays legible and a reader of the old record is pointed forward rather than misled. Nothing in the tree states it. The typed-link vocabulary is recorded in .abcd/development/principles/decompose-before-filing.md as supersedes / reverses / duplicates / refines, and adr-58 and adr-2609021016286571 demonstrate the shape in practice, but the rule that amendment is not an option is carried only by example, so an agent or a contributor reaching for the smaller edit has nothing to read that refuses it. The gap surfaced when a relocation of the transcript corpus made adr-29's stated location wrong and the question of amend-versus-supersede had to be asked rather than looked up. Fix: state the rule where the conventions live, in the AGENTS.md decisions section or as a principle under .abcd/development/principles/, naming the two typed links and the one edit a superseded record does receive, and say why: an amended record erases the fact that the decision changed, which is the thing the record family exists to preserve. Detector: the conventions text names the rule and a docs test or record-lint rule refuses a decisions section that omits it, or at minimum the rule is present where a contributor reading AGENTS.md would meet it before editing a record.
- iss-2609100518527863A record can cite a record id that does not exist, and for one record family an id cannot be resolved to a file mechanically at all. A session writing a spec during an autonomous run put two invented ids into it and caught them only by reading the document back afterwards; nothing in the record gates checks that an id written inside a record resolves to a record that exists. The gap is wider than a missing check. Two filename conventions coexist in the decisions family: the hand-numbered records carry a zero-padded ordinal in the filename whose value differs from the id in their own frontmatter, and the minted ones carry the timestamp id without the family prefix the id itself begins with. Neither shape lets a reader map a cited id to a file by name, so a resolver has to parse the frontmatter of every record in the family before it can answer, and an agent checking its own work by hand cannot do it reliably at all. Any check that lands has to resolve through frontmatter rather than filenames, which is also the reason the check does not exist yet.
- iss-2610262032188853itd-89 files transcripts in the local per-repo store; invariant 15 makes the user-level `~/.abcd.noindex/transcripts/<root-sha>/` the store, with a per-repo store only for a declared checkout
- spc-3lifeboat-coverage-experiment
- spc-4start-the-transcript-clock
- spc-2609090624222051A session's sub-agents are captured through the completion event, with lineage the record carries in fields
- spc-2609091722230648The recovery half: transcripts on disk are ingested into a named destination, and composite records are repaired in place
- spc-2609091722269727One session, one artefact, one telemetry file: what `history reconstruct` renders and what it refuses to assert