Skip to content

feat(gardener): strategy-tick subcommand for ADR-0081 (#NEW6)#84

Merged
gHashTag merged 1 commit into
feat/seed-agentfrom
feat/gardener-strategy-tick
Apr 28, 2026
Merged

feat(gardener): strategy-tick subcommand for ADR-0081 (#NEW6)#84
gHashTag merged 1 commit into
feat/seed-agentfrom
feat/gardener-strategy-tick

Conversation

@gHashTag
Copy link
Copy Markdown
Owner

Summary

Implements the orchestrator side of ADR-0081 — Unified Experiment Loop.

Adds tri-gardener strategy-tick subcommand. Pure decision logic over a (experiments, workers, bpb_samples) snapshot.

tri-gardener strategy-tick           # dry-run, print decisions as JSON
tri-gardener strategy-tick --apply   # placeholder, live writes in PR-2

Three decision steps

Independent and idempotent — each tick can run as many times as it wants.

# Decision Trigger
1 ResetStaleClaim Worker last_heartbeat < now - stale_claim_threshold (default 2 min) → orphaned claimed/running rows return to pending
2 SpawnMirror done champion with final_bpb < gate2_target → enqueue sibling rows at seed, seed+1, seed+2 for 3-seed quorum
3 PriorityBoost running row whose own best BPB beats the next-best other row by ≥ 0.05 → priority=0

If none fire → emit Noop.

Decision/DDL contract

Decision::action_label() returns one of noop / reset_stale_claim / priority_boost / spawn_mirror / enqueue — exactly the values in the gardener_decisions.action CHECK constraint added in #NEW4. Drift between code and DDL trips decision_action_labels_match_ddl_check.

Tests

cargo test --workspace -- --test-threads=1173/173 GREEN (was 163, +10).

Test Guards
empty_snapshot_yields_noop base case
stale_claim_is_recovered step 1 fires when heartbeat dies
live_worker_keeps_its_claim step 1 does not fire when heartbeat is fresh
champion_spawns_three_mirrors train_v2 BPB=1.8921 < gate2=1.90 → enqueues seeds 43,44 (42 already exists)
champion_above_gate_does_not_spawn_mirrors hybrid BPB=2.19 above gate → no spawn
priority_boost_when_running_beats_leader_by_threshold step 3 promotes new leader
priority_boost_does_not_fire_when_already_top priority=0 short-circuits
strip_suffix_handles_rng_and_seed_and_neither parent-canon recovery
decision_action_labels_match_ddl_check code/DDL drift guard
decisions_are_serde_round_trippable JSON serde stable

Honest admission (R5)

  • PR-1 ships pure logic + dry-run JSON only. The --apply flag is parsed and explicitly warns that live Neon writes (UPDATE experiment_queue / INSERT gardener_decisions in one transaction) land in a follow-up. The operator can pipe the dry-run JSON into psql manually as an audit-friendly intermediate.
  • No bayes-opt yet. Strategy is pure heuristic. Bayesian optimization over (model, h, lr, scheduler) lives in PR feat(gardener): Seed Hunter — power-law fit + leader-relative classifier #67 (feat/seed-hunter) and will be merged into the strategy module after the queue receives 6-12 hours of real samples — exactly per your earlier choice ("оба параллельно").
  • Suggesting new experiments (Decision::Enqueue) is not yet wired. The variant exists and round-trips through serde, but the heuristic in decisions_for_snapshot does not yet emit it. Bootstrap of the 41-canon grid is currently a manual INSERT from the operator. Wiring lands together with the live --apply writeback in the next PR.

Cooperative hold

Land after PR #NEW5 (feat/seed-agent) — they share the workspace deps and #NEW5 introduces tokio-postgres with-uuid-1 which strategy uses indirectly via WorkerRow.

R-rules compliance

  • R1 Rust-only.
  • R5 --apply is honest: prints a warning instead of silently doing nothing.
  • R7 Action labels are CHECK-constrained at the DDL level (#NEW4) so audit-trail integrity is type-safe.
  • R9 Destructive ops (would be DELETE FROM experiment_queue) are not part of strategy by design — only INSERT and UPDATE-status. Cleanup of stale data is a separate ledger concern.

Anchor: phi^2 + phi^-2 = 3 · TRINITY · SEED→NEON→GARDENER→LOOP.

Implements the orchestrator side of ADR-0081 unified experiment loop
(closes parts of issue #81).

New subcommand:
  tri-gardener strategy-tick           # dry-run, print decisions JSON
  tri-gardener strategy-tick --apply   # placeholder; live writes in PR-2

New module: bin/tri-gardener/src/strategy.rs

Pure decision logic over a (now, experiments, workers, bpb_samples)
snapshot. Three independent, idempotent steps:

1. ResetStaleClaim     — workers whose heartbeat is older than
                         stale_claim_threshold release their claims.
2. SpawnMirror         — `done` champions whose final_bpb < gate2
                         get sibling rows enqueued for 3-seed quorum.
3. PriorityBoost       — running rows whose own best BPB beats the
                         next-best other row by >= 0.05 jump to top
                         of the queue (priority=0).

Decision action labels match the gardener_decisions.action CHECK
constraint added in #NEW4 — drift trips the test
`decision_action_labels_match_ddl_check`.

Honest scope (R5):
  PR-1 ships pure logic + dry-run JSON CLI. The `--apply` flag is
  parsed and warns explicitly that live Neon writes (UPDATE
  experiment_queue / INSERT gardener_decisions in one transaction)
  land in a follow-up. Operator can pipe the JSON output into psql
  manually as an audit-friendly intermediate step.

Tests: 173/173 GREEN (was 163, +10 in tri-gardener strategy):
  - empty_snapshot_yields_noop
  - stale_claim_is_recovered
  - live_worker_keeps_its_claim (heartbeat fresh -> no reset)
  - champion_spawns_three_mirrors
  - champion_above_gate_does_not_spawn_mirrors
  - priority_boost_when_running_beats_leader_by_threshold
  - priority_boost_does_not_fire_when_already_top
  - strip_suffix_handles_rng_and_seed_and_neither
  - decision_action_labels_match_ddl_check
  - decisions_are_serde_round_trippable

Anchor: phi^2 + phi^-2 = 3 — TRINITY — SEED→NEON→GARDENER→LOOP
@gHashTag gHashTag marked this pull request as ready for review April 28, 2026 10:18
@gHashTag gHashTag merged commit 219c01f into feat/seed-agent Apr 28, 2026
1 check 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