Decisions consolidated into the Clara-as-a-Coworker tracker, Phase 4 (single list) — answers there count.
Fede's read on the codebase: "too complex to understand… bespoke solutions each session comes up with in isolation." This is the honest answer to that — where it's true, how bad, and what to do about it in three shippable waves.
Aug 26, 2026 · synthesized from 11 dimension inspectors, independently re-checked against 60 proposed fixes · nothing has changed yet — this is a plan, not a diff
In plain terms: this isn't sixty different problems. It's one habit, repeated about sixty times: whenever a session needed something that already existed — a way to page a person, a place to write down what happened on a call, a test harness, an on/off switch — it built its own copy instead of finding the one that was already there. The good news buried in that: the team already built the right version of almost everything, at least once. Most of this plan is deleting dead leftovers and pointing people at the good version that already exists — not designing anything new.
The technical version, for the record: today the system tells a person something through roughly 30–35 separate places in the code (27+ files that directly email a PM/owner/office, plus 3 digest cron jobs, plus 2 vendor-paging paths) — and only 9 of those are wired into the one "door" (the matter lane) built to dedupe and track them. Five of the seven facts checked that get written by more than one part of the system — call/transfer outcome, conversation escalation status, who's muted or being handled, person identity, tour linkage, work-order status — have 2 to 13 independent writers each, instead of the one-writer pattern the promise ledger already proves works. Guardrail/judge/classifier code is 169 files, and inside that, six or seven independently decide "did Clara make an unbacked promise" with zero shared vocabulary. Test harnesses and eval scripts sprawl across ~30 harness/eval directories, with 9 different "replay and grade" implementations and 35 eval scripts, 25 of which hand-copy the same credential-reading code instead of importing the shared one. Scheduled jobs run on 5 different clocks (Temporal, EventBridge, Vercel cron, GitHub Actions, a Mac mini) with 142 scheduled things total, and one of those clocks (GitHub Actions, 24 jobs) isn't tracked in the one manifest built to prevent exactly this drift. On/off switches exist in ~28 files using 3 incompatible conventions. The scripts/ folder alone has 657 files, more than 100 of them one-off scratch tools never cleaned up. And 434 published docs exist, ~95 of them clustered into 12 topics where the same problem gets re-diagnosed by a different session with no link back to the last attempt.
Eight places this shows up the most, and roughly what "fixed" looks like for each.
scripts/ folder, more than a hundred of them one-time throwaway tools nobody deletedIn plain terms: instead of listing sixty separate fixes, here's the shape they collapse into. Four buckets: one place that knows what actually happened (a call, a transfer, a PMS event), one place that tracks what's still owed to someone (a promise, an escalation, a stuck renewal), one door that decides what a person needs to be told and whether they already were, and a shared toolbox so the next one-off script doesn't have to reinvent things again.
| # | Bucket | Today | Target | Notes |
|---|---|---|---|---|
| 1 | Evidence — facts with one writer | 5 of 7 checked facts have 2–13 writers each (~24 writer call-paths total) | 7 facts, 7 writers (1 each) | Promise-ledger status and Tour.cancelledAt are already at 1 writer — they're the template, not a problem. |
| 2 | Obligations — one ledger | Promise ledger: 1 writer (good). Escalation "already being handled" only checked for the test property, not Camellia or any real property. ~30 notify paths never consult it. | Every open obligation checkable from one place before a second notice fires | The matter lane (open-matter.ts) is the existing candidate — it needs Camellia armed, not a new system. |
| 3 | Human Lane — one door | ~27 files directly email a person, +3 digests, +2 vendor-paging paths ≈ 30–35 distinct paths; only 9 of 27 map onto the published "13 doors" inventory | ~8–10 distinct message bodies, all routed through 1 door for the routing/dedupe decision | Some exclusions are deliberate (an owner-alert path was pulled OFF the matter lane on purpose after a real leak) — target is "route what's already recommended," not force everything through one pipe. |
| 4 | Toolkit — harness skeleton | ~30 harness/eval directories; 9 independent replay+grade implementations | ~5–6 systems, each remaining split documented | scripts/tour-replay looks cleanly supersedable by scripts/tour-decider-replay. |
| 5 | Toolkit — fixture + PII scrub | 8 independent PII scrubbers; 3 incident→fixture pipelines, 3 schemas | 2 scrubbers (log-masking vs. full fixture replacement are legitimately different jobs); schemas stay split only where the consumer differs | Don't force all 8 scrubbers onto one function — hot rule 7 requires full replacement in committed fixtures. |
| 6 | Toolkit — one detector family | 169 guard/detector/judge/gate files; 7 independent unbacked-promise checks | Most of the 169 stay; the 7 collapse to 1 shared function; 20 judge files share 1 runner | The repo already names the consolidation base: detectUnbackedPromise in the vendored Cerberus package. |
| 7 | Toolkit — one sender path | 2 email transports (legit) + 2 SMS entry points (1 legacy/25 callers, 1 new/12 callers); sender choice hand-set at ~50 call sites, already caused 2 real leaks | 2 transports stay; 1 SMS entry point; sender choice made by a type-checked gate, not a hand-set boolean | The two "policy gate" functions also need renaming — they do unrelated jobs. |
| 8 | Toolkit — one scheduler pattern | 5 runtimes, 142 scheduled things, 1 runtime untracked; 57 inline retry blocks (18 files skip the shared constants); 12 near-duplicate wrappers | 5 runtimes, all in 1 manifest; retries down to named constants; 12 wrappers behind 1 factory | ADR-0123 is already the enforced standard in practice — its status field still says "Proposed." |
| 9 | Toolkit — one gate factory | ~28 gate/flag files, 3 conventions, 21 registered arms + 2+ missing | ~2 gate factories, all arms in 1 registry | Don't ban "off by default on both ends" outright — 6+ live arms use that shape deliberately for staged rollout. |
| 10 | Toolkit — credentials + DB client | 25–39 scripts hand-roll credential reading; ~56 build a raw DynamoDB client; the client is forked into 2 hand-synced copies | 1 credential loader, 1 tuned DB client imported everywhere | The fork hasn't drifted yet — this is closing a gap before it bites, not a live bug. |
| 11 | Docs canonicalization | 434 pages; ~95 clustered into 12 topics, weak/zero cross-linking; 2 duplicates | Same ~95 pages mostly kept, but 1 canonical hub per cluster with full linking; 2 duplicates deleted | Only 3 pages today use the real machine-redirect "Moved" pattern — extend that, not the 4 hand-authored variants that don't point anywhere. |
In plain terms: everything below sorts into four buckets — stuff we can just delete (dead code, zero callers), stuff we should merge (the same thing built twice or more), stuff that's already good and other parts of the code should copy it, and docs that keep re-litigating the same question instead of pointing at one page. Click a tab.
Ranked by lines removed × confidence. Every row below has zero live callers found by a repo-wide search.
| What | Verdict | Risk | Size | Proof required |
|---|---|---|---|---|
5 docs/renewal-architecture/*.md files citing a route deleted 3+ months ago | Confirmed | Low | 1,395 lines (docs) | Grep for the route name repo-wide comes back empty |
HybridRepository — 2 copies, 0 callers | Confirmed | Low | 818 lines | Build stays green after removal; grep stays empty |
Dead elevenlabs/ planning subtree | Confirmed | Low | ~900 lines | Grep stays empty |
Duplicate approved-to-signed-2026-08-21.html | Confirmed | Low | 600 lines (doc) | No filename references found repo-wide |
capture-three-dot-v2/v3/v4.mjs (superseded debug iterations) | Partly | Low | ~232 lines | Update the 1 doc that still names them |
4 of 5 "unused barrel" index.ts re-export files | Partly | Low | ~59 lines | Grep stays empty |
StructuredEmailIngester interface stub | Partly | Low | 49 lines | Grep stays empty for the stub specifically |
src/lib/integrations/sqs/simulate-results.ts | Confirmed | Low | 42 lines | Grep stays empty |
RenewalSnapshot.channelsAttempted — flagged as write-only/dead, actually read by 3 canary/E2E test scripts (35 references) via a live Temporal query. Not dead. The real opportunity is deciding whether to also surface it in the UI, or drop only the redundant DynamoDB write.Ranked by realistic lines removed × confidence.
| What | Verdict | Risk | Size | Proof required |
|---|---|---|---|---|
| Route ~25–39 eval scripts through the 1 shared credential loader | Partly | Low | ~700–800 lines | Each migrated script's output matches its pre-migration run on the same fixture |
| Route ~56 scripts through the 1 tuned DynamoDB client | Partly | Medium | Unverified, plausibly several hundred lines | Smoke-run 3–5 migrated scripts (pool tuning is invisible to correctness) |
| Merge 4 "PMS event happened, tell the office" senders behind 1 composer | Confirmed (duplication) | Medium | ~220 lines | Byte-for-byte email-body diff, before vs. after, for every event type |
| Extract 1 shared activity registry between the 2 Temporal renewal-worker entrypoints | Confirmed | Medium | ~150 lines | 4 existing drift/parity tests stay green |
| Collapse 12 near-identical "thin scheduled wrapper" workflows behind 1 factory | Partly | Medium | ~150–200 lines | The naming drift test requires each file keep its own exported function name — factory is called FROM that function |
| Merge 4 copy-pasted "resolve caller-id, 60s cache" blocks | Confirmed — best ROI here | Low | 60 lines | Existing test coverage on the 4 call sites stays green |
Collapse 2 of 3 copy-pasted killSwitchEngaged() gate files | Partly | Low | ~40–60 lines | Existing gate tests stay green |
| Extract shared webhook plumbing between the 2 voice tool-call routes | Partly | Low (narrow slice only) | ~100–150 lines | Both routes' existing tests stay green |
Mostly 0 lines removed — these close real gaps or set the pattern others should copy.
| What | Verdict | Risk | Size | Proof required |
|---|---|---|---|---|
| Name the promise ledger as the reference pattern for every other multi-writer fact | Confirmed | Low | 0 lines (docs only) | None — documentation only |
Name handoff-claim-detector.ts as the reference pattern for detector consolidation | Confirmed | Low | 0 lines (docs only) | None — documentation only |
| Register the 2 missing kill-switch arms + extend the coverage test | Confirmed | Low | Closes a gap | New coverage-test assertion passes |
| Republish the arms audit doc (1 month stale) | Confirmed | Low | Closes a gap | Manual spot-check against current code |
| Extend the schedule-drift reconciler to track GitHub Actions as a 5th runtime | Confirmed | Low | Closes a gap | Reconciler run stays green with the new runtime added |
Wrap the 1 remaining unmonitored cron route (participant-name-reconcile) | Partly | Low | Closes a gap | Route shows up in the Jobs tab after the fix |
| Flip ADR-0123's status from "Proposed" to "Accepted" | Partly | Low | 0 lines (status field) | None — status-field edit |
Fix ElevenLabs config to include the missing maintenance_handyman agent | Partly | Low | Closes a gap | Run the existing dump command to pull its live config in |
| Consolidate 3 overlapping "EL tool dedup recovery" handoff docs into 1 | Partly | Medium | Closes a gap | Needs Gera's decision on dependent branches first |
Clusters of docs re-litigating the same question → 1 canonical hub each, with redirects.
| Cluster | Verdict | Risk | Size (today → target) | Proof required |
|---|---|---|---|---|
| Escalation / HITL architecture | Partly | Medium | 12 pages → 1 canonical + kept incident history | The 3 open decisions migrate intact into the canonical tracker |
| Eval / guard / Cerberus | Confirmed | Low | 14 pages → 1 hub, fully linked | Add the 3 missing links; confirmed via grep both directions |
| Voice / escalation / transfer | Partly | Low | 23 pages, same count, gaps linked | Grep confirms current gaps |
| Turnover | Confirmed | Low | 7 pages → 1 index linking all 7 | Grep confirms all current gaps |
| Email architecture | Confirmed | Low | 1 living doc, 0 → 9 outbound links added | Direct read confirms zero <a href> tags exist today |
| CI / pipeline | Confirmed | Low | Full cross-linking to 4 predecessor docs | Confirmed via direct read |
| Promise ledger | Partly | Low | 4 pages, open items pulled into 1 tracker | Confirmed a §5e section already exists and links out |
| Knowledge-base redesign | Unverified | Low | 2 docs → 1, older marked superseded | Not independently re-verified this pass |
| Stale email-template planning docs | Partly | Low | 2 docs archived or deleted | Zero references anywhere in the repo; the work is already shipped |
| The "Moved" redirect-stub pattern itself | Partly | Low | Extend from 3 pages to 6+ targets | None — template already proven |
This is the pattern you flagged most. A session hits a wall, doesn't check whether the platform already solved it, and builds its own version. Below: the five cases with real dates on them, in order, then four more confirmed cases where the underlying evidence didn't come with a calendar date attached — reported as such rather than guessed.
Four different "tell the office a PMS event happened" emails got built back-to-back in the same week — each one a fresh copy of the same send-and-check code instead of one shared version.
4 renewal/turnover PM-notify senders, each duplicating the same send/delivery-check wrapper.
The same "a vendor call ended, tell someone" feature got built three separate times, three weeks running — and the team's own tracking doc already calls this out by name and it's still not fixed.
The vendor-call-outcome trio, tracked in the published inventory as "door 5 (×3)."
Three escalation-design docs got written back to back, each one starting over instead of picking up where the last one left off — and the newest one never even links back to either of the first two.
escalation-architecture-decision-2026-08-04.html → escalation-journey-rethink.html (marked as successor) → escalation-architecture-2026-08-21.html (never linked back to either).
Two of the three on/off switches for kill-switch–style features were hand-built two to three months after the shared factory for exactly this shape already existed (that shared factory shipped May 26) — each one copying the same logic instead of importing it.
tour-decider-flag.ts (Aug 16) and voice-callback-flag.ts (Aug 19) each hand-copied the killSwitchEngaged() shape instead of importing createDomainGate (shipped 2026-05-26).
A new test script was built 8 days after the shared "read credentials safely" helper already existed — and still didn't use it. It's not a one-off mistake either: 25 of the 35 similar scripts repeat this exact pattern.
scripts/eval-application-link-subscription.ts (2026-08-19) vs. scripts/lib/subscription-eval-runner.ts (2026-08-11).
Four more confirmed cases, same pattern — no specific date came with the evidence for these, so none is invented here:
capture-three-dot-v2/v3/v4.mjs) after the final version already worked — a small instance of the same "never clean up the old one" habit, visible at every larger scale above.You don't have to approve all of this to get moving. Each wave stands on its own and has its own proof before the next one starts.
Every confirmed-dead delete from the table above (~4,000+ lines of code and docs, zero callers found anywhere); every pure doc-linking fix; the quick reuse wins (register the 2 missing switches, republish the stale audit doc, add GitHub Actions to the schedule tracker, wrap the 1 unmonitored cron job, flip ADR-0123's status, fix the ElevenLabs config gap); merge the 4 phone-lookup copies; delete the duplicate lease doc and the 2 stale email-template docs.
Merge the 4 renewal/turnover notify senders behind 1 composer (keeping the 2 real differences as parameters); share one activity registry between the 2 Temporal renewal workers; build the shared factory for the 12 near-identical scheduled workflows; route the eval scripts and raw-database-client scripts onto their existing shared helpers; extract only the shared plumbing between the 2 voice tool-call routes (not the safety-check logic); fix the 2 places that bypass the safe way of writing conversation-escalation status; group the 23 Camellia-named scripts into one folder.
The full redesign of what the conversation-escalation field means (gated on Decision 2 below); merging the 6–7 unbacked-promise detectors into 1 shared function; a shared judge-runner for the 20 judge-named files; the full "one door" human-notification consolidation onto the matter lane (gated on the Camellia arming decision, Decision 1); the voice tool-webhook merge scope (Decision 4); folding the person-identity work into the same "one writer per fact" bucket, since it's already tracked as its own project — this plan just says don't solve it twice.
Four calls, all about how far to push the harder work in phase 3. Click an answer — it saves and shows who answered. Reasoning is under "why" if you want it; the recommended option is marked.
1. How far do we push "one door" for paging a person?
2. The conversation-escalation field secretly means three different things (mute Clara, tell the PM, Clara hit her own safety limit). Fix now or later?
3. New on/off switches keep getting hand-built instead of through the shared factory. Require a checklist item, or leave it to review?
4. Merge the two systems that handle a voice-call request like "what's my balance"?
Anything else / caveats on an answer