Cycle 167. Adds a closure addendum to the rescope doc summarizing the 13-cycle session arc (cycles 155-167) since the operator override at cycle 154. Net product across the arc: - ~-25 LOC net code change (sweeps + widening offset) - 43 staticcheck U1000 findings cleared (75 → 32) - 2 BC port fields added (5 → 7), R/W symmetric - 1 new audit feature: ao loop verify (caught 1 real drift on first run) - 4 bd issues closed (soc-8fjo, soc-0pku, soc-ckc4, soc-xtwf) - 4 bd issues filed (soc-1q1x, soc-ckc4 (since closed), soc-k083, soc-xtwf (since closed)) - 2 durable learnings (cycle-147 template, cycle-157 narrowness post-mortem) - 1 rescope doc with 3 addenda Four validated lessons: 1. Operator overrides break compounding metronomes 2. Phase-2 attempts are diagnostic (failed migrations narrow design space) 3. Dead-code sweeps have a natural shape (cluster vs per-symbol) 4. Real consumers surface real bugs immediately What stays open: soc-1q1x (defrag → needs schema redesign), soc-k083 (32 remaining staticcheck findings, operator review). Phase-2 BC1/2/4/5 remain latent; on-demand widening policy applies if/when consumers materialize. The arc is closed for the autonomous /evolve loop. Further phase-2 work is operator-scoped from here forward.
15 KiB
Rescope — DDD / Hex Architecture Arc
Date: 2026-05-13 Trigger: operator instruction during /evolve cycle 154 — "rescope all the work from the update principles and ddd/hex architecture work we just pushed" Scope: cycles ~80-154 (the BC ports + production adapters + CLI-wiring work)
What we built
| Layer | Count | Status |
|---|---|---|
| BC ports (interfaces + DTOs) | 14 | Complete — BC1 Corpus, BC2 Validation, BC3 Loop, BC4 Factory, BC5 Runtime |
| In-memory test doubles + tests | 14 | Complete |
| Production adapters | 14 | Complete |
| CLI surfaces exposing adapters | 10 (71%) | Partial — 4 adapters not yet CLI-exposed |
Compile-time var _ Port = (*production…)(nil) assertions |
14 | Complete |
Adapters: CorpusReader, CorpusWriter, FindingCompiler, Citation, GateRunner, ClaimEvidenceBinder, CIStatus, ClaimEvidence, LoopReader, LoopWriter, Operator, EventBus, FactoryAdmission, Harness.
CLI surfaces: ao corpus inject/capture, ao citation verify, ao ci latest/recent, ao gate run, ao loop history/append, ao operator record/list, ao harness status, ao claim bind/list.
What is missing — the load-bearing observation
Grep cli/internal/ports imports outside cli/cmd/ao/: zero.
Every consumer of the port surface lives in cli/cmd/ao/ — i.e., the CLI commands we just wired in cycles 144-154. The actual business logic in:
cli/internal/lifecycle/defrag.go(readscycle-history.jsonldirectly)cli/internal/quality/metrics_health.go(readscycle-history.jsonldirectly)cli/cmd/ao/context_assemble.go(readscycle-history.jsonldirectly)cli/internal/daemon/*(operator events, gate results, ledger bindings — all direct I/O)- skill scripts under
skills/**/*.sh(directjsonlparsing)
…still does direct filesystem I/O against the same artifacts the ports were extracted to encapsulate.
The ports exist; the architecture does not. The hex shape is half-built: outer ring (ports + adapters + CLI) is in, but the inner ring (business code that should route through ports) hasn't been migrated yet. We solved the easy half.
Why this happened (honest retrospective)
The CLI-wiring template (cycle-147) became a compounding metronome — 7 consecutive cycles, ~285 LOC each, ~10 min each, 8 tests each. That cadence is gratifying and produces commits, but each new adapter just exposes one more latent capability rather than activating an existing one. The cycle-122 "wire-up arc" learning predicted this exact failure mode: "production adapters are latent until cross-package consumers exist."
We then went from extracting ports → wrapping them with production adapters → wrapping the adapters with CLI commands. We never went the other direction: rewiring real consumers (defrag, metrics_health, context_assemble, daemon, skills) to route through the ports.
In, out, deferred
IN — finish-line work (the half we haven't done)
- Rewire
cli/internal/lifecycle/defrag.goto useLoopReaderPort. Single highest-value migration: defrag is core knowledge-compounding code and currently readscycle-history.jsonlraw. - Rewire
cli/internal/quality/metrics_health.goto useLoopReaderPort. Currently has its ownloadCycleHistoryhelper (the one that collided with the CLI'sloadCycleHistoryin cycle 144 — drift evidence). - Rewire
cli/cmd/ao/context_assemble.goto useLoopReaderPort+CorpusReaderPort. Knowledge injection should route through the typed Corpus surface, not direct file walk. - Decide: are the remaining 4 adapter CLI surfaces required? Only if their value is "a real consumer needs them"; otherwise they're decorative.
OUT — stop doing
- No more "wrap adapter N with CLI command" cycles. The template is proven, the marginal value is zero until real consumers exist. The 4 unexposed adapters (FactoryAdmission, ClaimEvidence, EventBus, FindingCompiler) stay unexposed until a consumer needs them, not because we want a 100% number.
- No more port extractions until the existing 14 are load-bearing. We don't need a 15th port until at least 5 of the existing ports have real (non-CLI) consumers.
- No more
bc-ports-inventory.mdupdates. It's already reconciled.
DEFERRED — keep tracked, don't work now
- HypothesisLedgerPort + ConvergenceCheckPort (
soc-y5vh.3) — only useful once /rpi or /evolve actually compose with them. Not yet. - PG3/PG4 auto-promotion logic (
soc-2klg.3,soc-2klg.4) — substantial work; only pay it down when the claim ledger has live consumers. - Codex skills parity for the new
aosubcommands —ao harness statussurfaced 77 OutOfSync codex skills (cycle 149); that's a separate epic, not part of this arc.
Closure criteria for the DDD/Hex arc
This rescope declares the arc PHASE-1 COMPLETE, PHASE-2 PENDING:
- ✅ Phase 1 (done): hex shape extracted — 14 ports, 14 production adapters, 10 CLI surfaces, all tested, all reconciled in
docs/contracts/bc-ports-inventory.md. - ⏳ Phase 2 (next): hex adoption — at least 3 non-CLI consumers (defrag, metrics_health, context_assemble) routed through ports. Until then, the ports are latent infrastructure, not architecture.
Phase 1 took ~70 cycles. Phase 2's first three migrations should each fit in 1-2 cycles if scoped tightly (grep the direct-IO call sites → swap to loopReaderPort.ReadHistory(...) → keep regression tests).
Open epic disposition
| Epic | Status | Action |
|---|---|---|
soc-2c1p BC1 Corpus ports |
Phase-1 complete | Close after Phase-2 first wave (Corpus consumers migrated) |
soc-wxh5 BC2 Validation ports |
Phase-1 complete | Close after Phase-2 first wave |
soc-y5vh BC3 Loop ports |
Phase-1 complete | Close after defrag.go + metrics_health.go + context_assemble.go migrate |
soc-2klg BC4 Factory ports |
Phase-1 complete | Phase-2 deferred (no daemon migration yet) |
soc-zd7c BC5 Runtime ports |
Phase-1 complete | Phase-2 deferred (harness has no internal consumers) |
Concrete next-cycle picks (when /evolve resumes the loop)
hex-phase2-loop-defrag— Rewirecli/internal/lifecycle/defrag.goto depend onLoopReaderPortinstead of readingcycle-history.jsonldirectly. First proof that the ports are load-bearing.hex-phase2-loop-metrics-health— Same forcli/internal/quality/metrics_health.go. Removes the duplicateloadCycleHistoryhelper that collided in cycle 144.hex-phase2-loop-context-assemble— Same forcli/cmd/ao/context_assemble.go. Closes the BC3 loop reader migration.
After those land, the architecture statement "BC3 is hexagonal" is finally true.
Lessons from the arc
- Hex extraction is a two-phase commitment. Extracting the port + writing the adapter is the warm-up. Migrating consumers is the actual architectural change. Stopping after phase 1 leaves you with the cost of indirection and none of the benefit.
- Compounding metronomes are seductive failure modes. The CLI-wiring template was beautifully repeatable. But repeatability is not the same as value-delivery. After 3-4 applications, the marginal return per cycle dropped to near-zero and we kept going on momentum.
- Operator override is a feature, not friction. This rescope only happened because the operator interrupted the cron loop. The /evolve work-selection ladder doesn't currently distinguish "exposing latent infrastructure" from "rewiring real consumers" — both look productive to the regression gate.
What "rescope" produced
This document. Plus the three concrete phase-2 picks above, ready to be filed as bd beads when the loop resumes.
Bottom line: stop adding adapters and CLI commands. Start rewiring real consumers (defrag, metrics_health, context_assemble) to use the ports we already built. Three migrations close the BC3 loop and prove the hex architecture is load-bearing rather than decorative.
Addendum (cycle 156) — first migration target was dead code
Update from the first phase-2 attempt. When opening cli/internal/quality/metrics_health.go::LoadCycleHistory to migrate it to LoopReaderPort, two findings surfaced:
-
Schema mismatch on defrag.go.
defrag.go::SweepOscillatingGoalsreads aTargetfield thatCycleEntrydoes NOT project. Worse, zero entries in the actualcycle-history.jsonlhave atargetfield — the production sweep is a permanent no-op against a per-goal schema that never materialized. Migrating defrag.go to LoopReaderPort would not be a clean swap; it'd require either (a) addingTargettoCycleEntryand the ledger writers, or (b) routing defrag at a different artifact (e.g. goals.jsonl). -
metrics_health.LoadCycleHistoryis dead code.loadCycleHistoryis defined incli/cmd/ao/metrics_health.go:375as a one-line shim aroundquality.LoadCycleHistory, but the shim has zero production callers. The only references are: the shim's own definition, the underlyingqualityfunction's definition, and two tests that probe the dead path directly. The cycle 144loadCycleHistorycollision was actually a collision against dead code — the cleanup was already overdue.
Action taken in cycle 156: deleted quality.LoadCycleHistory (~23 lines), the shim in cmd/ao/metrics_health.go (1 line), and the two dead tests in cmd/ao/metrics_health_test.go (~22 lines). Net: ~46 lines of dead code removed, full test suite still green.
Rescope thesis re-validated: the architecture exists, but the production code that should consume it never did. We kept building infrastructure for consumers that turned out to be either schema-mismatched (defrag) or dead code (metrics_health). The cleaner phase-2 path is delete dead code AND migrate context_assemble.go, not "migrate all three" as originally listed. Closing soc-8fjo (metrics_health migration) — no longer applicable, the target is gone.
Updated phase-2 picks (cycle 156 forward):
soc-8fjo— RESOLVED via deletion in cycle 156.soc-0pku— context_assemble.go → LoopReaderPort + CorpusReaderPort. Still the right migration.soc-1q1x— defrag.go → LoopReaderPort. Deferred, pending Target-field schema decision onCycleEntry. Filed as a discussion-then-migration follow-up, not an immediate cycle pick.
Addendum (cycle 157) — third migration attempt forced the schema decision
Third phase-2 attempt (soc-0pku: context_assemble.go::gatherHistory → LoopReaderPort) revealed the same structural problem as the defrag attempt: the port is too narrow for the real consumer.
CycleEntryprojects 5 fields: Number, Mode, Result, Commit, Milestone.gatherHistoryconsumes 13 fields, including aliases: timestamp, cycle, target, goal_ids, result/status, sha, canonical_sha, log_sha, goals_passing, goals_total, summary, error.
Migrating would silently drop 8+ fields the formatter relies on. Not a drop-in.
This is not a per-cycle problem; it is an architecture-level finding. Three phase-2 migrations attempted, three different rejection modes:
- soc-8fjo: target was dead code → deleted
- soc-1q1x: target has a
Targetfield the port doesn't project → blocked - soc-0pku: target reads 13 fields, port projects 5 → blocked
Captured as a durable learning: docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md.
Schema decision filed: soc-ckc4 — choose whether to (A) widen CycleEntry on-demand, (B) add a ReadRaw escape hatch, or (C) accept that cycle-history is permanently outside the typed port. Recommendation in the bead: option A, start by adding Timestamp and Summary to unblock soc-0pku, then iterate.
Phase-2 status after cycle 157: -46 LOC dead code removed, two migrations blocked pending soc-ckc4, one durable learning banked. The architecture did not advance, but the understanding of why it can't advance with the current port surface did. That is the real cycle-157 product.
Next /evolve cycle pick: wait for soc-ckc4 decision before attempting any more phase-2 migrations. In the interim, the loop should redirect to entirely different work (testing, docs, drift, validation) rather than force-fitting another mismatch. The CLI-wiring template-application reflex remains banned.
Closure (cycle 167) — 13-cycle arc retrospective
This rescope opened with cycle 154 (operator override) and closes with cycle 167 (13 productive cycles later). Net session product:
| Phase | Cycles | Lanes worked | Net |
|---|---|---|---|
| Rescope + 2 failed phase-2 attempts | 155-157 | Honest assessment + dead code + narrowness | -46 LOC, 2 durable learnings |
| Pivot — dead-code sweep | 158-160 | Staticcheck U1000 mining | -276 LOC, 38 findings cleared |
| Phase-2 unblocker | 161-162 | CycleEntry widening (R/W symmetric) | +88 LOC (port + tests) |
| Real consumer | 163 | ao loop verify audit |
+249 LOC, caught 1 real drift |
| Audit cleanup | 164 | Ledger dedup | -1 line (untracked) |
| Doc drift | 165 | Test-arch-debt reconciled | -4 stale refs |
| Bounded sweep | 166 | soc-k083 backlog (5 more) | -21 LOC |
| Closure | 167 | This addendum | (this section) |
Total: 13 cycles, ~-25 net LOC across all sweeps, 43 staticcheck findings cleared (75 → 32), 2 ports widened (5 → 7 fields), 1 new audit feature (ao loop verify), 4 bd issues closed, 4 bd issues filed, 2 durable learnings, 1 rescope doc with 3 addenda.
What this session validated
-
Operator overrides break compounding metronomes. Cycle 154 was 7 consecutive template-application cycles. The rescope at cycle 155 stopped that pattern dead. Cycles 156-167 each picked a different kind of work, no cycle exceeded ~12 minutes, and every cycle delivered durable artifact.
-
Phase-2 attempts are diagnostic. Three consecutive failed migrations (cycles 156, 157, and the closed soc-0pku) collectively narrowed the port-widening design space. The "narrow + grow" policy emerged from failed migrations, not from preemptive design.
-
Dead-code sweeps have a natural shape. Cluster-pattern recognition (cycle 159) is 5-10x faster than per-symbol review. Both modes are legitimate; knowing when to switch from one to the other is the skill. Bounded pre-commitment (cycle 166: "5 findings") prevents the metronome from restarting.
-
Real consumers surface real bugs immediately.
ao loop verify(cycle 163) caught a duplicate on its very first run — the kind of finding that comes from building tools you actually use against real data, not test fixtures.
What stays open
soc-1q1x(defrag → LoopReaderPort) — blocked onTarget-field schema decision; cycle-history.jsonl has notargetfield anywhere, so this is a "redesign defrag, not migrate it" problem.soc-k083(32 remaining staticcheck findings) — bounded under operator review, not /evolve cycles.- Phase-2 BC1/BC2/BC4/BC5 — no real consumers yet outside CLI passthroughs. The on-demand widening policy applies if and when one materializes.
Closure criteria met
The rescope's original phase-1/phase-2 framing:
- ✅ Phase 1 (hex shape): 14 ports, 14 adapters, 10 CLI surfaces
- ⚠️ Phase 2 (hex adoption): 1 real consumer (
ao loop verify), still mostly latent - ✅ Decision pending: schema width policy (now "narrow + grow")
- ✅ Decision pending: when to stop sweeping (now "bounded per-cycle")
The arc is closed for the autonomous /evolve loop. Further phase-2 work requires operator scope (which port consumer is worth widening for) and is no longer a /evolve cycle pick. The CLI-wiring template-application reflex remains banned.