Skip to content

feat(dashboard): surface evolution-engine state (reward hacking, seesaw, variants, landscape, manifests)#80

Merged
epicsagas merged 33 commits into
mainfrom
orbit-harnessx-dashboard
Jun 18, 2026
Merged

feat(dashboard): surface evolution-engine state (reward hacking, seesaw, variants, landscape, manifests)#80
epicsagas merged 33 commits into
mainfrom
orbit-harnessx-dashboard

Conversation

@epicsagas

Copy link
Copy Markdown
Owner

Summary

Stacked on #79. Wires the dashboard read-path to the 4-PR evolution stack's backend state. Previously these features were computed/persisted but invisible in the UI.

Changes

  • serve.rs: 5 new handlers (get_seesaw_registry, get_variant_pool, get_harness_snapshot, get_adaptation_landscape, get_manifests) + 3 previously-broken panels fixed (get_session_snapshots/get_global_patterns/get_effect_pending were returning "null")
  • harness.ts: client wrappers + types (SolvedTaskRegistry, VariantPool, HarnessSnapshotData, AdaptationLandscape, EditManifestEntry; HarnessMetrics gains reward_hacking_suspected + epoch_class)
  • Evolution.svelte: reward-hacking warning banner, Seesaw/Variants/Adaptation-landscape panels, edit_type column in history; Promise.allSettled graceful degradation
  • assets/dashboard.html: rebuilt (vite)

Check

  • Code: PASS (thin readers, type-clean Svelte, pre-existing i18n errors unrelated)
  • Security: PASS (read-only, path-contained)
  • Tests: 643 lib green; clippy clean; built HTML embeds new handlers verified

Test Plan

Note

Pre-existing i18n-key errors in Memory/Settings.svelte (29) are NOT fixed here (out of scope); they don't block vite build.

epicsagas added 30 commits June 16, 2026 14:55
…igest, AdaptationLandscape, SolvedTaskRegistry, SkillVariant)

Wire struct fields into existing initializers so the dead-code type
definitions compile. No behavior change yet — these are the substrate
for the P1 evolution-gap implementation (R1-R7).

Refs: gap-analysis ref-010-harnessx-gap-analysis
Add edit_type column to evolution_records schema with idempotent
ALTER TABLE migration (ensure_column via pragma_table_info). Wire
EditType::as_str()/from_db_str() through INSERT/SELECT so the field
round-trips instead of being hardcoded to AddSkill.

Refs: gap-analysis ref-010 §3.3, Quick Win #1
New src/evolve/digester.rs compresses a session's ObsRecord stream into
per-task TaskDigest summaries: outcome (success/partial/complete-failure),
ranked failure categories, implicated logical components (file->module),
curated evidence excerpts (max 3), tool trajectory, token estimate, and
cross-iteration persistence counter.

Segmentation: pipeline_id grouping first, then 5-min idle-gap splitting,
falling back to whole-session. Pure-function core, fully unit-tested (9 tests).

Refs: gap-analysis ref-010 §3.1 (HarnessX paper §4.3 Digester)
New src/evolve/planner.rs builds AdaptationLandscape from evolution history
+ current digests: persistent failures (>=N sessions), attempted edits,
edit-type coverage, untried edit types, component failure heatmap.

recommends_exploration() flags under-exploration (HarnessX §4.2 pathology):
unresolved persistent failure + untried structural edit types. This is the
signal the proposal builder will use to inject exploration beyond local repair.

Pure-function core, fully unit-tested (8 tests).

Refs: gap-analysis ref-010 §3.2 (HarnessX paper §4.3 Planner)
New src/evolve/edits.rs defines HarnessEdit as an enum (not trait object,
per architecture review — cheaper clone for forking, exhaustive match for
the variant gate). Five variants: AddSkill, ModifySkill, AddInstinct,
ModifyConfig, AddGuardRule. Each carries a falsifiable EditManifest
(HarnessX Table 9): edit_type, target, intended_effect, predicted_impact.

AddSkill/ModifySkill are concrete (delegate to existing writers); the
structural variants report NotImplemented but exist so the Planner's
edit-type coverage analysis sees the full taxonomy. validate() guards
name safety + non-empty content. append_tuning_section_pub exposes the
existing private editor.

