Skip to content

Extraction Report

how the design was derived: root cause, pattern ledger, rejected patterns, risks, plan

Rendered from docs/EXTRACTION_REPORT.md — the same Markdown GitHub shows.

This is the record of how DESIGN.md was derived. It exists so that every design decision can be traced to an observed failure or an observed success, and so that a reader can see what was considered and rejected, not only what was kept.

The source material was a large private engineering environment: instruction files across roughly twenty repositories, one very large monorepo with a mature policy and hook corpus, several years of session notes and retrospectives, and four earlier attempts at a supervisory tool. Nothing from that material is reproduced here. Paths, names, quotations, and internal vocabulary have been removed. Magnitudes are kept because they are the evidence.

Method: study → abstract → challenge → synthesise. Not copy → rename → publish.


1. Product Definition

Prismatic Majordomus is a lightweight supervisory control layer for AI-assisted work. It holds one canonical policy, projects it into whatever instruction format each AI worker reads, keeps durable task state outside every conversation, coordinates scope between concurrent workers, and refuses to call work finished until a finish contract is met.

It is not a model, not an agent framework, and not a runtime. It supervises workers; it does not run them.

One sentence: AI workers should not manage themselves.

2. The single root cause

Every extraction thread converged on one finding, and it is stated first because the rest of the design is downstream of it.

Enforcement is declared in prose and never reconciled against what actually runs.

Independent instances found in the source material, each verified by reading the wiring rather than the documentation:

#DeclaredActual
139 pre-commit scripts, README describing a fallback dispatch loopdispatcher hard-codes 5; 34 orphaned; no loop exists
2two worktree guard hooks, policy frontmatter reads BLOCKINGnever dispatched; their self-test is echo OK; exit 0
3a filename-convention hook "enforced since" a given datenot wired for months; then wired with a flag bug that matched zero files on every platform
4catalogue of 546 AI agents cited as headline capability1 of 183 sampled entries actually registered with the runtime
57 runtime hooks under a tool's config directoryno settings file in the repository had a hooks key
6a well-engineered commit guard with self-test and rationaleregistration lost when unrelated tooling rewrote the settings file; hook, tests, and docs all still present
7a tool-call budget governor (read clamp, subagent cap, output condensation)installed, never registered, state directory empty
8a session guard with digest-based drift detectionran for one day; 18 status files; nothing since
9two audit ledgers mandated by the enforcement contractnever created
10a protected-branch config file read by the override validatordoes not exist
11a compression module with byte-exact validation table, "Production Ready"module does not exist
12a prior extraction attempt claiming 106 artefactsshipped 2
13a claims registry reporting "100 % verified, zero violations"the baseline file one directory away records 27,263
14a doctrine pillar with module, policy, and hookregistered in no pillar list; never executed
15a process-timeout option on a runtime callthe function has no such option; the rescue branch is dead code
16five scripts headlined as the recommended onboarding workflownone exist
17a --changed scoping flag on a CI jobcomputes the list, prints it, runs unscoped anyway

Seventeen is not exhaustive. It is the count at which the pattern stopped being interesting to extend.

The consequence for the design: doctor's primary deterministic guarantee is that every enforcement the policy declares resolves to an artifact that exists, is executable, and is reachable from the named dispatcher. That check is roughly sixty lines. The source environment described it in five places and implemented it in none.

3. Pattern Ledger

Columns: Proven means observed to work in production, not merely designed. Generalizable means usable by an ordinary team with git and a shell, no other runtime. Complexity is an estimate for a portable-shell implementation.

