How the harness's multiagent swarm relates to the Darwin Gödel Machine (arXiv:2505.22954) and Hyperagents (arXiv:2603.19461), what the trajectory log records, and the design for an internal DGM-H-style system built on the harness's primitives.
Yes — but until this branch, only in one fixed way. Agent.systemPrompt()
gives every subagent the same comptime-frozen sub_system_prompt (a lean
variant of the root prompt with the meta-tools stripped). The root's prompt
— including runtime set_system_prompt mutations — never propagates to
children. Differentiation between swarm members came entirely from the
task prompt (the user message), never the system prompt.
Now the swarm supports per-child prompt variants: the subagent tool
and each workflow task accept an optional system_prompt that replaces
the lean default for that child only. That makes the swarm
prompt-heterogeneous on demand — the precondition for any DGM-style
evolution over prompts.
So the complete answer: the system prompt changes at three places, each visible in the trajectory log —
| where | mechanism | trajectory evidence |
|---|---|---|
| root, between turns | set_system_prompt (--json / SDK) |
spine node's prompt_sha changes, prompt_mutated: true |
| every subagent | fixed lean sub_system_prompt |
child prompt_sha differs from spine sha, prompt_mutated: false |
| any subagent / workflow task | per-child system_prompt override |
child carries its own sha, prompt_mutated: true |
Each run-scoped .graff/trajectories/<run-id>.jsonl holds one JSON node per
agent run, in exactly the shape of the DGM's archive tree:
nodes are agents, edges are parent→child, and every node carries the
fingerprint of the program (here: system prompt) it ran with.
{"id":2,"parent":1,"kind":"turn","label":"gpt-5.5","t":14581,"ms":6146,
"prompt_sha":"06ce42037d0ec874","prompt_mutated":true,
"task":"Call the subagent tool…","ok":true,"context_tokens":1084}
{"id":3,"parent":2,"kind":"subagent","label":"hello child","ms":1580,
"prompt_sha":"55f810d6ab325126","prompt_mutated":true,
"task":"Reply with exactly: done","ok":true,"context_tokens":381}- Spine: root turns chain
parent = previous turn. Aprompt_shachange along the spine is aset_system_promptmutation edge. - Fan-out: every subagent / workflow task attaches to the turn that
spawned it.
prompt_mutated: truemarks a per-child variant. - Fingerprint: first 8 bytes of SHA-256 of the effective system prompt,
hex. Equal hashes = identical prompt. The full text behind each
fingerprint is captured once per session as a
kind:"prompt"record, so the archive is self-contained: any lineage can be replayed or re-mutated from the file alone. - Append-only aggregate archive: each process exclusively creates one
run file, so concurrent sessions never share a cursor. Readers scan all
.graff/trajectories/*.jsonlfiles (plus the old single-file archive during migration). Node ids restart per run; cross-run lineage threads throughprompt_sha. Delete the directory any time to reset the archive. - Scores: the evaluation phase writes back through the --json protocol —
{"type":"score","prompt_sha":"…","score":0.7,"notes":"…"}appends akind:"score"record (SDK:h.score(prompt_or_sha, value, notes), withpromptFingerprint/prompt_fingerprinthelpers so drivers compute the same sha the harness logs). Multiple scores per sha accumulate; readers aggregate. /trajectoryrenders the current session's tree in the REPL, with the latest archive-wide score shown next to any node whose prompt has one.
The Hyperagents paper (DGM-H) alternates two phases over an archive: metacognitive self-modification (a parent generates a modified child) and evaluation (the child is scored on tasks), with parent selection ∝ sigmoid(score − frontier midpoint) × 1/(1 + children). The harness now exposes every primitive that loop needs; the loop itself belongs in SDK code (Python/TS), not in the harness:
- Editable program = the system prompt (+ task prompt). The hyperagent
insight is that the meta-level must itself be editable: with
set_system_prompt, the driver agent's own prompt is as mutable as its children's — task agent and meta agent are the same editable surface, which is precisely the hyperagent property (one program, both roles). - Modify = the driver asks its model to rewrite a parent's prompt (or
the driver's own), then spawns the child via
subagent/workflowwithsystem_prompt: <mutated>. - Evaluate = run the child on a task set; score its reports (an
LLM-judge subagent, exit-code checks, benchmark diffs — whatever fits the
domain). Write scores back as an annotation file keyed by
prompt_sha. - Archive = the union of
.graff/trajectories/*.jsonlfiles across runs plus the score annotations. The trajectory gives lineage (parent,prompt_sha,prompt_mutated); the annotations giveperformance(aᵢ)and children counts — everything DGM-H's parent selection needs:sᵢ = σ(λ(αᵢ − α_mid)),hᵢ = 1/(1+nᵢ), sample ∝ sᵢ·hᵢ (λ=10, α_mid = mean of top-3, per the paper). - Recipe outcome = every root turn also binds the prompt genome to its exact provider/model, reasoning effort, toolset hash, harness version, and coarse task class. Success, latency, model/tool calls, tool errors, cache/uncached tokens, and estimated cost remain separate objectives so a later selector can maintain a Pareto frontier instead of promoting a cheap failure through one gameable scalar. This is observational today; automatic model/effort promotion requires signed recipe provenance and canary rollback.
- Recitation / memory (the capability DGM-H invents for itself —
performance trackers, IMPROVEMENTS.md): the harness already encourages
filesystem-as-context (Manus lessons); the driver should keep its
archive + scores in files, exactly like the paper's emergent
PerformanceTracker.
Sketch of the driver loop (Python SDK) — everything below is wired today:
from harness_sdk import Harness, prompt_fingerprint
archive = load_all(".graff/trajectories/*.jsonl") # prompts + nodes + scores
h = Harness(yolo=True)
for _ in range(iterations):
parent = sample(archive, key=lambda a: sigmoid(a.score) / (1 + a.children))
child = h.ask(f"Mutate this agent prompt to fix its weakest behavior:\n{parent.text}")
report = h.ask(f'Use the subagent tool: prompt "{task}", system_prompt "{child}"')
h.score(child, judge(report)) # kind:"score" appended, keyed by sha
archive = load_all(".graff/trajectories/*.jsonl") # all capturedThe aggregate archive is the single source of truth: the harness captures the
lineage (kind:"turn"/"subagent" nodes), the genomes (kind:"prompt"),
and the fitness (kind:"score"); the driver only decides what to mutate
and how to judge.
KV-cache note (Manus): per-child system_prompt overrides are cheap —
each subagent is a fresh context anyway, so a variant prompt costs nothing
extra. Mutating the root prompt mid-loop is the expensive operation
(full-prefix cache invalidation); a DGM-H driver should mutate children
freely and its own prompt only at generation boundaries.
MAP-Elites keeps an archive organized as a grid over behavioral niches, retaining only the best (elite) solution per cell — quality and diversity, which is what keeps open-ended search from collapsing onto one local optimum (the DGM family inherits this directly).
In the harness the answer to "where do we create the MAP-Elites" is:
- Niches = agent types. The feature space is the set of named personas.
Builtins ship compiled in — reviewer, researcher, implementer, skeptic —
chosen as orthogonal behavioral dimensions (find defects / gather
evidence / change code / refute claims), not job titles. Finer grids
encode extra dimensions in the name (
reviewer.terse,reviewer.deep). - Elites =
.harness/agents/<niche>.md. One markdown per cell: frontmatter (name,description,score) + body = the elite's system prompt. A file shadows the builtin of the same name — promoting a new elite is just overwriting the file./agentslists the live grid with each cell's fingerprint and score. - Selection pressure = the driver. After scoring a variant
(
h.score(...)), the driver compares against the incumbent's frontmatterscore; if the challenger wins its niche, the driver writes the new markdown. The next session — or the nextagent:"<niche>"spawn — automatically runs the new elite. Nothing else needs to change: the prompt is fingerprinted and captured into the trajectory archive on first use like any other. - Collector:
harness-telemetry, a Cloudflare Worker (zigrepper/services/harness-telemetry) sharing codegraff-data's D1 —OTEL_EXPORTER_OTLP_ENDPOINT=https://harness-telemetry.rachpradhan.workers.devlands sessions/scores/events inharness_sessions/harness_scores/harness_events, withharness_variant_fitnessas the per-sha leaderboard andGET /v1/statsas the did-it-work endpoint. - Validation = OTEL. When an OTLP endpoint is configured, every score
write-back emits a
scorelog record (prompt_sha,valueas a double), every override-spawned child bumpsprompt_variants, and the session summary carriesprompt_variants+scores_recorded— so a dashboard can confirm the evolution loop is actually running (variants spawned, evaluations landing, scores trending) without reading any local file. Verified end-to-end: a namedagent:"echoer"spawn +h.score()produced matching fingerprints in the markdown, the trajectory archive, and the OTEL score record, with session countersprompt_variants=1, scores_recorded=1.
The promotion loop, concretely:
def promote_if_elite(niche, challenger_prompt, challenger_score):
inc = read_frontmatter(f".harness/agents/{niche}.md") # may not exist
if inc is None or challenger_score > inc["score"]:
write_agent_md(niche, challenger_prompt, challenger_score) # new eliteThe least things a DGM loop needs — everything below is wired:
| ingredient | where it lives | captured by |
|---|---|---|
| genome | a system prompt | kind:"prompt" records (auto) |
| archive | variants + fitness | trajectory file / harness_scores |
| lineage | parent → child mutation edges | score(parent=…) → parent_sha |
| process signal | tool sequences per run | tools on nodes + run OTLP events |
| evaluate | a judge — the only domain-specific part | driver-owned (judge()) |
| modify | mutate a parent's prompt | one h.ask call |
| select | sigmoid(fitness−frontier) × 1/(1+children) | ~12 lines, driver-owned |
examples/dgm_loop.py is that checklist as ~110 runnable lines. Verified
live: two generations evolved seed → child → grandchild, with generation 1's
parent selection reading generation 0's score out of the archive, the
lineage chain intact in parent_sha, and each child's tool sequence
(bash,bash) recorded against its fingerprint.
Tool-sequence mining then works on either store: locally,
jq 'select(.kind=="subagent")' .graff/trajectories/*.jsonl joined to scores
by sha; in D1, run events land in harness_events (attrs JSON includes
tools, ok, variant) and harness_variant_children gives the novelty
term. Patterns with positive lift feed back as prompt instructions or as
per-niche tool grants (agent_variants.tools).
The four-agent debate (and the honest threat model) converged on one crux:
the fitness float was both ungrounded (an LLM grading a self-report) and
unauthenticated — a forged {"kind":"score",…} row appended to a
trajectory JSONL via write_file (cwd is inside confinedPath)
or echo >> manufactures fitness AND parent_sha lineage that selection
then rewards. A perfect judge doesn't matter if nothing forces the loop
through it. So Step 0 authenticates the channel; it's the precondition for
any level of closing the loop, and it's useful even if you never do.
What's signed. Each score record carries sig = HMAC-SHA256(key, msg)
where msg is the canonical, version-tagged, newline-joined tuple
v2\n{prompt_sha}\n{parent_sha}\n{score:.6f}\n{run_id}\n{judge_id}\n{artifact_sha}\n{eval_set_hash}\n{niche}\n{provider_class}
(score fixed to 6 decimals so Zig/Python/JS agree byte-for-byte —
src/scoring.zig scoreSigMessage, harness_sdk.score_signature, the worker's
scoreSignature). Run-scoped files reserve top-level run_id for invocation
identity and store the signed score provenance as score_run_id (legacy/D1
rows use run_id). Records also gain judge_id, artifact_sha, and
eval_set_hash so the evaluator, the artifact it judged,
and the held-out eval are all bound into the signature; v2 (issue #168 Gap 2)
additionally binds niche and provider_class so a score can't be replayed
into another MAP-Elites cell. Verifiers still accept the legacy v1 envelope
(the same tuple without the last two fields) during the transition window.
Key custody. GRAFF_SCORE_KEY_FILE names a key file outside cwd and
defaults to ~/.simple-harness/score.key. The file is unreadable by the evolving
variant — subagent file tools are confined to cwd, and subagent bash is
cwd-locked with no redirection. confinedPath guards cwd-escape, but the
archive and main.zig live inside cwd, so the key must live outside it.
Root / --yolo is the trusted orchestrator boundary.
Verification (fail-closed when a key is set). dgm_loop.py's
load_archive rejects unsigned/forged score rows (and logs the count); the
harness-telemetry worker, given a SCORE_KEY secret, recomputes the HMAC
on OTLP ingest and drops unsigned/forged scores so the shared D1 fitness
store can't be poisoned. Unset key → accept all (backward compatible).
Verified end-to-end: a Zig-signed row verifies in Python; tampering the
score, the lineage, dropping the sig, or using the wrong key all reject;
dgm_loop admits the genuine row and rejects a hand-appended one; and the
deployed worker accepts a genuine signed score while dropping a forged
unsigned OTLP POST.
Grounding came next — Step 1, below. Signing makes the float authentic; Step 1 makes it meaningful.
Step 0 left one hole: the judge was still h.ask grading a self-report,
which saturates to 1.00 on easy tasks and rewards confident claims over
actual work. Step 1 replaces it as the primary gradient:
Held-out replay eval. examples/replay_judge.py runs a candidate
genome against examples/dgm_eval_set.jsonl — tasks with DETERMINISTIC,
out-of-process assertions (a shell command's exit code or an exact final
answer), each in a fresh scratch directory so file assertions are hermetic.
Exact-answer checks deliberately reject substring false positives such as
1391 for an expected answer of 391. The separate
examples/dgm_canary_eval_set.jsonl supplies unseen boundary variants for a
final generalization check. The judge is dependency-free (speaks the
harness --json protocol directly) so its pinned copy runs anywhere. Candidate
genomes are installed through the stdin control protocol rather than command
arguments, keeping full prompt text out of ordinary process listings.
Custody (same model as the score key). --pin installs the eval set
and the judge itself under ~/.simple-harness/evals/ — outside the
editable tree — and prints GRAFF_EVAL_SET_FILE, GRAFF_EVAL_SET_HASH,
GRAFF_REPLAY_JUDGE for the orchestrator's environment, which a
cwd-confined variant cannot edit. The judge recomputes the eval set's
sha256 on every run; a pin mismatch refuses to score (exit 3, fail
closed), and that hash is written into each score record's signed
eval_set_hash field, so the verdict is bound to the exact suite used.
Score bands make the Goodhart ceiling structural. In dgm_loop.judge:
replay judge failed/missing → 0.0 (fail closed)
pass_rate < 1.0 → 0.8 × pass_rate (capped at 0.8)
all held-out tasks pass → 0.9 + 0.1 × efficiency
Once correctness saturates, efficiency = tasks / (tasks + tool_calls)
reorders passing variants. A variant that fails any deterministic check cannot
reach the promotable band (≥ 0.9), no matter how persuasive its report sounds.
Verified (examples/test_step1.py): the band invariant holds for every
failing pass rate; exact checks reject substring false positives;
judge-missing and pin-mismatch paths fail closed; and live, a sabotage variant
whose report claims success is pinned ≤ 0.8 by the failing replay suite while
the honest seed lands ≥ 0.9 with eval_set_hash populated and signed.
- Eval-set breadth: the 15-case replay suite plus 10-case canary prove the mechanism; a real evolution run still wants a larger private corpus broad enough that overfitting is harder than being generally good. Growing it is data work, not code.
- Parent selection: the archive has everything the formula needs (scores per sha, children per node); the sampling lives in the driver — per the paper, even DGM-H's self-modified selection didn't beat the handcrafted score-child-prop mechanism.
- Depth > 2: subagents can't spawn subagents, so trees are spine + one fan-out level. DGM-H doesn't need deeper nesting (its tree is generational, not call-stack), but lifting this is the obvious next step if a child should itself act as a meta agent.
Steps 0–1 close the loop on one machine. Step 2 is the reframe that makes graff's being free + provider-agnostic the whole point: the fleet is the archive. Thousands of installs generate and evaluate variants during normal use, the shared D1 store is the MAP-Elites grid, and the output is better built-in personas shipped back to everyone. Single-tenant DGM → federated DGM.
The load-bearing constraint is "let the provider be me": every expensive
step (mutating a prompt, running the eval) runs on each user's own provider,
locally unless the user raises the learning ceiling. Central infra (harness-telemetry) does only
cheap, deterministic work — HMAC-verify, artifact re-check, SQL-aggregate,
serve elites. No central inference → it runs at fleet scale for free.
- Promotion: the backend decides over millions of traces (automatic, no human PR). Defended statistically (below), not by review.
- Eval: private suite, public hash. Only
eval_set_hashis public; the suite is secret and rotated, so the honest majority can't study to the answer. Already bound into every score signature (Step 0). - Contribution: aggregate by default, independently scoped.
/privacy aggregateis the shipped default, announced once per machine, and admits prompt-free scores/metadata;/privacy localopts out./privacy templatesraises the ceiling, but each private persona still requires exact interactive approval;GRAFF_FLEET=offremains a master kill switch. - Grid: niche × provider-class. The elite you receive is the one that won on your model tier — "let the provider be me" made literal.
The hard invariant is that raw task/user content never enters fleet egress. Aggregate mode sends only scores and controlled metadata. A reusable persona genome can be transmitted only after the user previews and approves that exact version; task prompts, bindings, repository data, reports, and tool results remain local.
| phase | who runs it | what happens |
|---|---|---|
| A — Harvest | aggregate-enabled installs | signed pinned-eval scores + variant fingerprints accumulate in D1 |
| B — Propose | the user's own provider | mine local failures → mutate the niche's current elite → run the private replay suite → sign(score, eval_set_hash, artifact_sha, provider_class) → submit |
| C — Rank | central worker (SQL, no inference) | per cell (niche × provider_class × eval_set_hash): HMAC-verify · re-check golden artifacts · K-install floor · LCB · anomaly-eject → harness_elites |
| D — Promote | central worker (automatic) | challenger beats the incumbent built-in by margin M over W days with ≥K distinct installs → flip live |
| E — Distribute | startup pull / release | graff fetches the elites for its tier; loadAgentTypes prefers a live fleet elite over the baked built-in |
Two entry points feed a niche's cell. Both tag the score with the niche so the
worker can group it into (niche × provider_class × eval_set_hash). Aggregate
mode can establish fitness without content. A promotable genome row is created
only when the exact persona text has separately passed template consent:
-
Harness eval loop. Name the role the eval optimizes:
graff --eval ./score.sh --until 95 --niche reviewerOn each new best,
runEvalemitsfleet:propose(the genome) plus a niche-taggedscoreandfleet:submit. Without--nichethe score lands in the anonymous""cell, whichpullElitescan never match to a built-in, so an eval session that wants to grow a champion must name its role. -
SDK. Pass
nichetoscore()with the full persona text:h.score(persona_text, 95.0, niche="reviewer", eval_set_hash=suite_hash)
await h.score(personaText, 95, "", undefined, "reviewer");
Given the full text plus a niche,
score()first emitsfleet:proposecarrying the genome (deduped byprompt_sha), then the niche-tagged score.
Step 0's HMAC proves a score came from a real graff build and wasn't
tampered. It does not prove the eval was actually run — every release ships
the same baked SCORE_KEY, so a binary holder can sign pass_rate=1.0 with
the right eval_set_hash without running anything. Over a million honest
traces a few liars wash out; a coordinated adversary does not. Since there is
no human gate, the backend earns trust with a defense stack that needs no
central inference:
- Artifact-bound scores. The propose step submits the replay judge's deterministic command outputs (never task content). For golden-substring tasks the Worker re-verifies in plain JS — a claimed pass whose artifact lacks the golden string is dropped. "Signed" → "shown."
- Sybil floor + per-install caps. A cell promotes only with ≥K distinct
install_ids; one actor can't manufacture consensus. Scale is what makes a large K affordable. - Lower-confidence-bound, not mean. Rank by the LCB of pinned pass-rate so a 1.0 from 3 installs loses to a 0.95 from 4,000.
- Distributional anomaly ejection. Drop install-clusters whose score distribution deviates from the fleet for the same cell.
- Reversible by construction. What ships is a prompt — auditable and revertable. A "last N champions, one-tap revert" changelog is kept even though selection is automatic.
harness_scoresgainsprovider_class,artifact_sha,eval_set_hash(the signed fields, now persisted so ranking can group by them).- New
harness_elites(niche, provider_class, eval_set_hash, prompt_sha, prompt_text, lcb, mean_score, n_installs, n_scores, live, promoted_at)— one row per grid cell;live=1is the shipped champion. - View
harness_fleet_fitnessrolls scores up per cell with a distinct-install count (the Sybil-floor input).
POST /v1/logs(existing) — score ingest now readsprovider_class,artifact_sha,eval_set_hashand persists them; afleetevent body is recognized for the propose/elite-pull signals below.GET /v1/elites?provider_class=<tier>&eval_set_hash=<hex>(new) — returns the live champions for a tier:[{niche, prompt_sha, lcb, n_installs, ...}]. graff calls this at startup for its own tier.
Derived from the running model's models.dev metadata that
graff already carries — frontier / mid / small (by capability tier;
exact thresholds are a TODO). The harness tags its own provider_class on every
score and asks /v1/elites for that tier. A small-model user literally gets
personas evolved on small models.
The fleet loop is observable end-to-end without reading any local file. Every
signal is an OTLP log record POSTed to the collector, tagged
client.name = harness | sdk-ts | sdk-py so CLI / TS / Python contributions are
distinguishable. The SDK emitters live in sdk/generate.py (templated into
both clients, since the SDKs are generated):
| body | kind | emitted by | attrs | meaning |
|---|---|---|---|---|
score |
— | harness (on h.score()) |
prompt_sha, value, parent_sha, provider_class, artifact_sha, eval_set_hash, niche, sig |
a signed fitness write-back (the gradient) |
fleet |
propose |
harness · SDK driver | niche, prompt_sha, parent_sha, provider_class; consented CLI proposals may add prompt_text |
a variant was generated/mutated |
fleet |
submit |
harness · SDK driver | niche, prompt_sha, provider_class, eval_set_hash |
a scored variant was submitted to the fleet |
fleet |
elite_pull |
SDK driver | provider_class, eval_set_hash, n_elites |
current elites fetched from /v1/elites |
fleet |
promote |
worker | niche, provider_class, prompt_sha, lcb, n_installs |
the backend flipped a new champion live |
GET /v1/stats gains fleet: { proposes, submits, elite_pulls, promotes, live_cells }
so a dashboard confirms the federated loop is running (variants proposed →
scores submitted → cells promoted) per client, exactly as prompt_variants /
scores_recorded did for the local loop.
SDK surface (both clients): score(promptOrSha, value, …, niche) carries the
signed fields and can emit prompt-free fleet:propose/fleet:submit in
aggregate-or-higher mode. The generated SDKs have no interactive artifact
review UI, so their final emitter strips prompt_text even in template mode.
pullElites(providerClass) / pull_elites(...) is gated by the same learning
mode and the per-instance telemetry/fleet controls. Raw error detail is never
serialized; event kinds and provider classes are allowlisted, while unexpected
free-form labels are reduced to bounded fingerprints. A future SDK content API
needs an explicit preview/consent primitive.
- Scaffolded now: the migration, the
harness_elitestable + fleet-fitness view, theGET /v1/elitesendpoint, score-ingest capture of the signed fields, the SDKfleetsignal emitters, and — as of this change — the harness-sidefleetemitters too:Telemetry.fleetEvent(bodyfleet, matching the SDK_fleet_signal),fleet:proposeat the variant spawn (runSub) and on each eval-loop best (runEval, gated on--niche), with genome text admitted only after exact CLI template consent; the niche-taggedfleet:submitat thescorewrite-back, aproviderClass()tier tag, and the graff-side startup pull (pullElites→fleet:elite_pull, withloadAgentTypes/builtins deferring to a live champion's prompt). Verified live: a harness session moved/v1/statssubmitsandelite_pulls. - TODO (worker-only, not in this repo): the LCB + anomaly-ejection in C (the
scaffold ranks by mean with a hard K-install floor), the artifact
re-verification for golden tasks, and the auto-promote margin/window rule in D
(so
promotes/live_cellsmove). The harness side is now complete: thefleetemitters, a curated-table + models.dev-priceproviderClass(), the startup elite-pull, the learning-privacy gate, and theGRAFF_FLEETenv //fleetmaster kill switch.