Summary
The DGM persona-evolution pipeline is architecturally sound and running end-to-end, but several metadata gaps prevent most scores from forming promotable fitness cells. This issue documents each gap, why it matters, and the specific fix needed — all within this repository.
Gap 1: scoreVariants drops the niche (workflow tournament scores are uncelled)
Location
src/subagent.zig line 261 — the prov string sent with scoreEvent:
const prov = std.fmt.bufPrint(&provbuf, "{s}\t{s}\t{s}\t{s}\t{s}", .{ "", "", esh, pclass, "" }) catch "";
The last field is "" — but niches[i] is available at the call site (line 263 passes it to fleetEvent on the very next line).
Impact
Workflow tournaments that judge multiple prompt variants produce scores that land in an anonymous cell. The backend cannot determine which built-in persona (reviewer, researcher, implementer, skeptic) they should replace. These scores are permanently invisible to promotion.
Fix
Change line 261 to pass niches[i]:
const prov = std.fmt.bufPrint(&provbuf, "{s}\t{s}\t{s}\t{s}\t{s}", .{ "", "", esh, pclass, niches[i] }) catch "";
Gap 2: Niche and provider_class are not part of the signed score envelope
Location
src/scoring.zig line 95 — the canonical message signed by signScore:
return std.fmt.bufPrint(buf, "v1\n{s}\n{s}\n{d:.6}\n{s}\n{s}\n{s}\n{s}", .{
prompt_sha, parent_sha, score, run_id, judge_id, artifact_sha, eval_set_hash,
}) catch null;
Niche and provider_class are transported but not HMAC-signed. The code at src/mainloop.zig line 295-296 acknowledges this explicitly:
// + provider_class, niche (unsigned transport)
Impact
A score signed for one niche/tier could be replayed as a different niche/tier by mutating the unsigned transport fields before ingestion.
Fix
Bump to v2 with niche and provider_class in the signed message:
return std.fmt.bufPrint(buf, "v2\n{s}\n{s}\n{d:.6}\n{s}\n{s}\n{s}\n{s}\n{s}\n{s}", .{
prompt_sha, parent_sha, score, run_id, judge_id, artifact_sha, eval_set_hash,
niche, provider_class,
}) catch null;
The backend worker needs a coordinated update to verify v2 signatures against the ingested niche/provider_class, while still accepting v1 during a transition window.
Gap 3: Genome text is not validated against its claimed fingerprint
Location
src/telemetry.zig line 206 — fleetEvent("propose", ...) sends both prompt_text and prompt_sha, but nothing verifies that SHA256(prompt_text) actually produces the claimed fingerprint.
Impact
A proposal could carry mismatched text and fingerprint. If later promoted, clients would pull text that doesn't match the fingerprint that earned the scores.
Fix
Add a debug assertion at the proposal site (e.g., in runSub line 101-108) that promptFingerprint(so) == child_fp before sending. The backend should also independently recompute and reject mismatches.
Gap 4: Score scale is ambiguous (0–1 vs 0–100)
Location
The scoring pipeline accepts any numeric value. parseEvalScore in repl_glue.zig parses both scales. The backend clamps means into [0, 1], so percentage scores (43, 76, 98) all become 1.0 after clamping — indistinguishable from a perfect fractional score.
Impact
Cells containing mixed-scale scores produce invalid aggregate statistics and wrong champion selection.
Fix
- Canonicalize: all DGM scores must be
[0, 1].
- In
runEval and scoreVariants, normalize percentage scores (divide by 100 if > 1).
- In the backend, reject scores outside
[0, 1] rather than silently clamping.
Gap 5: scoreVariants doesn't propose the genome before scoring
Location
src/subagent.zig scoreVariants (line 252-263) calls scoreEvent and fleetEvent("submit", ...) but never calls fleetEvent("propose", ...) with the prompt text.
Compare with runEval in src/agent_compact.zig (line 110):
if (niche.len > 0) t.fleetEvent("propose", niche, genome, "", pclass, "", 0, sys);
Impact
If a variant enters scoreVariants without a prior runSub proposal, the score lands but the genome text is absent. A cell with scores but no genome text cannot produce a deployable champion.
Fix
Add a propose before submit in scoreVariants, matching the runEval pattern:
if (niches[i].len > 0) t.fleetEvent("propose", niches[i], genome, "", pclass, "", 0, overrides[i].?);
Proposals are deduplicated by fingerprint in the backend, so this is safe even if runSub already proposed it.
Gap 6: Run telemetry and score telemetry share no stable join key
Location
runEvent (in runSub, line 145) and scoreEvent (in runEval/scoreVariants//score) use different run_id sources — trajectory turn IDs vs scoring.g_run_id.
Impact
Cannot correlate operational outcomes (duration, tools, success) with evaluation fitness for the same agent execution.
Fix
Standardize on a single run_id that both runEvent and scoreEvent use for the same logical execution.
Priority
- Gap 1 — one line, biggest impact on cell formation
- Gap 4 — prevents corrupted rankings as volume grows
- Gap 3 — data integrity, cheap to add
- Gap 2 — security, needs coordinated backend change
- Gap 5 — completeness edge case
- Gap 6 — analytics, nice to have
Related files (this repo only)
src/scoring.zig — fingerprints, provider class, signing
src/subagent.zig — runSub (propose + run), scoreVariants (workflow judging)
src/agent_compact.zig — runEval (eval-driven scoring)
src/mainloop.zig — /score JSON ingestion
src/telemetry.zig — scoreEvent, fleetEvent, OTLP encoding
src/fleet.zig — pullElites, agentTypePrompt, promoteAgents
Summary
The DGM persona-evolution pipeline is architecturally sound and running end-to-end, but several metadata gaps prevent most scores from forming promotable fitness cells. This issue documents each gap, why it matters, and the specific fix needed — all within this repository.
Gap 1:
scoreVariantsdrops the niche (workflow tournament scores are uncelled)Location
src/subagent.zigline 261 — the prov string sent withscoreEvent:The last field is
""— butniches[i]is available at the call site (line 263 passes it tofleetEventon the very next line).Impact
Workflow tournaments that judge multiple prompt variants produce scores that land in an anonymous cell. The backend cannot determine which built-in persona (reviewer, researcher, implementer, skeptic) they should replace. These scores are permanently invisible to promotion.
Fix
Change line 261 to pass
niches[i]:Gap 2: Niche and provider_class are not part of the signed score envelope
Location
src/scoring.zigline 95 — the canonical message signed bysignScore:Niche and provider_class are transported but not HMAC-signed. The code at
src/mainloop.zigline 295-296 acknowledges this explicitly:Impact
A score signed for one niche/tier could be replayed as a different niche/tier by mutating the unsigned transport fields before ingestion.
Fix
Bump to
v2with niche and provider_class in the signed message:The backend worker needs a coordinated update to verify v2 signatures against the ingested niche/provider_class, while still accepting v1 during a transition window.
Gap 3: Genome text is not validated against its claimed fingerprint
Location
src/telemetry.zigline 206 —fleetEvent("propose", ...)sends bothprompt_textandprompt_sha, but nothing verifies thatSHA256(prompt_text)actually produces the claimed fingerprint.Impact
A proposal could carry mismatched text and fingerprint. If later promoted, clients would pull text that doesn't match the fingerprint that earned the scores.
Fix
Add a debug assertion at the proposal site (e.g., in
runSubline 101-108) thatpromptFingerprint(so) == child_fpbefore sending. The backend should also independently recompute and reject mismatches.Gap 4: Score scale is ambiguous (0–1 vs 0–100)
Location
The scoring pipeline accepts any numeric value.
parseEvalScoreinrepl_glue.zigparses both scales. The backend clamps means into[0, 1], so percentage scores (43, 76, 98) all become1.0after clamping — indistinguishable from a perfect fractional score.Impact
Cells containing mixed-scale scores produce invalid aggregate statistics and wrong champion selection.
Fix
[0, 1].runEvalandscoreVariants, normalize percentage scores (divide by 100 if > 1).[0, 1]rather than silently clamping.Gap 5:
scoreVariantsdoesn't propose the genome before scoringLocation
src/subagent.zigscoreVariants(line 252-263) callsscoreEventandfleetEvent("submit", ...)but never callsfleetEvent("propose", ...)with the prompt text.Compare with
runEvalinsrc/agent_compact.zig(line 110):Impact
If a variant enters
scoreVariantswithout a priorrunSubproposal, the score lands but the genome text is absent. A cell with scores but no genome text cannot produce a deployable champion.Fix
Add a propose before submit in
scoreVariants, matching therunEvalpattern:Proposals are deduplicated by fingerprint in the backend, so this is safe even if
runSubalready proposed it.Gap 6: Run telemetry and score telemetry share no stable join key
Location
runEvent(inrunSub, line 145) andscoreEvent(inrunEval/scoreVariants//score) use differentrun_idsources — trajectory turn IDs vsscoring.g_run_id.Impact
Cannot correlate operational outcomes (duration, tools, success) with evaluation fitness for the same agent execution.
Fix
Standardize on a single
run_idthat bothrunEventandscoreEventuse for the same logical execution.Priority
Related files (this repo only)
src/scoring.zig— fingerprints, provider class, signingsrc/subagent.zig—runSub(propose + run),scoreVariants(workflow judging)src/agent_compact.zig—runEval(eval-driven scoring)src/mainloop.zig—/scoreJSON ingestionsrc/telemetry.zig—scoreEvent,fleetEvent, OTLP encodingsrc/fleet.zig—pullElites,agentTypePrompt,promoteAgents