PatternProblem it addressesEvidenceProvenGeneralizableComplexityInclude
Enforcement wiring reconciliationdeclared-but-not-wired checks17 instances aboveno — never builtyes~60 linesyes, core
Git-derived identity on state recordsworkers hallucinate branch/headhandover writer forbids authored identity; resolver validatesyesyeslowyes
Read-time divergence label (exact/advanced/diverged/different_context)stale state trusted silentlyhandover resolver via merge-baseyesyeslowyes
Scoped recovery, never repo-wide fallbackunrelated note leaks into contextresolver tiers; absence exits 0yesyeslowyes
Append-only, atomic, never-staged handoveroverwritten or auto-committed statehard-link publish with retryyesyeslowyes
Required handover sections enforced at writeempty continuation recordsawk section check refuses writeyesyeslowyes
Always-loaded budget with failing check + pointer integritycontract grew 0→1,100 lines over 200 edits200-line cap held at 49; the uncapped sibling regrew 3.6×yesyeslowyes
"Unscoped rule is always-loaded context"hidden context growthlinter line flagged ityesyestrivialyes
Counts computed, never writtenfive contradictory app counts, three agent counts, five pillar counts across authority filesa validator exists and still found 15 discrepanciespartlyyeslowyes
Separate axes: model / effort / verbosity / context / verification"Sonnet with Reasoning" treated as a modelcurrent rubric separates model and effort; verbosity absent everywhereyes (2 axes)yestrivialyes, extended
Effort as delta from defaultprofile sprawl"set only where it differs"yesyestrivialyes
Named execution profile bundling the axesno such bundle existeda budget governor had an implicit unnamed profile with drift alarmsnoyeslowyes
Typed outcomes (closed vocabulary)no_match vs failed indistinguishable in prose157 of 298 notes carried a free-text status; four inconsistent enumsnoyestrivialyes
Count-ratchet (one integer baseline; increase fails; missing baseline warns)legacy dirt blocks everyone or is allowlisted awayfour live ratchets; absolute gates hit 1,400 findings and were allowlisted to 8yesyes~40 lines/metricyes
Hard-zero for catastrophic classes, never ratcheta ratchet permits the worst instancedecision matrix in sourceyesyestrivialyes
Exit-code contract; no "warn and continue"fail-open hookscontract states it; scanner catches || trueyesyestrivialyes
Trailer proves authorship, never that a gate rancommit-message markers accepted as evidencepostmortem in sourceyesyespolicyyes
Authorization inputs derived or corroborated, never ambientoverride validator defeated by env varspostmortem in sourceyesyeslowyes
Self-disabling bootstrap hatchshipping a gate into a repo that predates itone record, worked as designedyesyes~10 linesyes
Claim normalisation + containment both directionstrailing-slash and subtree overlaps undetectedlive registry shows both classesno — was brokenyeslowyes
Overlap on touched files, not declarations3,200 collisions invisible to declaration checksforensic collision matrixnoyeslowyes
Claim folded into start, not optional10 of 18 worktrees had empty claimscompliance auditnoyestrivialyes
Registry rebuildable from git82 % stale registry entries at one auditrepair command existedpartlyyeslowyes
Retention cap on append-only stores10 GB of unread metrics snapshotsnone existednoyestrivialyes
Tool self-applies its own checks500-line anti-sprawl linter decayed in 19 daysregression measurednoyespolicyyes
Published "intentionally absent" listreaders assume omissions are oversightsone protocol README did thisyesyesproseyes
Reproduce command on every findingaudits contained false claims caught only by a second readermultiplenoyespolicyyes
Semantic output condensation (head + diagnostic lines + tail, self-stamped)oversized tool outputgovernor implementationyes, brieflyprovider-specificlowdeferred v0.2
Read-size clamp at tool boundaryworker inhales 5,000-line filegovernor implementationyes, brieflyprovider-specifictrivialdeferred v0.2
Subagent count budgetfan-out costgovernor set it to 1 globally; rolled back within daysyes, then rejectedyes if task-derivedlowdeferred v0.2
Exclusion telemetry on context packingunder-filled context is undebuggablepacker manifestyesyeslowdeferred
Compliance markers as scoped greppable exemptionsinvisible suppressionsconvention in source; one marker documented as not workingpartlyyeslowdeferred
Task dependency edgesone demo file, subtasks pending a year laternounclearmediumrejected v0.1
Description-based routing with trigger phraseschoosing proceduresworks in sourceyestool-specificprojection concern only

4. Patterns Rejected

Rejected outright, with the failure that justifies rejection:

