Skip to content

Canary Phase 2 + 3: add invariants S-02, E-01, E-05, B-01, S-03, B-02, R-01 #882

Description

@obasilakis

Summary

Phases 2 and 3 of the Canary Invariant Harness (#411), shipped together in PR #884. Follow-up to PR #653 (Phase 1 — S-01, E-02, L-03).

Adds seven invariants in two waves. The split reflects internal complexity — Phase 2 is the four single-source SQL/Redis checks, Phase 3 adds the three "moderate" ones that each need one new piece of plumbing.

Context

Phase 1 landed the canary library + service + fleet manifest + admin endpoint. The orchestration invariant catalog (docs/testing/orchestration-invariant-catalog.md) identifies 12 high-impact invariants for the starting subset; 3 are now in production. This issue covers the next 7. After it lands, the 2 remaining catalog invariants (E-06, G-01) require either HTTP fan-out or one-shot restart probes — different shape, deferred to a future ticket.

Acceptance Criteria

Phase 2 — single-source SQL/Redis (no new sources)

  • S-02 — No overbooking added: ZCARD(agent:slots:A) ≤ agent_ownership.max_parallel_tasks. Critical. Tier A.
  • E-01 — Terminal-state closure added: status='running' AND started_at < now() - (timeout_seconds + 300s) must be 0. Critical. Tier B. Uses iso_cutoff() (architecture invariant #16).
  • E-05 — Dispatched rows have session added: status='running' AND started_at < now() - 60s ⇒ claude_session_id IS NOT NULL. Major. Tier B. Guards bug: Cleanup service misses 'skipped' executions and slow to detect no-session failures #106.
  • B-01 — Queue-status coherence added: per agent, db.get_queued_count matches snapshot's independently-collected queued id-list count. Critical. Tier A. Regression guard against future cache layer / status-filter drift.

Phase 3 — moderate complexity (one new piece of plumbing each)

  • S-03 — Slot TTL ≥ execution timeout added: for every member of agent:slots:A, the companion agent:slot:A:{eid} HASH has TTL ≥ execution_timeout_seconds + 300s (SLOT_TTL_BUFFER). Critical. Tier A. Guards bug: Stale slot cleanup uses fixed 20-min TTL regardless of agent timeout #226.
  • B-02 — No queued without slots-full added: if any agent has len(queued) > 0, then either len(slots excluding sentinels) == max_parallel OR a drain-tick fired in the last 60s. Critical. Tier B. Catches stalled drain callbacks. Requires CapacityManager.run_maintenance() to write a canary:drain_tick_at heartbeat Redis key on each successful sweep.
  • R-01 — No zombie Claude processes added: for every running trinity.platform=agent container, ps -eo stat,comm | grep ' Z.*claude' | wc -l returns 0. Critical. Tier A. Guards bug: agent-server.py spins at ~83% CPU after claude CLI subprocess becomes defunct #407. New source type (Docker exec) — failures recorded in sources_unavailable so an unhealthy container doesn't kill the cycle.

Common

  • Each invariant wired into deterministic library and registered with CanaryService.run_cycle().
  • For each: a manual reproduction in PR description — mutate code or state to provoke the violation, observe POST /api/canary/run-cycle reports it, revert, observe green.
  • docs/memory/architecture.md CANARY-001 section updated with Phase 2 + Phase 3 invariant blocks.

Technical Notes

  • Phase 2 stays scope-pure to canary/ (+ architecture doc). Phase 3's B-02 plumbing touches services/capacity_manager.py:run_maintenance — a one-line heartbeat write at the END of the sweep (so a crash mid-sweep doesn't claim success).
  • R-01's docker exec per cycle is bounded by agent count (not execution count). Cost ≈ 1 exec per running agent per 5 min — negligible.

Out of scope

  • Phase 4 — E-06 (HTTP fan-out across agents to /api/executions/running) and G-01 (one-shot post-restart probe — different shape, doesn't fit the 5-min loop).

Refs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions