The furnace: one service with 15 methods centralizing the machinery the hooks kept duplicating - the ledger, the gate, the guarded write, the wiring, the schema factory.
The case study: how this family got built.
The development story of the twelve packages, told as it happened - the phases, the misdiagnoses named before the fixes, and the decisions that survive in the shipped seams. Every challenge below is stated with what actually happened.
Overview
The family grew from five bundles and 231 smoke checks to twelve packages across one commit era - the Oct 3–6, 2026 stretch that ends at 5982aa8 - with roughly 800 checks across twelve suites as the final count. Every step of the way, the smokes were the arbiter: zero assertion changes across refactors, renames and a framework port. And every real bug - the shadowed patch layers, the silent journal open, the version guard that could only succeed once - was found live, not by the smokes. That asymmetry is the story's spine, and the sections below keep it honest: what the suites caught, what only a restart could catch, and which two theories died on the way to the truth.
Phase I - The boilerplate era
The setup: three independent hooks - the package governor, the package pinner, the cargo governor - plus the dash normalizer, each carrying its own copy of the same machinery: ledgers, exclusion matching, policy loading, guarded writes, wiring, schemas. The decision (49e5b3d, 859f16a, 018919c): consolidate onto one factory service - 15 methods, one transform contract, and the ledger strings owned by the modules themselves. The technique: the Wire/Attach/ Continue seam trio, and a transform leaf the factory drives but never absorbs - each hook's transforms stay its identity. The outcome: five bundles green, 231 checks, and every ledger string byte-identical to pre-refactor behavior, smoke-asserted. The factory's page shows the seam table this era produced; it has since grown 15 → 16 → 17 methods as later phases moved machinery in.
The package.json governor: chain-canonicalization plus the update stage, run as a detached, contained continuation.
The static pinner: every ranged dependency version rewritten to its exact form, protected by a keep-list.
The Cargo.toml governor: surgical line rewrites on raw TOML and cargo upgrade driven through the subprocess seam.
The first stream normalizer: the dash family rewritten live in the model output, at the injection point a file hook does not have.
Two live-verified fixes from this era never regressed since: a hook's inject and Config must sit on the loader's default export object, and apply must return nothing - a returned value silently kills event delivery. Both are the kind of bug no type checker sees; both were found with a restart and a missing ledger line.
Phase II - The audit and the fixes
A confirmation agent re-verified all five bundles and found three things that rewrote the queue. Each is a finding with a fix; none was visible from the passing smokes alone.
The pinner's tsc went red - TS2339 four times plus TS2310, a type that recursively references itself as a base. The factory's built declarations shipped unresolved @Interface/* aliases; any consumer whose tsconfig maps the same paths resolves them to its own files. Runtime unaffected: the smoke still passed 28/28.
Fix. Relative specifiers everywhere and named type exports from the factory entry - then the type-import flip deleted the consumers' local structural mirrors outright (~208 lines). Type-only imports are erased, so the consumers' only-Config-imports-the-factory posture survives the flip untouched.
The build was nondeterministic: the build tool's TypeScript hook ran tsc then tsc-alias through an exec() that was never awaited, so the emitted declaration specifier style depended on timing - rebuilding did not reproduce the committed state.
Fix. Each bundle's prepublishOnly now runs the build with the racy hook neutralized, then explicit sequential tsc && tsc-alias - and a red tsc fails the build instead of shipping. Rebuild twice, get an identical tree.
The package governor still registered its in-flight key as the plain target key - the same key the factory's continuation uses. An update running under key K plus an external edit could orphan the update's controller. The cargo flavor had fixed the same collision locally a phase earlier.
Fix. The key helper moved factory-side as the 16th method - UpdateKey(target) yields update:<targetKey> - and both governors adopted it. Collision-window smoke cases went into the factory and governor suites the same day.
The rest of the queue executed in one session: the module terminology rename ( 3c29108 - mechanics only, the cargo flavor literal in the activation string stays), the standalone schema helper that deleted three config workarounds (060e3b6), the core extraction as a dependency-free pure package (1fef4ca), and the granularization splits (711d5b2 ). One operational beat worth keeping: the smoke suites lived in /tmp and were lost once - replayed from the session archives step by step, then archived to ~/.dsh/smokes/ forever after. The arbiter must not be disposable.
Phase III - Going live
The first live battery exercised everything the smokes had proven: the governor + pinner interlock on one manifest write, the update stages as real jobs, the cargo's actual cargo upgrade with the full argv, the dash normalizer counting replacements in live streams. It also surfaced the first live-only failure: the harness's storage domain enforces one open per name, so the shared journal domain was single-winner per boot. At that boot only the pinner's records landed; the governor's and the cargo's silently never did - while the human ledger files remained the complete record, which is the only reason the loss was even noticed.
The fix, in two commits
The first successful open binds its handle on the factory instance ( 952eaa1), and every module's records route through it. The boot-window race came next (aa53c91): records journaled before any open completes now buffer in a capped, drop-when-full pre-bind queue and drain with their timestamps preserved - a three-stage routing of shared sink, pre-bind buffer, then per-state fallback. The third boot confirmed both live:
One boot's complete record set - the shared domain
Before the fix, that file showed one module's activations. After it, all four - and the interlock cascade and the dispatched update stage record themselves in the same place. The update stage's awaited promise resolves at the dispatch: the background job keeps running, and the caller never awaits the job.
Phase IV - The stream family
The dash normalizer generalized. The core took over the pure machinery - generic over structural shapes, with no dependency on the host's LLM types: a fragile cross-bundle symlink workaround for a nominal-type clash was replaced by a durable design (98be27b, c2beb06). Five sibling flavors followed on the same template - quotes, ellipsis, spaces, invisible, fullwidth - each with its own ledger, activation line and count line (ad4c2c3, 68afdcd). Then the tool-args gate (1d827ad), and its three-way refinement (9d6e195): edit calls pass by identity - the old-string must match the real file bytes - and a per-call marker lets a single call opt out entirely. The marker is honestly stated: smoke-verified, but not reachable from the structured tool interface at the time; the practical per-call raw route is the edit exemption.
The stream, before and after
One beat belongs to the family's self-awareness: the system normalizes itself. The agents building the family saw their own comment prose come out with ASCII hyphens, because their output streams run through the same flavors. The planned cosmetic pass was cancelled by the user - em-dashes live only in the README examples, never in code. The workbench and the matrix pages run exactly these flavors live, and the workbench lets you watch the gate make its per-call decisions.
Phase V - The raw-write saga, commonalization and granular control
The raw-write tool landed first (bb9a6fd): a write wrapper with an explicit normalize parameter, exempt from stream normalization by name like edit, mirroring the built-in write's filesystem path so the governance hooks see its writes. Then the live tests showed its writes never triggered the governance chain - and the diagnosis took three rounds, two of them wrong. The emit-context theory: the event must be scoped to the emitting plugin. The listener-scope theory: the listener must be registered at the wrong level. The delivery research refuted both with code, simulation and live policy evidence: the event is delivered; the gate rejects the actor. The shared event bus has no scope filter on plain emits - the root registration fix aimed at the misdiagnosis was harmless, and stayed.
The real blocker: the patch layers
The deployed mutationTools value of all three governance bundles was [write, edit, str_replace_editor] - no raw-write - even though the schema default included it. The bundles' own patch layers set mutationTools explicitly, and a patch layer's config replaces the entry config wholesale, shadowing the default. The inspection tooling projected the schema, so the projected list looked correct while the live value was not - the verification blind spot in one sentence. The fix (affa730) was one line in three patch files, and it became the family's standing lesson about verifying the live effective config.
The journal migration that failed silently
The journal domain bumped to v2 to carry the stream counts (4c0e83b) - and every boot after it landed zero records. The v2 open failed against the v1 file on an exact-equality version check; the open's catch swallowed it; every record drained into a never-drained buffer and died with the process. The fix ( 756d566): a per-record layout that auto-migrates the v1 records, backup-and-skip for invalid ones, and a loud failure line on any open failure - because the silence is what made it undiagnosable.
Commonalization: three executors become one
The write-path audit found the built-in write, the raw-write tool and the factory's guarded write all implementing the same five steps, three times. The merge ( 91293d2): the factory's Write becomes the shared executor - the guarded write lives on as its alias - with the update envelope and the activation composer folded into the core the same pass. Then the fine-grained control (e69c5d3): normalize selects the transforms, govern selects the chain steps through the factory's Govern- the 17th method - and a step registry the modules populate. The governance marker (2c8764e, 83ba8e5) inverts the event path into a direct path: the raw-write's actor carries the govern selection, so direct governance is the only channel and no flag means no chain - the escape hatch.
| Shared step | Built-in write | raw-write (before) | Factory guarded write (before) |
|---|---|---|---|
| 1. resolve | session-aware resolve options (cwd, escalation root) | same call, no cwd option - a real divergence, documented | none - the target arrives pre-resolved |
| 2. fs/write-intent | the waterfall, next() -> void | identical waterfall, next() -> undefined | builds the intent inline: replaceIfVersion + version |
| 3. sandbox policy | fail-closed controller + standing session policy | verbatim replica of the no-escalation branch | the per-call workspace-write fence at the target's own directory |
| 4. writeText | the five-argument call + denial remediation | same five arguments, no remediation | same five arguments, plus the stash pre-registration |
| 5. fs/observed | present emit with the written version, actor = the exec | byte-identical emit shape, actor = the exec | delegated to the same-actor refresh |
The fold, and the guard that made it converge
The direct steps' detached chains raced - nondeterministic writes on the same file - so the fold awaits each step's chain before the next reads (d7a3c93, 140faa0). Then the deeper bug: every step wrote against the version observed at the triggering event, so only the first write could succeed and the later steps failed the stale-version check silently. The fix (5982aa8): the chain pass stats the target before its write and takes the fresh version, so the fold converges in any step order - which matters, because the pinner activated before the governor at the decisive boot. One more fix belongs to the Effect-TS port: the write executor originally mapped the caller's abort signal onto the run promise's options, so a pre-aborted signal interrupted the whole fiber and the caller saw all fibers interrupted without error instead of the Classic aborted-before-write behavior. The dash suite's pre-aborted-write scenario caught it; the fix drops the signal mapping - the signal forwards to the seams only, exactly the Classic semantics.
The final battery's outcomes, verbatim
Phase VI - The releases, the monorepo and the Site
The completed codebase became the boilerplate for two official releases off one source: the Classic implementation stays live-first, and the Effect-TS port ships only after its smokes prove parity - the ledger strings remain the contract in both (register #13). The release prep anonymized and de-personalized every bundle and rebuilt the READMEs on a single skeleton borrowed heavily from the sibling project's conventions. The Effect-TS mapping planned the parallel-govern toggle in from the start - concurrency is different there, and designing the toggle first beats retrofitting it. The naming inverted ( register #15): packages read as @-sentences - Hook @ DSH @ Governor @ Package - and group alphabetically by kind, role and domain, at version 0.0.1, one repository per package under submodule structure. And the Site you are reading is the last item: the integration showcase, with the versions page carrying the two engines side by side.
The decision index
Every decision below survives in a shipped seam. The technique column names where it lives; the evidence column links the narrative to the commit era.
| Decision | Rationale | Technique / seam | Evidence |
|---|---|---|---|
| Module-owned ledger strings; the smokes as the arbiter | Byte-identical strings make renames safe and behavior provable - the literal activated (cargo flavor, ...) survives every refactor | The transform contract + the smoke harness; renames change mechanics, never strings | the handoff P2/P5; 3c29108, e106a98, 66bfd81 |
| Stat-before-write version guard | In a multi-step fold every step wrote against the SAME observed version - only the first write could succeed; the fresh stat makes the fold converge in ANY step order | The guarded write's replaceIfVersion intent + a fresh version before each write | 5982aa8 |
| Wrapped-actor governance marker + the direct path | The raw-write's actor carries {...exec, govern}; the gate's govern reason makes direct governance the only channel - the event path skips marked actors, so no flag means no chain (the escape hatch) | The gate's actor step + the factory's Govern (the 17th method) + the step registry | 2c8764e, 83ba8e5, e69c5d3 |
| The sequential fold; the parallel toggle designed but gated | Detached chains raced (nondeterministic writes); same-file steps - canonicalize + pin on one package.json - DO race, so parallel only when disjointness is proven | The direct-govern fold in the write executor's continuation; the per-call toggle rides the same govern flag shape | d7a3c93, 140faa0 |
| Per-record journal layout + the loud failure line | The v2 open failed silently against the v1 file (an exact-equality version check) and buffered every record into a never-drained buffer; silence made it undiagnosable | layout: per-record, compatibleVersions: [1], invalidRecords: backup-and-skip - one ledger line on open failure | 4c0e83b, 756d566 |
| Patch layers carry the live config | A patch layer's config REPLACES the entry config wholesale, shadowing schema defaults; the schema projection alone is the verification blind spot | The three governance bundles' patch layers + the mutationTools gate input | affa730 |
| Explicit tools over background rewriters | Background rewriting breaks the edit old-string contract and interleaves with bounded passes; an explicit tool is visible, opt-in, and a no-op writes nothing | The tools seam (ctx.tools.register) + the stream gate's name-exemption | 48f9515 |
| One shared write executor owned by the factory | Three executors duplicated the same five-step pipeline; the factory is the only home both tool-style and continuation-style writes already depend on | The factory's Write (the 16th method; the guarded write lives on as its alias) | 91293d2 |
| The core as pure delegate; the stream dispatch dependency-free | A cross-bundle symlink workaround for a nominal-type clash was fragile; generic structural shapes survive every narrowing | Stream/Chunk + Stream/Block with no dsh-llm import anywhere | 98be27b, c2beb06 |
| Host-era pins; verify from the installed app, never stale node_modules | The @deepseek-ai family pins must match the running host; the registry's latest tag is stale and the profile's old links lie | The dependency-pin discipline | the handoff P12 |
| Dual release: Classic live-first, Effect-TS ships only after parity | Keep the working implementation onboard; the ledger strings stay the contract in BOTH implementations | The stream gate, the services + layers, the tracing spans | the monorepo plan; the versions page |
| Reversed hierarchical naming + version 0.0.1 + submodules | Names read as @-sentences and group alphabetically; one repository per package | Package naming; the monorepo layout | the branding brief |
| The abort signal forwards to the seams, never onto the fiber | The Effect-TS write executor originally mapped the caller's signal into the run promise's options; a pre-aborted signal interrupted the whole fiber - the caller saw All fibers interrupted without error instead of the Classic aborted-before-write semantics | The Effect-TS Write executor with the signal mapping dropped - the signal rides the seams only, exactly the Classic mechanics | the dash suite's pre-aborted-write scenario (the Effect-TS round) |
The working lessons
Every real bug - the patch layers, the v2 journal open, the version guard, the delivery puzzle - was found LIVE, not by the smokes. The smokes prove the mechanics; the live battery proves the wiring. Neither report is complete without the other.
The pinner activated before the governor at the decisive boot - never rely on the bundle-list order for step ordering. The fold plus the fresh-version writes make the order irrelevant.
The storage timestamps are epoch milliseconds; the local display adds +03. The missing-records scares were filter mistakes, not lost data - one hour of arithmetic saved an hour of dread.
One file per response keeps agents alive; agents that batch die more often. Agents crash mid-flight without a closing message - recover from the on-disk state. And the subagents' own streams get normalized: their comment prose came out hyphenated.
The meta-lesson of the patch-layer saga: a schema projection is not the truth. The patch layer replaces the entry config wholesale - read the composed layers, never just the defaults.
The state the era ended in: twelve packages, twelve green suites, a clean tree, the full live matrix passed - and a backlog of decisions already designed, waiting for the next era. The install command below is the whole family's front door.
$ pnpm add @playform/hook-dsh-core