RejectedWhy
Named agents, personas, roles, tiers, commanders, registries546 entries; 305 were naming variants with no functional difference; 139 were stubs whose capabilities equalled their responsibilities; 109 were pitch-deck slides. A supervisory tool that adds nouns becomes the thing it supervises.
An advisory tier that calls itself enforcementEvery advisory tier decayed into decoration. Two-tier "advisory/blocking" was documented and did not exist.
Closed enum for worker identityAn 8-value enum used only to build filenames, unvalidated on read, already violated in its own live registry.
Hand-maintained dispatcher with meaningful-looking numeric prefixes5 of 39 invoked; ordering was decorative; chmod -x silently removed a gate.
Fail-open guard pattern if [ -x hook ]; then hook; fiMissing executable bit deletes the gate with no error. The correct shape blocks with a restore instruction.
Owning the mergeThe source implementation force-moved the target branch after rebasing, discarding unpushed commits, with no ancestry assertion.
Bundled quality-gate runner inside the coordination toolDuplicated CI logic; the source audit recommended collapsing it. Shell out to a configured verify command.
Global constant budgets (subagents = 1, always haiku)Rolled back within days. Budgets must be task-derived or the human disables the governor and nothing is measured.
Silent mutation of a worker's tool inputInvisible to the worker; drift should be surfaced before it is forced.
Bytes ÷ 4 labelled as a token countIndistinguishable from a measurement in the manifest. Estimates are labelled and excluded from enforcement.
Documented metrics with no producerA compression subsystem with byte-exact retention percentages for code that was never written. The tool refuses to display a metric with no producer.
Multiple overlapping state storesFour coexisting namespaces required a disambiguation table. Ship one store.
Hand-rolled restricted-JSON serialiser in shellCorrect only because the writer rejected escaping; cost a sort-key bug, a regex portability trap, and a durability hole. Use a real serialiser.
Vestigial schema fieldsFour always-null routing fields ported from an earlier system. Every field is written and read or it is removed.
Minimum fan-out / minimum agent count44 procedures were marked failed unless they spawned 3–6 agents, taxing even trivial ones.
Rename accepted as fixThe migration caught itself mechanically renaming a doctrine and reverted. If a check can be satisfied by search-and-replace it is not a check.
Mythological vocabulary2,850 files still carried banned phrasing a year after the removal order. Ordinary engineering language only.
Unbounded session-note directories10 GB, 1,496 files, a manual fifteen-to-thirty-minute recovery runbook.
Any daemon, server, database, queue, MCP surface, background monitor, vector storeNo evidence any of these solved a problem the file-based mechanisms did not. The stdio MCP reader added later in apps/majordomus-cli/ is none of these: no process outlives its client, no state outlives a process; see .ai/repo/adrs/0001-rust-cli-and-stdio-mcp.md.

5. Proposed File Tree

.
├── README.md
├── LICENSE
├── SECURITY.md
├── CONTRIBUTING.md
├── AGENTS.md                    # canonical AI-readable contract for this repo
├── CLAUDE.md                    # pointer to AGENTS.md until `update` generates it

├── bin/
│   └── majordomus               # single portable-shell entry point

├── lib/                         # sourced shell modules, one per subcommand
│   ├── common.sh
│   ├── doctor.sh
│   ├── start.sh
│   ├── check.sh
│   ├── watch.sh
│   ├── update.sh
│   ├── handover.sh
│   └── finish.sh

├── share/
│   ├── skeleton/                # what `init` copies into a project's .majordomus/
│   │   ├── policy.yaml
│   │   ├── profiles/            # routine · implementation · debugging · deep-work
│   │   ├── templates/           # handover, completion, decisions, open-questions
│   │   └── providers/           # body.md + one wrapper template per provider
│   └── allow/                   # key allowlists: unknown keys are errors

├── .majordomus/                 # this repository supervising itself
├── .githooks/                   # pre-commit runs doctor, pre-push runs finish --check

├── test/
│   ├── run.sh
│   └── cases/                   # one script per behaviour, disposable repo each

├── examples/
│   └── minimal/                 # a scratch repo walked through start → finish

└── docs/
    ├── README.md                # index
    ├── DESIGN.md                # specification
    ├── CLI.md                   # commands, exit codes, target output
    ├── SCHEMAS.md               # every file, with a concrete example
    └── EXTRACTION_REPORT.md     # this document