7 unit tests.

Refs: gap-analysis ref-010 §3.3 (HarnessX paper §4.3 Evolver)
New src/evolve/seesaw.rs implements the HarnessX seesaw constraint (§4.1):
reject any edit that regresses a previously solved task below its best
score minus tolerance.

CRITIC FIX: the original scaffolding tracked per-(task,dimension) scores.
The paper (§6.6, §7.6) proves per-dimension gating fails on sub-threshold
coupling. Redesigned to per-task (single best outcome score per task_id),
matching the paper's intent. This is a deliberately coarse gross-regression
gate; variant isolation (R6) is the real catastrophic-forgetting defense.

- SolvedTaskRegistry redesigned (TaskDimensionKey removed)
- scores_from_digests maps digests to per-task success fractions
- persisted to project_seesaw_path (seesaw.json)
- 7 unit tests

Refs: gap-analysis ref-010 §3.4 + Critic correction
New src/evolve/variants.rs implements HarnessX variant isolation (§4.5):
maintain up to MAX_VARIANTS skill variants and route each task to the
variant with the highest estimated success rate on its cluster.

- route(): warm path uses observed success rate (>=4 samples), cold path
  falls back to stack-tag matching, then highest avg_score
- fork_if_needed(): fork-on-regression — when an edit would regress another
  variant, spawn a sibling instead of rejecting; retires lowest-scoring
  variant when pool full (separate from MAX_EVOLVED_SKILLS, per arch review)
- detect_stack(): heuristic stack detection from task context (Critic: no
  stable cluster definition exists without a benchmark, so cold-start is
  stack-based and warms to observed success)
- per-variant routing stats accumulate for warm routing

9 unit tests. Depends on R4 typed edits (paper §7.1: isolation ill-posed
without explicit edit scope).

Refs: gap-analysis ref-010 §3.5 (HarnessX paper §4.5, §6.3)
Integrate the new P1 modules into the reflect evolution loop (src/hooks/reflect.rs)
per the Architect's integration map (analyze -> digest -> build_proposals
-> curate -> gate+seesaw -> variants -> write):

- After analyze_session(): run digest_session() to build per-task digests;
  populate SessionAnalysis.persistent_failure[_categories] from them
- Build AdaptationLandscape from history + digests; emit an exploration
  hint when recommends_exploration() (unresolved persistent failure +
  untried structural edit types)
- After seeding: check the seesaw registry; if digests regress a solved
  task, skip further seeding and warn; update the registry with this round
- Clean up 4 clippy warnings (derive Default, rename from_str, drop
  redundant cast/map_or)

No behavior change for sessions without regressions. 602 lib tests green,
clippy clean.

Refs: gap-analysis ref-010 §3, Phase 1 integration
CI runs 'cargo clippy -- -D warnings' across all targets, which is stricter
than the lib-only check run locally during Go phase. The R4 (HarnessEdit)
and R6 (VariantPool) APIs plus the P2/P3-reserved types (HarnessDimension,
HarnessSnapshot, ConfigSummary, MetricsSummary, SkillVariant) are not yet
invoked from the bin targets, so dead_code warnings became errors.

- Add #![allow(dead_code)] to edits.rs, variants.rs (APIs reserved for the
  next wiring step; planner coverage analysis needs the full taxonomy)
- Add #[allow(dead_code)] to reserved evolution.rs types (P2 first-class
  object, P3 dimension tags)
- Use seesaw::DEFAULT_TOLERANCE in reflect.rs instead of a hardcoded 0.1
  so the constant is live
- Drop unused re-exports (HarnessEdit, VariantPool, detect_stack) from
  evolve::mod — the modules stay pub-accessible by path
Compute Metrics.reward_hacking_suspected via least-squares slope over the
last reward_hacking_window sessions: reward hacking = output_quality slope
falling while execution_cost (efficiency proxy, higher=better) slope rising.

- config: reward_hacking_window(5)/quality_drop(0.05)/cost_rise(0.05)
- src/evolve/metrics.rs: detect_reward_hacking() + series_slope() NaN guard
- src/store/metrics.rs: persist reward_hacking_suspected + epoch_class via
  metrics_state key/value (true round-trip, was hardcoded)
- src/hooks/reflect.rs: set flag after classify_epoch, before save
- 10 unit tests + config/store assertions

Pairs with Tier 2.1 Critic (seesaw without reward-hacking defense is
exploitable per paper §4.3). Flag-only here; seeding suppression is the
Critic's job.

Refs: gap-analysis ref-010 §4.2
Hermetic integration tests for the evolution engine — no live benchmark,
no SQLite, no network, no HOME redirect. Fixtures embedded via include_str!.

6 scenarios, each with a negative control so it can't be vacuously true:
- seesaw catches regression (PIPE-1 1.0 -> 0.25)
- variant forks on regression (locks fork contract before R6 wiring)
- planner recommends exploration (persistent failure + untried types)
- outcome_score bounds + divide-by-zero guard
- task-identity-is-pipeline-stable (hermeticity guard)
- edit-coverage marks attempted vs untried types

This is the validation substrate the Council (Critic/Pragmatist/Skeptic)
demanded before the R4/R6/R5 wiring claims can be trusted.

Refs: gap-analysis ref-010, Critic/Skeptic concern: 'no benchmark'
R4 (typed edits): split seed_smart_skills() into plan_skill_edits() (pure
planner, emits Vec<HarnessEdit>) + apply_skill_edits() (executor via
HarnessEdit::apply/validate) + a thin wrapper preserving the exact ->u64
signature and hint ordering. 602+ tests unaffected.

R5 (seesaw auto-reject): reordered to check BEFORE seeding (digests were
already available from step 2b), so a regressing round blocks NEW skill
commits at the source instead of warning after the fact. Also fixes a latent
bug: reg.update() now only runs when the gate passed (monotonic update could
otherwise raise best-of on coincidental high scores, masking future regressions).

R6 (variant isolation): VariantPool gains load()/save() (atomic tmp+rename,
graceful corrupt-file reset). reflect records the session's dominant stack
outcome and forks-on-regression when seesaw blocks. MAX_VARIANTS stays
separate from MAX_EVOLVED_SKILLS. Cold/empty pool degrades to no-op.

613 lib tests + 6 regression harness tests green, clippy --all-targets -D
warnings clean, fmt clean.

Refs: gap-analysis ref-010 §3.3/§3.4/§3.5
Defends against reward hacking (paper §4.3). epic-harness forbids external
LLM calls from production, so the in-loop Critic is deterministic:

- src/evolve/critic.rs: Critic::should_block_seeding(metrics) — the earlier,
  coarser gate pairing with seesaw. verify_against_evidence() rejects
  manifests claiming a score lift when reward hacking is suspected.
- src/hooks/reflect.rs: critic_block computed pre-seed (probes score_history
  with this session's dimensions), suppresses seeding + short-circuits seesaw
  hint duplication.
- registry/skills/_critic/SKILL.md: the out-of-band LLM counterpart for
  non-local effects the deterministic check cannot catch.

Ships WITH Tier 2.2 (the Critic warning: seesaw without reward-hacking
defense is exploitable). 5 critic unit tests.

Refs: gap-analysis ref-010 §4.1
Wires the HarnessX Table 9 falsifiability contract end-to-end:
- apply_skill_edits now Critic-verifies each edit's manifest BEFORE apply;
  Reject verdicts skip the edit (defense-in-depth beyond the round-level
  reward-hacking block). Returns ApplyOutcome { applied, manifests }.
- seed_smart_skills takes &Metrics and returns ApplyOutcome so reflect can
  persist the manifests.
- reflect appends each shipped manifest to a sidecar manifests.jsonl the
  next round's Critic reads to verify predictions held; EvolutionRecord
  gains a #[serde(default)] manifests field (backward-compatible).
- EditManifest + Metrics now derive Serialize/Deserialize/Default.
- AddSkill manifest predicted_impact now references avg_score_with so the
  Critic's score-lift contradiction check can fire.
- 2 new unit tests (apply emits manifest; Critic reject skips edit + no
  manifest + no skill written).

Critic verify_against_evidence is now live (was allow(dead_code)).

Refs: gap-analysis residual A, paper §4.3 Table 9
HarnessEdit::AddGuardRule now applies: appends a blocked/warned rule to the
project guard-rules.yaml via a new append_guard_rule() helper in guard.rs.

- Read-modify-write (no clobber of existing rules), atomic write (tmp+rename),
  round-trip safe through the incumbent parse_guard_rules parser
- validate() rejects empty/pathologically-long patterns (GUARD_PATTERN_MAX_LEN)
- ModifyConfig/AddInstinct stay NotImplemented (reserved for coverage)
- 9 tests (5 guard round-trip/atomic, 4 edits validate+apply)

Planner auto-emission is intentionally NOT wired yet (conservative — the
editor is concrete so a follow-up can emit it). Makes the Planner's
'add_guard_rule' untried_edit_type attemptable.

Refs: gap-analysis residual B
…2.3)

