_canon_mutable_trees previously RESOLVED the registered mutable-tree
paths against the process cwd, folding the absolute checkout path into
the contract hash — the same workspace hashed differently when evolve
ran from a different directory, or after the workspace moved, and
spuriously rolled its epoch. Paths are now normalized (never
filesystem-resolved). BREAKING: every existing epoch's contract hash
moves once; the workspace auto-rolls on the next evolve (the standard
contract-change behavior). The parity CONTRACT-HASH golden is
re-captured and is now checkout-independent.
All notable changes to zicato are recorded here. Format roughly follows Keep a Changelog.
Existing workspaces whose contracts do not pin these knobs will AUTO-ROLL
their epoch on the next evolve — the flipped defaults change the
contract's canonical form, so the contract hash changes and a fresh epoch
opens (generations across epochs are not directly comparable; this is the
intended contract-roll semantics, not a migration bug). Pin the old values
in scoring.json to keep an epoch's contract byte-stable
("tournament": {"params": {"replicates": 1}},
"proposer_quality": {"best_of_n": 1},
"overfitting": {"min_board_size_for_split": 8} — see
examples/zicato_examples/target_0_convergence/scoring.json for the
canonical pinned deterministic contract).
- Per-duel
replicatesdefault 1 → 2 for gauntlet / single-elim / double-elim / swiss (racing keeps 1 — its replication is intrinsic to the escalating board slices). The gauntlet's full-A/B path (run_tournament) now honors the structure's resolved replicates: two paired board runs, per-replicate cache slots, per-entry losses averaged before the gate. A duel decided by one paired run is decided by one noise draw; two averaged runs is the cheapest hedge. - Proposer
best_of_ndefault 1 → 3 with the self-critique pass: each propose-step samples a slate of three (each slot steered by a distinct edit-class hint) and selects the best. Pinbest_of_n: 1for scripted / deterministic proposers. overfitting.min_board_size_for_splitdefault 8 → 6: boards of 6–7 entries now derive a holdout split.- The Bradley–Terry evidence gate stays OPT-IN (
promote_confidence_thresholdabsent ⇒ off) — deliberately. Measured (Tier-2 power harness): the gate blocks 100% of A/A false promotes, but on a two-contestant crowning pair its CIs separate only after an unbroken win streak of ~37 duels (mixed records never separate) — so a silent on-by-default gate with a small budget would freeze every true promotion atinconclusive, and a converging budget costs ~32×2×board fresh runs per crowning. The gate is a soundness device; decision power is bought with per-duel replication and a margin calibrated above the measured noise floor. When enabled it now reaches BOTH selection shapes: the multi-challenger driver AND the gauntlet crowning duel (previously unreachable under the default structure). - Scaffolds enable the gate explicitly:
zicato initwrites the full effective recommended contract (racing field 4 / eta 2 / board_fraction 0.4, replicates 2,promote_confidence_threshold: 0.8,promote_confidence_replicates: 32) to the operator's livescoring.json(only when absent), and the tournament builder's blank draft opens on the same contract — the operator sees and prices the gate rather than inheriting a silent freeze or silent noise. - A/A noise-floor calibration is the default soundness surface:
zicato board audit(and the opt-incalibrate_noise_floorworkspace knob) duels the champion against itself, persists the measureddelta_scalarspread on the epoch record, andevolveWARNS loudly — with amargin_below_noise_floorhealth finding — whenpromote_marginsits inside the measured noise while the evidence gate is off (measured: a naive margin below the floor promotes pure noise — 20/60 A/A promotes in the Tier-2 harness).
- Cross-epoch meta-loop ledger on the home view: the fleet of epochs and the champion lineage across them now lead the environment overview, replacing the full-width cross-epoch sparkline. Each contract change and champion-reign tick is anchored to the real generation position.
- Settings is a routed right-side drawer overlay, not a full page. Its Contract tab reuses the tournament builder's live preview, so the contract a run will use is previewed in place. Settings also carries a launcher into the standalone builder.
- The tournament builder is its own first-class view (
#/builder), reachable from the Settings launcher and from thezicato builderCLI command (which boots the dashboard focused on the builder deep-link). - Live racing hero redesigned: a metadata baseline strip, a full-width
scalar track with a rung stepper, dense champion-gate rows, and a
two-column
WHAT'S RUNNING | LIVE ACTIVITYlayout. Per-round racing models and the epoch/candidate views now build through one shared structure resolver, so non-gauntlet views render identically. - S/M/L font-size control in the typeface picker (recalibrated ladder).
- Non-racing live-hero figures go responsive / full-width.
- Per-entry views surface a continuous outcome score plus precision / recall, alongside the existing pass/fail.
- Static dashboard assets are served with an
ETagderived from the file's identity and honorIf-None-Match: an unchanged file revalidates to a bodyless304(no re-download), and editing a file changes itsETagso the browser always picks up CSS/JS edits.
- The tool-using ADK agent is now the DEFAULT proposer (in
builtin_defaultmode, bound to the auxiliary model and the read-only proposer tool registry). The skill-composed proposer is now the explicit opt-in (a configuredproposers/<name>/dir). The proposer is contract input #5: configuring a proposer dir — or editing one of its skills — rolls the epoch. - A board-anonymized, train-slice-only failure-mode feedback channel
to the proposer via the
outcome_summarizer_specoperator hook: the proposer is fed marginal failure signal computed over the train slice only, with board contents anonymized, so it can target failure modes without memorizing the board.
- Configurable racing wall-clock budgets: the racing strategy reads
matchup_budget_seconds(caps every duel's total board-unit wall-clock) andfinal_rung_budget_seconds(overrides it for the final rung; defaults to the matchup budget). Both are opt-in — unset leaves a rung uncapped. - Cost estimator uses per-structure default replicates: when
params["replicates"]is unset, the builder's cost meter reads each strategy's own_default_replicates(swiss / single-elim / double-elim = 2; gauntlet / racing = 1) from the selection layer's single source of truth, instead of assuming a flat 1, so the meter cannot under-report the schedule a structure actually runs.
- Selectable pass-rate monotonicity scope.
ScoringWeightspass_rate_monotonicity_scopeselectsper_entry(default — reject when any entry the parent passed regressed) vsaggregate(reject only when the child's overall pass-rate falls below the parent's beyond tolerance). The on/off switch remains the separatepass_rate_monotonicitybool. The scope is authored through the builder gate-config op (set_gate), surfaced in the analyzer report and the dashboard breakdown, and now carried through the subprocess-worker transport so an operator'saggregatechoice can never silently downgrade toper_entryinside a worker. - Continuous per-entry outcome scores. An expectation may return a
float in
[0, 1]recorded as a per-entry score plus precision / recall metrics, surfaced per entry in the dashboard.
per_judge_weightsnow survives the subprocess-worker transport (symmetric with the other scoring fields), so a worker scores with the operator's configured per-judge weights instead of resetting to the default.- In-run process judges now grade the real tool-call ledger rather
than the agent's narration (the
file_findabilityjudge was reading the agent's prose, not the actual tool calls), so a judgement reflects what the harness actually did, not what it claimed.
- Settled the storage design end-to-end and rewrote
docs/design/STORAGE.mdaround it. The five data kinds get five fits: runtime state and the lineage/experiment/journal records → files (one record per file, throughStorageBackend); telemetry → JSONL (goldfive's format, unchanged); generation source trees → a pluggableGenerationStore(directory snapshots today, git on the roadmap); the cross-run analytical index → a real DB (index.db, SQLite — DuckDB evaluated and deferred until scan latency is felt). The store of record stays files-canonical so concurrent subprocess runs stay lock-free and crash-isolated. - Resolved the record-level vs generation-level seam fork:
StorageBackendstays record-level (key→blob); generation source trees get a separate peer seam,GenerationStore, at theepoch/domain layer — not an extension ofStorageBackend. Forcing source-tree transactions into the record ABC would make it carry domain vocabulary and stop being an honest storage seam. zicato.epoch.genstore— theGenerationStoreprotocol plus the shippedDirectoryGenerationStore(the existing directory-snapshot mechanism, byte-for-byte).derive_generationis the generation-level transaction boundary the record seam could not express: copy the parent tree, apply the patch set all-or-nothing, return the child snapshot root. The orchestrator's snapshot / baseline-seed / patch-apply paths now route through this seam — the single site a git backend would later be substituted.epoch/record I/O migrated ontoStorageBackend(via the newzicato.epoch._storageadapter, mirroringruntime/._storage).experiment.json, the per-patch files,lineage.json, per-epochconfig.json/scoring.json, andjournal.mdare now written with the same.tmp+fsync+ rename atomicity the runtime layer already had — a crash mid-write can no longer leave a truncated record. No on-disk bytes moved; everyepoch/signature is unchanged.- Continuous indexing promoted from an add-on to the stated design: the
orchestrator's live dual-write keeps
index.dbcurrent as the loop runs;zicato reindexis the batch rebuild / repair path. The canonical-file-first ordering rule is documented as load-bearing.
zicato evolve --max-wall-clock-seconds <S>— a total wall-clock budget for the whole evolve invocation. Previously only individual board entries had awall_clock_budget_seconds; an N-entry × M-round invocation had no aggregate ceiling. The budget is enforced between rounds (the loop stops cleanly once it is spent) and within a round (a round that would overrun it is cancelled viaasyncio.wait_forand recorded as an aborted round). It applies on top of — not instead of — each entry's per-entry budget. Also reads theZICATO_MAX_WALL_CLOCK_SECONDSenvironment variable. When the loop stops on this budget, theevolvesummary says so explicitly.
- The Files view gained a mutation-site browser. Below the
generation file tree and patch set, an operator can now browse the
mutation surface — every
# zicato:mutableannotated span — and, for each site, see a line-level diff of thev0baseline content against the patched content in any generation whose patch touched that mutation id. Sites no patch has touched just show their baseline content. - New endpoints
GET /api/mutations/{epoch}(the epoch's mutation surface, each site with file, role and the generations that patched it) andGET /api/mutations/{epoch}/{mutation_id}(one site's baseline content plus the per-generation patched content for a diff). Both are deterministic and backend-neutral: they re-enumerate each generation's materialised snapshot throughenumerate_mutationsand route patch-set reads through theGenerationStoreseam, so the directory and git backends render identically. No re-apply — a generation's snapshot already IS its post-apply state.
Observability + analytics release. zicato grows a real dashboard, a SQLite analytical index, a tournament/competition view distinct from goldfive's execution view, and loop-health diagnostics. First real run against a live model landed in this cycle.
zicato-supervisorRust binary: watchdog (state-file monitoring + SIGTERM→SIGKILL escalation) + HTTP/SSE dashboard server, auto-spawned byzicato evolve.- Multi-view dashboard (vanilla HTML/CSS/JS, bundled in the binary): Overview, Tree (cross-epoch lineage graph), Tournament (the bracket), Epoch (the contract). Fragment-routed; SSE-live.
/api/epoch— the epoch's full evaluation contract (board, rubric, scoring, registered harness, mutation surface)./api/tournaments+/api/tournaments/:id— the gauntlet bracket and full per-matchup detail./api/health-report— loop-health findings.- Static assets served at document root (fixes the relative-path 404 that rendered the dashboard unstyled).
.zicato/index.db— a derived, queryable index of cross-run data (epochs / generations / experiments / patches / runs / loss_profiles / metric_counts / tournaments). Files stay canonical; the index is rebuildable viazicato reindexand dual-written live by the orchestrator. The Rust supervisor reads it via rusqlite.- Generation source trees are NOT in SQLite (git, per the roadmap); per-run event capture stays JSONL. SQLite is the analytical layer only.
- The dashboard Tournament view is a king-of-the-hill bracket: a champion lineage (winners spine) + discarded challengers, each matchup parent-vs-child over the board.
- Per-matchup detail: hypothesis, patches, per-entry A/B grid (improved/regressed/flat), scalar breakdown, gate verdict + reasoning.
zicato/tournament/detail.pyanalytics: bracket assembly, A/B grid, hypothesis ledger (proposer calibration with explicit sign+magnitude match semantics), optimization trajectory + plateau detection, mutation heat map (win-correlation), tournament cost.- Architectural split: harmonograf renders the execution view (the temporal trace of one run); the zicato dashboard owns the competition view. A per-run drill-down links the two.
- The tournament runner now scores each board unit the instant its
run(s) settle — on the same concurrency fan-out as the runs, not
batched after every board finishes. As each unit completes its
per-entry losses fold into a running partial aggregate
(
_IncrementalScorer) which is rewritten onto the liveActiveTournament(partial_parent_agg/partial_child_agg). - A finished board's score is therefore available ASAP — while the sibling boards are still running — rather than only at round end. The dashboard's Partial aggregate panel now consumes the server-computed running aggregate (with a legacy fallback), so the scalar climbs as the tournament runs instead of sitting at 0.00.
- This release folds together six dashboard / runner / proposer fixes
developed in parallel: the route-driven Files view with a
side-by-side changed-files diff; the Overview rebuilt as the
environment home (with the queued-label fix); the Epoch view's
narrative redesign; the Tournament view's harmonograf jump-off links
(
adk_session_id); incremental per-board scoring; and proposer/patch resilience (post-apply validation retry, full proposer content, surgical span apply). The partial-aggregate panel now lives in the Tournament view's hall — the Overview no longer renders the full board.
zicato/health/— detects a toothless evaluation loop: degenerate scoring (consecutive zero-Δ tournaments), non-differentiating board entries, flat drift signal, missing expectations, stalled loop.zicato healthCLI; the orchestrator writes a per-round health report, logs a loud warning on a critical finding, and stops the loop after sustained degeneracy (evolve --stop-on-degenerate).- Motivated by the first real run — v0 and v1 scored identically (1.000000); the loop produced zero optimization signal and that was only discoverable by manual inspection.
- Orchestrator writes a populated heartbeat (epoch / generation / round / phase); per-run progress is bumped on each goldfive event so dashboard run cards animate.
mutations.jsondumped per epoch so the dashboard can show the mutation surface.HarmonografSinkattached whenZICATO_HARMONOGRAF_URLis set — runs stream live to a harmonograf server; the dashboard deep-links each run to its harmonograf session.- Progressive
analysis.html— regenerated after every generation.
- pedapudi/harmonograf#292
— a
harmonograf-replaycommand ingests a zicato run'sevents.jsonlfrom disk, so any finished run is viewable in harmonograf.
- New:
TOURNAMENT.md,ANALYTICAL-INDEX.md,LOOP-HEALTH.md,RUNTIME.md,DASHBOARD.md,ROBUSTNESS.md,STORAGE.md. - Updated
ARCHITECTURE.md/CLI.md/EPOCHS-AND-JOURNALING.md.
- ~961 Python tests + the Rust supervisor suite (48 unit + 19 integration). The 5 pre-existing environment-dependent failures (goldfive editable-install drift) are unchanged.
Second alpha. Major surface expansion: drift-free objectives are first-class, the board API has a friendly Python builder, multi-objective scoring lands, regression-suite gating protects against breaking-change patches, and zicato now consumes goldfive's decision telemetry through a dedicated LLM analyzer.
MetricCountreplaces drift-only counts. Arbitrary namespaces:drift:*,cost:*,latency:*,rubric:*,output:*,schema:*.LossProfile.metric_countscarries the unified view alongsidedrift_countsfor back-compat. New first-class fields:tokens_spent,output_chars,schema_failures.HypothesisSpec.expected_metric_movementsaccepted alongsideexpected_drift_movements. Proposer schema validates either form.OutcomeRecord.metric_movementsrecords actual deltas per namespace.- New detectors:
detect_metric_frequency(namespace=...),detect_cost_outliers,detect_rubric_score_movement. The originaldetect_drift_kind_frequencyis now a thin wrapper. - Analysis renderer:
render_metric_movement_table(namespace_filter=...)with a "drift" filter producing byte-identical legacy output.
ScoringWeights.namespace_weights— per-namespace weights with a sign convention (positive = higher-is-worse; negative = higher-is-better; zero = excluded from scalar).ScoringWeights.namespace_monotonicity— per-namespace monotonicity flags. Regression on a tracked namespace hard-rejects the candidate regardless of overall scalar improvement.aggregate_namespaced_metrics+scalar_componentsin the generation aggregate.evaluate_gateadds rule 3: per-namespace monotonicity check.
zicato.tournament.regression.run_regression_suite— asyncio subprocess pytest invoker with timeout + failed-id parsing.ScoringWeights.regression_gate_enabled/regression_test_command/regression_timeout_s. When enabled, any test failure in the candidate snapshot hard-rejects regardless of other metrics.tournament --skip-regressionCLI flag.
Board+Entryprogrammatic builder.Entry(id=..., input=..., evaluate=..., budget_s=...)auto-detects the kind from arguments (turns→ multi-turn-scripted,persona→ multi-turn-emulated,adversarial_agent_spec→ synthetic-adversarial, etc.).Predicatefactory family:Predicate.contains/.regex/.schema/.python.Rubric.judge(rubric_text, *, threshold, scale)— built-in LLM-as-judge matcher. Operator supplies the rubric text only; no separate Python module needed.- New expectation kind
"rubric"withevaluate_rubric_judgeruntime. budget_sJSONL field as a back-compat alias forwall_clock_budget_seconds(preferred on write, accepted on read).
- New
zicato.analyzermodule:DecisionEventSummary+aggregate_decision_eventsparse goldfive's new decision events (SteeringDecisionMade,LadderTransitionDecided,DetectorDispatchOrdered,PolicyApplied,RetryBudgetSpent). analyze_epoch_telemetrycalls the auxiliary LLM with a structured prompt and writesepochs/{epoch}/insights/round_{N}.mdwith sections: Headline observations, Suspected over-intervention, Suspected under-intervention, Suggested next mutations.- Orchestrator invokes the analyzer best-effort after every round; proposer embeds the latest insights into its user prompt so the next proposal is informed by the cross-run patterns.
- CLI:
zicato analyze-telemetry [--workspace ...] [--epoch ...] [--round N].
- pedapudi/goldfive#440
— manifest expansion from 31 → 60 entries; new proto events
LadderTransitionDecided,DetectorDispatchOrdered,PolicyApplied,RetryBudgetSpentat tags 40-43. - pedapudi/goldfive#439
— pluggable
Judgeprotocol withJudgeContext/JudgeVerdict;goldfive.wrap(judges=[...]); built-in detectors refactored as Judge instances; newJudgementEmittedproto event at tag 44.
- 644 → 813 passing (+169). Same 5 pre-existing environment failures
on
test_adapter_adk/test_synthetic_adversarial/test_telemetry_reducer_real_goldfiveare unchanged (all environment-dependent on goldfive-editable-install drift).
- Every existing
LossProfile, JSONL board,expected_drift_movementshypothesis, and tournament scoring config from 0.1 continues to work. The drift namespace is preserved verbatim; multi-objective scoring uses sensible defaults when namespace weights aren't configured.
First public alpha. End-to-end evolve loop functioning against both intended v0 dogfood targets. The library is usable; the API will still break in patch releases until v0.2.
zicato init/register/epoch new/mutations/propose/tournament/evolve/epoch closeCLI surface auto-discovered viazicato.cli.commands.*.- Annotated mutation surface (
# zicato:mutableand# zicato:mutable:file) with AST resolution, applier, validator, and amutationsaudit subcommand. - Board format (single-turn, multi-turn-scripted, multi-turn-emulated, synthetic-adversarial, synthetic-clean) with five expectation kinds.
- Collusion-proof multi-turn emulator with the two-callable rule, sealed context construction, and a post-hoc answer-leak heuristic.
- Telemetry ingest via goldfive's
JSONLPersistenceSinkper run + a post-run reducer that producesLossProfile. - Six pattern detectors (drift-kind frequency, hot tasks/agents, plan-revision instability, multi-turn memory failure, multi-turn context loss).
- LLM proposer with structured-output
Experimentschema (hypothesis + patches) and retry-on-parse-failure. - Tournament runner: full mode + fast inline keep/discard. Promote gate enforces a scalar margin AND strict pass-rate monotonicity.
- Epoch lifecycle with manual close + auto-close fallback; running
journal.md; analysis pass at close producing bothanalysis.md(LLM-driven narrative) and a self-containedanalysis.html(deterministic — inline SVG lineage, score trajectory, drift heatmap, per-experiment cards, dark-mode aware). - Per-patch JSON files under
generations/v{N}/patches/{patch_id}.jsonwithpatch_idsreferences inexperiment.json(atomic write order: patches first, then experiment).
.zicato/runtime/state file layer (heartbeat, active_runs, active_tournament, control, control_log, lock).- Workspace lock with PID-aware stale-pid stealing.
- Heartbeat beater task lifecycle wired into
evolve_n_rounds. - Per-call
aux_call_llmtimeouts (ZICATO_AUX_CALL_TIMEOUT, default 120s) at every auxiliary call site (proposer, judge, emulator, analysis). - Progressive
analysis.htmlregeneration after each generation (in addition to the final LLM-drivenanalysis.mdat epoch close). - Tournament runner publishes
active_tournament.json+ per-runactive_runs/{run_id}.jsonfor the live dashboard.
- Rust
zicato-supervisorbinary (single static binary, ~4.4 MB stripped). Auto-spawned byzicato evolve; URL printed to stdout. Opt out with--no-dashboard. - Watchdog half: inotify-based file watching with SIGTERM → grace → SIGKILL signal escalation for stalled orchestrator + stalled runs.
- HTTP + Server-Sent Events server (default
:7892). Endpoints:/api/state,/api/lineage,/api/active-runs,/api/active-tournament,/api/heartbeat,/events,/api/health, plus POST control endpoints (/api/control/pause,…/kill/{id},…/promote/{gen},…/reject/{gen},…/rubric). - Single-page browser UI (vanilla HTML/CSS/JS, no build step, ~77KB
total) bundled with the binary via
include_dir!. Includes lineage SVG, score trajectory, drift heatmap, tournament status panel with deterministic predicted-gate-verdict, active-runs strip, log tail, drill-down panels. Dark-mode aware. Action buttons present but disabled — v1.3 enables them via the control-file protocol.
examples/target_1_presentation/— vendored harmonograf reference agent with 9 mutation points + 7-entry board + rubric + scoring + deterministic mocks +RUN.md.examples/target_2_goldfive_steering/— adversarial board against goldfive's optimization surface (pedapudi/goldfivePR #436) withLoopingAgent/HallucinatingAgent/RefusingAgentetc., amanifest_bridgetranslating goldfive's optimization manifest into zicato MutationPoints, mocks +RUN.md.
ARCHITECTURE.md,MUTATION-SURFACE.md,BOARD-FORMAT.md,EPOCHS-AND-JOURNALING.md,TELEMETRY.md,SCORING.md,EMULATOR.md,DOGFOOD-TARGETS.md,CLI.md,RATIONALE.md,VOCABULARY.md,RUNTIME.md,DASHBOARD.md,ROBUSTNESS.md,STORAGE.md— comprehensive coverage of every load-bearing decision.
- hatchling-based packaging; uv + pip-editable installs work.
pytest+pytest-asynciotest suite (~640 passing).ruff+mypy --strictclean on the Python tree.cargo build --release+cargo test+cargo clippyclean on the Rust supervisor binary.- GitHub Actions CI workflow over Python 3.11/3.12.
Makefilewithinstall,test,lint,format,typecheck,check,supervisor,supervisor-test,install-supervisor,cleantargets.
- Mock-driven smoke runs produce byte-equal outputs across generations in v0; the tournament gate correctly rejects with "insufficient margin". Replace mocks with real LLMs for a meaningful loop.
- Multi-turn scripted/emulated drivers abort some examples with TypeErrors; the reducer treats those entries as zero-signal and the tournament continues. Cleanup is v1.0.1 work.
- Subprocess tournament workers (L3 robustness) and orchestrator
control-file consumer (v1.3) are NOT yet implemented; see
docs/design/ROBUSTNESS.mdfor the phasing plan. - Git-backed generation storage (G1-G10) is documented but not yet implemented; v0 uses directory snapshots + per-patch JSON files.