Removed from the initial proposal: principles/ (folds into policy.yaml and README), workflows/ (the lifecycle is the CLI; a workflows directory would be a second description of it), a scripts/majordomus-* fan of executables (one entry point, sourced modules). Nothing was kept because the prompt proposed it.

6. Canonical Concepts

Ordinary engineering vocabulary. No court.

TermMeaning
policythe one canonical YAML file; provider-neutral
profilea named bundle fixing effort, verbosity, context toggles, verification, and output contract for a task class
projectiona provider-specific instruction file generated from policy; fingerprinted
taskthe one active unit of work; state/current.yaml
scopethe normalised path set a task may touch
claimscope recorded for overlap detection; part of start
checkpointan update to current.yaml; staleness is measured from the last one
handoveran append-only continuation record with required sections
outcomeone of completed / partial / blocked / no_match / failed
finish contractthe checklist finish evaluates before accepting completed
ledgerstate/ledger.jsonl, append-only, retention-capped, written only by Majordomus
ratcheta one-integer baseline that may fall but not rise
wiredan enforcement whose artifact exists, is executable, and is reachable from its dispatcher
driftany deterministic disagreement between policy, projection, state, and git

7. MVP Boundary

v0.1 can: hold canonical policy; define four profiles; start a scoped task with a normalised claim; keep durable state with git-derived identity; label state staleness at read time; write and resolve handovers; evaluate a finish contract; detect the drift classes in the design; generate fingerprinted projections for four providers; prove its own enforcement is wired; run entirely offline in portable shell.

v0.1 cannot: invoke a model; measure tokens or cost; route dynamically; hook a worker's runtime; run anything in the background; coordinate across machines; represent task dependencies.

The boundary is stated in the README with equal prominence for both halves.

8. Extraction / Confidentiality Audit

Performed on this repository's contents before each push.

  • No internal paths, hostnames, URLs, customer or case identifiers, or secrets appear. Checked by grep for the source environment's directory names, host patterns, and vocabulary; zero hits.
  • No quotation from private material appears. Magnitudes are reported; sentences are not.
  • The source environment's doctrine vocabulary (pillar names, acronyms, theatrical labels) does not appear.
  • The only brand reference is "Prismatic" in the product name and in the one-way origin statement.
  • The working ledger used during extraction lives in a session scratch directory outside this repository and is not committed.
  • Dependency direction: none. This repository imports nothing and links to nothing in the source environment.

9. Risks

RiskMitigation in design
Majordomus itself decays like the four tools before itdoctor runs against its own installation first; every check has a reproduce command; the README states what is guaranteed versus observed
Portable shell limits (macOS ships bash 3.2; BSD awk and sed differ)no associative arrays, no mapfile, no GNU-only flags; tested on both; YAML handled by a small dependency with a documented fallback
YAML parsing in shell is fragilev0.1 restricts the policy schema to a flat, parseable subset and validates it; a real parser is used where available
Projections get hand-editedfingerprints make it visible in one command; the generated header names the regeneration command
Users skip start and work unsupervisedcheck and finish refuse without current.yaml; the projected instructions tell the worker to run start
Over-blocking creates bypass cultureblocking is limited to cheap, deterministic, self-evidently correct checks; everything about work-in-progress is reported, not blocked
Scope creep during implementationthe "intentionally absent" list is a public commitment; the next phase's review asks, for each responsibility, documented / represented / validatable / enforced / measurable, and does not confuse the first with the last
Two concurrent AI writers in one checkout, which the design says should be two worktreesObserved while building v0.1 itself: the CLI and the derived website were written by two sessions in one working copy. It held because the scopes were disjoint by directory and both sides declared them to each other before touching anything, which is the claim step the design makes mandatory. The tool cannot yet enforce this on the checkout it lives in; start refuses a second task, so the second writer simply did not run it. Recorded as a limitation, not hidden. The cleaner example from the same evening: the site generator was correct against a base URL it never actually resolved against, so every asset 404ed once deployed under the repository path, and neither writer could have caught it alone; it took one screenshot from one side and one fix from the other, and the fix shipped with a check that fails on any unprefixed path. That is the repository's own rule applied to itself: no claim without an executable behind it
The brand invites reading it as a slice of the source platformthe origin statement is one-way and explicit; there is no shared code

Five phases, each gated by the reality of the previous one, none started before the previous is reviewed.

  1. Design — this document and DESIGN.md. Complete.
  2. Implement v0.1 — complete; see CLI.md, SCHEMAS.md, test/cases/. Original brief: — the file tree above; behavioural tests in disposable repositories for: init happy path, missing policy, invalid YAML, unknown key, missing scope, unfinished task, valid completion, handover write and resolve, refusal to overwrite, each drift class, and the healthy no-finding case. Every README claim is compared against implementation before the phase closes.
  3. Make supervision real — for each responsibility, answer documented / represented / validatable / enforced / measurable honestly; close the highest-value gaps in this order: state integrity, policy integrity, drift detection, lifecycle integrity, handover integrity, projection drift, budget visibility, coordination visibility. Produce a supervision model document that separates guaranteed from observed.
  4. Hostile review — claim/implementation matrix; attempt to delete 30 % of the repository; ten-minute new-user walk-through; clean-extraction scan including git history; security and portability audit; terminology audit. Returns READY or NOT READY with blockers.
  5. Release — only after READY. Template repository, minimal CI, v0.1.0, release notes without inflation, remote validation including a fresh template-generated repository.

Phase 3 is the next action.


11. Second pass — session records and a knowledge compiler

The first pass studied instruction files, hooks, state stores and coordination. It did not study two subsystems that turn out to matter for the same problem: a file-based session-note discipline, and a stateless Markdown compiler that produced a browsable generated note vault from a repository's own documents.

They were re-examined when M003 was planned. The method is unchanged — study, abstract, challenge, synthesise — and so is the confidentiality rule: mechanisms and magnitudes are reported, code and vocabulary are not.

The two are of very different quality, and the difference is itself the finding. The session-note discipline is filename-shaped: it enforces a naming grammar and has no model of what a record contains. The compiler is properly built, and nearly every guard in it carries a comment naming the failure it prevents.

What the evidence says

ObservationMagnitudeConsequence for this design
A session-note directory with a stated retention of "most recent 50 files"roughly 10 GB across 585 entries at this pass; section 4 records 1,496 files at the first pass, and both are kept rather than reconciled — the count moved, the volume did notThe bloat was not a retention failure. A second producer wrote non-Markdown snapshots into a directory whose every tool filtered on one extension, so no threshold applied and no audit saw them. Retention has to be a property of a store, not of a file pattern.
Its recordsfree text end to end; a machine-checkable field count of zero, including an identifier field labelled auto-generated that nothing generatedA record that asserts a commit hash nobody computed is worse than no record. Every identity field of a Majordomus session is computed from git and refused in an authored body.
Its "compression" stepreplaced the original in place with a grep-derived digest, keeping the only full copy untrackedMajordomus archives and never rewrites. Nothing summarises a record, because nothing here calls a model and a regex digest is a lossy model with worse failure modes.
That step's file selectora hardcoded year prefix that stopped matching when the year changed; it exits reporting success having done nothingA maintenance command that can do nothing and report success is indistinguishable from one that works.
Its ordering and selectionfiles selected by filesystem mtime, then grouped by the date parsed out of the filenameTwo clocks in one loop, and mtime does not survive a clone. Majordomus orders by the recorded timestamp and breaks ties with the ledger.
Its enforcement hookdocumented as blocking; silently matched nothing on every platform because NUL-delimited input was piped into a line-oriented filterThe same failure class the first pass found seventeen times, found again in the subsystem meant to prevent it.
Its retention numbersfour destination-and-threshold pairs for one dataset, across a policy, a README and two scripts, none reconciled, none with a stated derivationRetention is one policy block, read by one command.
Its compliance percentagecounted exempt files in the denominatorA number that systematically understates the thing it exists to report.
The compiler's discoverydriven by git ls-files; a last recorded run classified 25,539 tracked Markdown files into 3,655 curated, 2,662 session logs and 19,222 excludedRepository truth, no exclusion globs to maintain, no build output, no untracked files. Adopted directly.
The compiler's edges7,731 edges, every one explicit, none inferredThe graph knows only what somebody typed. That is the property worth having first.
Its broken links and orphansroughly a third of its nodesReported, never gated, with the reasoning recorded: gating on a count that is large by design trains people to ignore the failure.
Its cold runabout 2.1 seconds over 3,655 sources, with the manifest short-circuiting rendering but not analysisA manifest that only skips writes is worth much less than one that skips work. Measure before believing.

Adopted, by re-derivation

Each of these is an idea, re-implemented from scratch in portable shell and awk against Majordomus's own records. No code, no schema and no vocabulary was carried across.

IdeaWhat it becomes here
Identity from a stable source fact; hashes only for change detectionA node id is a canonical id or a repository path. source_hash says whether it moved, never what it is.
Provenance required on every edgeAn edge carries the file it was observed in, and an edge without one is a validation failure rather than a silent drop.
A confidence vocabulary whose "inferred" value is currently unusedThe graph's trustworthiness becomes a checkable claim: the count of inferred edges is zero, and a command says so.
Classify from structure and declared metadata, never from proseA node's kind comes from its source class or an explicit field. A document containing the word "roadmap" does not become a roadmap.
unknown as a first-class answerAn unrecognised source is a node of kind unknown, reported, not guessed at and not dropped.
Never default a status to a plausible valueAlready the rule for issue status, which is derived and has no stored field. Extended to node kind.
Gate only on defects this run causedA broken reference between two things Majordomus owns is a failure. A link to a deliberately external resource is not.
Absent is not corruptA missing manifest is a first run. A manifest that does not parse fails loudly, because treating it as absent would silently disable the guarantee it exists to provide.
Tag a link by the syntax it was written in, not by the shape of its targetAn extension-less relative link is a link because of how it was written.
Strip fenced code before scanning for linksA path inside a code sample is an example, not a reference.
Order-independent, hash-backed collision disambiguationA readable fragment is a hint; the hash is the guarantee.
Record both values when two sources of one fact disagreeReport the drift rather than picking a winner and hiding it.
A per-file failure is an error, not an aborted runOne malformed input must not cost the build.
Sorted, de-duplicated generated outputThe artefact is diffable, so a rebuild's effect is reviewable.
Project a high-volume, low-durability corpus as one indexCheckpoints are referenced from their session, not made one node each.
Filename-carried UTC timestamps for chronological recordsAlready the shape of a checkpoint and a handover here, and it already carries the sub-day resolution and the uniqueness component the studied grammar lacked.
Skip the heavy runtime when the job does not need itAlready true by construction: there is no runtime to skip.

Refused

RefusedWhy
Any database — embedded, relational, columnar or graphThe corpus is a few thousand small files. A database is a dependency, a migration story and a second source of truth bought before anything measured a need for one. Measure first.
Embeddings, vector search, similarity, clustering, automatic taxonomyNone can name the line that justifies the relation, which is the test the extraction boundary now uses. They are also unfalsifiable by the person best placed to notice they are wrong.
Any generated summary of a session, by a model or by a regexMajordomus calls no model, and the studied regex digest is the case study in why the cheap substitute is worse. A worker writes its own summary or the record has none.
A note-vault renderer, and any dependency on a particular note-taking applicationThe graph is renderer-independent. A renderer can be added later against the same generated data; nothing in the core may assume one.
Copying record bodies into a session noteThe single most consequential defect of the studied session store. A session is an envelope of references.
A mutable session file that other commands append references to as they runIt puts a write on the hot path of every command and recreates the second store. The envelope is derived from the ledger at close.
Destructive compression or in-place rewriting of any recordArchiving moves; it never rewrites and never overwrites an archive.
Filesystem mtime as an ordering or selection inputIt does not survive a clone and it is not the time the record asserts.
A retention rule that names a file pattern rather than a storeThe 10 GB directory is what that costs.
Bypass channels for an enforced ruleTwo existed there, in a discipline whose own policy said it could not be bypassed.
A second "search" whose contract quietly replaces the firstsearch stays a literal scan over durable records with no index. knowledge search is a different corpus with a different contract, and both say so.