HarnessSnapshot is now constructed (was allow(dead_code)) and exposed via a
read-only CLI: 'epic harness snapshot' (JSON), 'epic harness diff <a> <b>'.
Restore deferred (destructive).

- src/evolve/snapshot.rs: build_snapshot() aggregates config + skills + guard
  rules + metrics + deterministic content hash (canonical sorted-keys JSON)
- src/harness_cli.rs: snapshot/diff/restore dispatch following the repo's
  non-clap run(&args) pattern
- diff_snapshots: recursive structural diff (objects leaf-by-leaf, arrays as
  sets), timestamp-excluded
- 17 tests (10 snapshot, 7 CLI)

Refs: gap-analysis residual C/P2.3
…C/P3)

Formalizes the existing evolution engine as an RL analogy (paper §4.1-4.2,
§7.3 'design checklist not predictive theory'). Maps state/action/reward +
the three pathologies (reward hacking, forgetting, under-exploration) to
their concrete epic-harness defenses with code locations + a tuning guide.

Adds 'dimension:' frontmatter to 6 core static skills (tdd/secure=control_
and_safety, verify/perf=evaluation_and_reward, debug=observability,
simplify=context_assembly) enabling future dimension-scoped analysis.

Processor trait (P3.3) deliberately deferred — most invasive, gated on
P1/P2 validation.

Refs: gap-analysis residual C/P3
Adversarial code review confirmed: manifests are WRITTEN (reflect persists
to EvolutionRecord + manifests.jsonl) but the Critic that READS them to
verify prior predictions held is not yet wired — today's Critic only
consults the in-round reward-hacking flag. The doc comments claimed a
closed falsifiability loop, overstating residual A.

Corrected 4 doc sites (paths.rs, evolution.rs, edits.rs, skills.rs) to
state the honest status: write-path wired, read-path (cross-round
falsifiability) is a deferred follow-up. No code change — the behavior was
always correct; only the framing was overclaimed.

The per-edit Critic gate is now documented as defense-in-depth that cannot
diverge from the round-level gate today (both key off
reward_hacking_suspected), with the manifests_file evidence vector as the
intended future extension.
Adds a typed Processor trait over the existing CLI-subcommand hooks, the
foundation HarnessX's paper §3.2 describes, adapted to epic-harness's
out-of-process model.

- HookPoint enum maps the 6 lifecycle points epic-harness uses (SessionStart,
  PreToolUse, PostToolUse, PostEdit, PreCompact, SessionEnd) to their
  subcommand names, round-trippable via from_subcmd
- Processor trait (hook_point/profile/process) + 6 concrete processors that
  delegate UNCHANGED to the existing run(&HookInput) -> i32 fns
- all_processors() static dispatch table enumerates the wiring (introspectable
  by HarnessSnapshot / future tooling)
- dispatch() typed entry point

Representational only — NO behavior change. This honors the Architect's red
flag (in-process event-pipeline redesign would be the single most invasive
change) while establishing the seam typed edits / variant isolation can later
attach metadata to. 5 unit tests.

