๐ The Seat Contract
The engine (engine/dual_agent_loop.mjs) is target-agnostic. A concrete
harness is a config object that fills the seats for one domain. You
should never need to touch the engine; you write a config, bundle it withtools/bundle.mjs, and run it.
The seven seats
| seat id | holder | glyph | algebra | phase | permitted writes |
|---|---|---|---|---|---|
measure |
the assessor | ๐ง๐ฐ | โ | Measure | nothing (numbers are returned, not written) |
propose |
soulbae | ๐ง | bnot |
Propose | nothing (plans only, never implements) |
hold-apart |
the Gap | โฟป | xor |
Hold-apart | runs/<id>/p<i>-*/gap.json |
assay |
soulbis | โ๏ธ | neg |
Assay | runs/<id>/p<i>-*/ scratch + verdict.json |
critic |
the critic | ๐ก๏ธ | โ | Critic | nothing (classifications are returned) |
chronicle |
the chronicler | ๐ | โ | Chronicle | runs/<id>/CHRONICLE_DRAFT.md |
keystone |
soulbae โฅ soulbis | ๐งโ๏ธ | succ |
between rounds, main session | frontier.json ยท claims_register.md ยท manifest.yaml ยท fold into the artifact |
The three algebra seats are the core: soulbae proposes (bnot, complement โ
reduce, reshape), the Gap separates (xor, the held-apart derivation),
soulbis proves (neg, negation โ the verdict that does not flatter). Their
composition is the successor: neg(bnot(x)) = succ(x). The keystone is not a
third agent โ it is the pair itself, operating in the main session where the
ledgers live, with the door (G4) always the First Person's.
The config shape
export default {
name: 'my-harness',
// objective โ the first three required; the fourth is advised and will
// likely become required
objective: {
metric: 'what frontier.json tracks, lower is better (e.g. "words")',
gate: 'the held-out check that must fully pass โ T5: zero collapses',
hardConstraint: 'validity no score can override โ GR-3',
canary: 'an artifact that passes `gate` BY CONSTRUCTION, and why',
},
// trust literals โ conform.mjs checks these
door: 'first-person', // T6, exact literal
heldApartRule: 'REQUIRED. Injected verbatim into every propose prompt. ' +
'State that the proposer never sees, chooses, or influences the ' +
'held-out witnesses, and that witnesses derive from the proposal ' +
'artifact by hashing.', // T2 / GR-4
keystoneOnlyWrites: ['frontier.json', 'claims_register.md', 'manifest.yaml'],
// finders โ soulbae's parallel lenses, >= 2, blind to each other
finders: [
{ lens: 'lens-a', hint: 'what this lens looks for' },
{ lens: 'lens-b', hint: 'a genuinely different angle' },
],
// prompt builders โ strings; the engine prepends the boot preamble
prompts: {
measure: (ctx) => '...',
propose: (finder, measure, ctx) => '...',
holdApart: (proposal, i, ctx) => '...',
assay: (proposal, gap, i, ctx) => '...',
critic: (proposals, verdicts, ctx) => '...',
chronicle: (round, ctx) => '...',
},
// JSON Schemas forcing structured seat output โ all five required
schemas: { measure, proposal, gap, verdict, critic },
// stopping
stop: { dryRounds: 2, maxRounds: 5 },
// predicates
isValidated: (v, measure) => v.status === 'VALIDATED', // must already
// conjoin gate-pass AND metric win inside the assay verdict (T5)
isStructural: (critic, leverId) =>
critic.classifications?.some(c => c.leverId === leverId && c.class === 'structural'),
// optional: extra numeric checks conform.mjs runs against frontier.json
conformChecks: [(frontier) => /* return array of error strings */ []],
}
ctx is { root, repo, runId, runDir, config } โ paths already normalized.repo is the instance directory (frontier, artifact, runs); root is the
skeleton clone holding GROUND_RULES.md, TRUSTS.md, and seats/, which
every seat boots from (T3). Pass root in the run args whenever the instance
lives outside the skeleton and does not carry its own copies of those
documents โ it defaults to repo.
Recommended schema fields
Instantiate these for your domain; the shapes below are what survived the
source harnesses.
- proposal: the schema validates one FINDER's return, and the engine
expects the wrapper{ proposals: Proposal[] }(a lens may return more
than one lever). EachProposalis{ leverId, title, lens, rationale, expectedMetric, hardConstraintNote, diffPlan, killedLeverCitations[] }โleverIdis a short kebab id;diffPlannames real parts of the real
artifact;killedLeverCitationsproves the proposer readnotes/KILLED_LEVERS.md. - gap:
{ seedHex, draw, transcript }โ the transcript must let a third
party re-derive the seed: the exact canonical serialization (sorted keys,
no whitespace), the exact hash command, the exact draw rule. - verdict:
{ leverId, status: 'VALIDATED'|'MIRAGE'|'BLOCKED', metric, gateResult, evidence, scratchDir }โ evidence is run commands plus key
output lines.MIRAGE= passed the proposer's story, failed the full gate;
name the failing check.BLOCKED= could not run; name the missing piece. - critic:
{ classifications: [{leverId, class: 'structural'|'probe-limited'|'noise', why}], nextLead, killedLeverDrafts[] }
โ exactly onenextLead.
Optional: mana (advisory, display-only)
A config MAY declare mana: ['๐', โฆ] (or a single string) โ the instance's
declared mana affinity from the workshop taxonomy of its universe. The engine
ignores it entirely; the conformance gate does not check it; the workshop
console and the artefact mint carry it through as display chips and asartefact.json.mana. It is colour, not proof โ the mana is what you play
with; the VRC is what you prove with โ and an instance with no universe
simply omits it.
The canary โ and when NOT to build a harness
A gate with no artifact known to pass it cannot distinguish a bad candidate
from an impossible gate. Name one, in objective.canary.
The spar is safe by accident: its 8 questions are drawn from GUIDE.md, so
the uncompressed original answers all 8 by construction. The feasible set is
provably non-empty, and every MIRAGE is therefore the candidate's fault.
Take the canary away and a total failure becomes ambiguous โ which is exactly
what happened to the universe-builder instance, where two blind lenses each
scored 0/8 against a gate that no candidate could pass, and the critic,
having no word for a bad gate, convicted the proposer on sound evidence.conform.mjs advises on a missing canary today.
Before you write any of this, ask whether you need a harness at all:
The Gap earns its cost only where the claim space is too large to check.
shor_mage samples 9,024 witnesses because you cannot run every input. tigzkp
samples held-out points because R1CS equivalence cannot be enumerated. There,
sampling is a necessity and hashing the proposal is what makes it honest.
If your claim space is enumerable, check all of it. Against an exhaustive
check a mirage is impossible: there is nothing to tune to. You want an
auditor โ deterministic, total, cheap โ not a prover. universe/audit.mjs
is one, and universe/retired/ is the harness it replaced, kept as a worked
negative result.
The complementary test, from Promise Theory: the split is warranted when a
single agent would have to promise two things it cannot independently
control. If one agent can honestly promise the whole job โ because anyone
may verify it completely, afterward โ you do not need the pair.
Product objectives โ the complement pair
If your objective is a product of two factors (say gates ร qubits),
give soulbae one finder per factor โ Factor-A-minimiser โฅ Factor-B-minimiser,
blind to each other โ and put a cliff-watcher clause in the assay prompt:
score ฮ(product), and reject any move that wins one factor at the other's
expense past break-even. The constitutional basis is T5: any zero (or any
net-negative trade) collapses the whole. This pattern comes from the
shor_mage instance, where the score is avg executed Toffoli ร peak qubits.
When one factor is a binary gate (pass/fail), the gate itself is the
cliff-watcher: a fail is a zero, and a zero collapses the product. The spar
(examples/field-guide/) uses this form.
The runtime adapter
The engine takes rt = { agent, parallel, pipeline, phase, log }:
agent(prompt, opts) -> Promise<any>โ run a subagent; withopts.schema(JSON Schema) it returns a validated object.parallel(thunks) -> Promise<any[]>โ barrier; failed thunks resolve null.pipeline(items, ...stages) -> Promise<any[]>โ per-item stages, no
barrier between stages; stage callbacks receive(prev, item, index).phase(title),log(msg)โ progress display.
runHarness returns { status, rounds, tally, confirmed, best, detail, keystoneTodo }. status is 'COMPLETE' or 'INCOMPLETE'. A round that
loses a seat to infrastructure โ a dead agent, a killed session โ is markedFAILED, does not count as a dry round, and stops the loop with anaborted block naming the dead seats. An outage is not an exhausted search,
and keystoneTodo says FOLD NOTHING when the two are confused. A finder
that returns zero proposals is still a dry round: empty is evidence, dead is
not. engine/loop.test.mjs pins this.
Inside the Claude Code Workflow tool these five exist as globals andimport is unavailable โ which is why tools/bundle.mjs concatenates the
engine and your config into one self-contained .workflow.mjs. Under the
Claude Agent SDK or a plain node driver, implement the five functions
yourself and call runHarness(config, rt) directly. Configs must therefore
be self-contained: no imports.
Assets
Navigation
the wiki is a projection; ~/dual-agent-harness (git) is the source of truth ยท (โ๏ธโฅโฟปโฅ๐ง)๐ = neg โ bnot โ succ