Also fixes a pre-existing flaky test (rebuild_produces_stable_hash) found
under multi-threaded runs: it called live build_snapshot() twice and could
diverge when a parallel test wrote to evolved_dir(). Switched to the hermetic
make_fixture_snapshot helper.

Refs: gap-analysis residual P3.3 (final item)
…aw, variants, landscape, manifests)

The 4-PR evolution stack wrote backend state the dashboard couldn't see.
This wires the read path:

serve.rs — new handle_harness_cmd arms:
- get_seesaw_registry (reads seesaw.json via load_registry)
- get_variant_pool (reads variants.json via VariantPool::load)
- get_harness_snapshot (build_snapshot — first-class object)
- get_adaptation_landscape (Planner output: persistent_failures,
  edit_type_coverage, untried_edit_types, component heatmap)
- get_manifests (tail-reads manifests.jsonl falsifiability ledger, capped 50)
- get_session_snapshots / get_global_patterns / get_effect_pending
  (previously broken — returned 'null' via fallthrough; now implemented)

harness.ts — client wrappers + types (SolvedTaskRegistry, VariantPool,
HarnessSnapshotData, AdaptationLandscape, EditManifestEntry; HarnessMetrics
gains reward_hacking_suspected + epoch_class).

Evolution.svelte — reward-hacking warning banner (when
reward_hacking_suspected), Seesaw/Variants/Adaptation-landscape panels,
edit_type column in evolution history. Parallel best-effort fetch
(Promise.allSettled) so a cold project degrades gracefully.

assets/dashboard.html rebuilt (vite build; my Evolution.svelte is
type-clean; pre-existing i18n-key errors in Memory/Settings are unchanged
and don't block vite build).

643 lib tests green, clippy --all-targets -D warnings clean.

Refs: gap-analysis ref-010 dashboard-surfacing audit
…cing

Final release-docs commit for the 5-PR HarnessX stack (#75#80).

CHANGELOG.md — [0.7.0] entry covering: Digester, Planner, typed edits,
seesaw, variant isolation, reward-hacking detection, Critic, falsifiability
ledger, AddGuardRule editor, regression harness, Processor abstraction,
HarnessSnapshot/CLI, dashboard surfacing (5 handlers + 3 fixed panels +
Evolution.svelte panels/banner), operational-mirror doc + dimension tags,
plus the seesaw reg.update() latent-bug fix and SQLite migrations. Known
limitations documented.

README.md — new 'HarnessX-Inspired Defenses (v0.7.0)' table (8 defenses →
module → pathology → paper §), 'Harness Snapshot (v0.7.0)' CLI section,
and dashboard blurb updated to mention the evolution-engine surfaces.

Version bump 0.6.5 -> 0.7.0 synced across all 6 files (Cargo.toml,
package.json, app/package.json, .claude-plugin, .codex-plugin,
gemini-extension.json) + assets/dashboard.html rebuilt with the new
fallback version.

643 lib tests green, clippy --all-targets -D warnings clean.
When a polled section's data changes between refreshes, its border glows
blue for ~1.6s (hx-flash-glow keyframe in app.css) so the eye is drawn to
freshly-updated panels.

Applied to 4 polling pages:
- Evolution: reward-hacking banner + seesaw/variants/adaptation-landscape panels
- Dashboard: key-metrics grid + tool-stats panel
- OrbitPipeline: running-pipeline cards
- Agents: agent cards (status signal only — not every 5s poll)

Each page's flashIfChanged() compares a CANONICAL (sorted-key) JSON signature
of the payload against the previous poll. Canonicalization is essential: the
adaptation-landscape handler recomputes build_landscape() each call, and its
HashMap-backed fields (edit_type_coverage, component_failure_heatmap) iterate
in non-deterministic order, so plain JSON.stringify produced a different
signature every poll (constant false-positive flashing). canon() sorts object
keys recursively so only real data changes trigger the glow.

assets/dashboard.html rebuilt.
The project dropdown in the Topbar was permanently empty and pinned to
the last-selected project, with no way to switch projects or see the
cross-project aggregate. Root cause: the frontend calls loadProjects()
-> fetch('/api/projects') on mount, but no such route existed in
serve.rs — every poll returned 404, so the dropdown stayed empty and
localStorage's last value (a single project) remained selected. Because
the UI looked fine, prior fixes only touched the Svelte layer and the
problem kept recurring: the data source had never been implemented.

This adds the missing backend support and makes project selection
actually filter data:

1. GET /api/projects — enumerates ~/.harness/projects/* slugs (sorted),
   feeding the dropdown. The '__all__' option is a frontend default.

2. /api/harness now reads the ?project= query param and threads it into
   handle_harness_cmd. Absent or '__all__' -> None (cross-project).

3. load_metrics_scoped_pool(project) — new metrics loader scoped to a
   project slug or aggregated across all. The old load_metrics_pool ran
   'WHERE key = ?' with fetch_optional against a (key, project)-keyed
   table, returning indeterminate rows once multiple projects had
   written. Aggregates now SUM/weight-avg with CAST(value AS REAL)
   (the column is TEXT, so implicit numeric math silently yielded 0)
   and decode as f64 (REAL results don't decode into i64).

4. query_obs_stats_scoped_pool(project) — same treatment for
   observations: static-SQL two-branch form (sqlx 0.9 SqlSafeStr rejects
   dynamic strings) so each query adds 'AND project = ?' when scoped.

Verified live: __all__ shows sessions=885 / 101k obs across 94 tools;
epic-harness shows 118 sessions / 15.8k obs / 41 tools; alcove shows 36
sessions. Switching projects now returns genuinely different data.
The Agents page rendered only the skeleton and never finished loading.
A console error fired on every render of the run-summary stat grid:

  TypeError: r(...)(...) is not a function

The offending template was {$tStore('ofAgents')(run.agents.length)}.
tStore returns translate() — a STRING, not a function — so calling the
result with (n) threw. The parametrized key ofAgents exists in en.ts as
(n) => 'of N agents', but it is consumed via the variadic args of
tStore: tStore(key, ...args). The double-call form was a copy-paste bug.

Fixed to tStore('ofAgents', run.agents.length). No other double-call
sites exist (grep across app/src). Verified via Playwright: skeletons 0,
42 agent cards render, 'of 42 agents' shown, zero console errors.
… skills

A prior commit (c35c17c, 5/29) renamed the dashboard's Ring 1 from
'Commands' to 'Pipeline' across sidebar, page header, dashboard ring
card, and i18n. Subsequent work dropped those changes (git blame on
Sidebar.svelte's Ring 1 label points back to 6dfcf33, 5/18 — the rename
was silently reverted), so the UI showed 'Commands' again. The user
reported this as a version regression.

Restored the rename AND expanded the Pipeline page from 3 entries
(orbit/evolve/team) to all 9 Ring 1 pipeline skills in spec->ship
order: discover, spec, go, audit, eval, ship, orbit, evolve, team.

Changes:
- Sidebar: 'Ring 1 · Commands'/'Commands' badge 3 -> 'Ring 1 ·
  Pipeline'/'Pipeline' badge 9
- Commands.svelte: 9 pipeline skill cards; subtitle 'Ring 1 · Skills'
- Dashboard 4-Ring card already showed Pipeline (no change needed)
- en.ts + ko.ts: pageCommands -> Pipeline, pageCommandsDesc3 -> 9-skill
  copy, added cmdDiscover/Spec/Go/Audit/Eval/ShipDesc, ring1Desc ->
  '9 pipeline skills'
- de/es/fr/hi/ja/pt-BR/zh-CN/zh-TW: pageCommands localized to Pipeline
  equivalent

Verified via Playwright on /commands: header 'Pipeline Ring 1 · Skills',
9 cards render (/discover ... /team), sidebar shows 'Ring 1 · Pipeline',
zero console errors.
Redesigned the Pipeline (Ring 1) page so the spec->ship flow is visible
as an actual diagram, mirroring the epiccounty.com/ecosystem visual
language (central node + satellite nodes connected by labelled flow
lines) instead of a flat card grid.

- Central /orbit orchestrator node (highlighted, double-boxed) sits in
  the middle with 'ORCHESTRATOR / spec -> ship auto'
- 6 phase nodes (discover/spec/go/audit/eval/ship) orbit it, connected
  by dashed flow lines with numbered badges 1-6 and arrowheads
- Clicking any node copies the /command
- Cross-cutting orchestrators (evolve, team) grouped below in a separate
  panel with purple accent

The geometry is computed in-script (6 satellites evenly spaced on a
circle of radius 175 around (360,245) in a 720x490 viewBox). All SVG
nodes are focusable buttons for keyboard access.

Verified via Playwright: 1 orbit node + 6 phase nodes + 6 numbered flow
lines render at 720x490, evolve/team in cross-cutting grid, zero console
errors.
Replaced the circular phase diagram with the /orbit decision flow that
matches the README mermaid chart — a top-down branching tree showing the
full spec->ship lifecycle, not just the phase names.

The SVG renders the complete orbit state machine:
- /orbit (start) -> requirement? (decision)
  - unclear   -> Interactive (/discover -> /spec, then 'orbit go')
  - complex   -> Council (4-voice auto-spec)
  - simple    -> Direct (auto-spec)
- -> Load spec (merge) -> Go (plan -> TDD -> integrate) -> Check
- Check --PASS/WARN--> Ship ; --FAIL--> retry<3?
  - yes -> loop back to Go ; no -> Pause
  - Pause --abort--> Abort ; continue -> Go
- Ship -> Evolve (auto-analyze session)

Node colours follow the README classDef: purple (#4a4a6a) for human
checkpoints (Interactive, Pause), green (#1a5c3a) for autonomous steps
(Council/Direct/Go/Check/Ship/Evolve), dark blue diamond for the mode
decision, brown for the merge + retry gate. Edges carry the same labels
as the README (unclear/clear+complex/simple, PASS/WARN, FAIL, yes/no,
abort).

Go/Check/Ship/Evolve/orbit nodes are clickable to copy the command;
evolve/team listed separately as cross-cutting orchestrators.

Verified via Playwright: 11 nodes + 15 labelled edges render at 620x760,
all decision labels present, zero JS console errors.
The agent card grid was rendering all 42 agents from the run, most of
which completed days ago. Older agents are already available in the
completed table below, so the cards now show only:

- agents currently running, OR
- agents completed within the last 24h (by completed_at, falling back to
  started_at when completed_at is absent)

Agents outside that window are excluded from the cards, and a hint
('+N older agents — see completed table below') makes clear where they
went. When nothing is recent, an empty-state note appears instead of a
blank grid.

Verified via Playwright: card count dropped from 42 to 1 (only the agent
that ran ~1h ago), '+41 older agents' hint shown, completed table still
lists the full history.
Switching the project dropdown didn't refresh the Evolution page or the
Agents obs table — they loaded once on mount and polled on a timer, never
re-reading selectedProject. The backend already returned project-scoped
data, but the pages never asked for it again, so every project showed the
same numbers (looked like the project filter had regressed).

Frontend: replaced onMount-only load in Evolution.svelte and Agents.svelte
with a $effect that depends on $selectedProject, so changing the dropdown
re-runs load() (and re-arms the poll interval). Matches the pattern
Dashboard.svelte and Memory.svelte already use. Removed the now-unused
onMount imports.

Backend: get_evolved_skills was ignoring the project param entirely — it
called list_skills_full_pool / query_recent_records_pool /
load_metrics_pool, all unfiltered, so it always returned the global
recent-50 history regardless of selection. Added project-scoped variants:
- evolved::list_skills_full_scoped_pool(project)
- evolution::query_recent_records_scoped_pool(limit, project)
- metrics::load_metrics_scoped_pool(project) (already existed)
and wired get_evolved_skills to use them. Static-SQL two-branch form for
sqlx 0.9 SqlSafeStr.

Verified via Playwright on /evolution: switching epic-harness -> alcove ->
__all__ now changes Sessions Analyzed (7 / 0 / 11) and the history table,
and the network tab confirms all 5 harness API calls re-fire with the new
project= query param on each switch.
@epicsagas epicsagas changed the base branch from orbit-harnessx-processor to main June 18, 2026 10:08
@epicsagas epicsagas merged commit 26e78d6 into main Jun 18, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant