Skip to content

feat: refusal + suspended/resume states, contract poll budget, self-healing markers#147

Merged
rctruta merged 2 commits into
mainfrom
feat/refusal-and-poll-budget
Jul 6, 2026
Merged

feat: refusal + suspended/resume states, contract poll budget, self-healing markers#147
rctruta merged 2 commits into
mainfrom
feat/refusal-and-poll-budget

Conversation

@rctruta

@rctruta rctruta commented Jul 6, 2026

Copy link
Copy Markdown
Owner

The two states edge cases 3+4 demanded, plus the crash-recovery fix from today's live incident.

  • REFUSAL stateHANDOFF: impossible reason=… from config_builder → orchestrator outcome refused (first-class honest exit, not a failure). Edge-3 evidence: honest models burned up to 157K tokens thrashing to say "can't".
  • SUSPENDED state + resume — poll timeout while the experiment still executes → suspended (non-error, experiment_id preserved); Orchestrator.resume(exp_id) / multi_agent.py --resume <id> picks up at the analyzer later. Edge-4 rerun evidence: a legitimate 16GB out-of-memory sort outlived even a 30-min budget — no synchronous budget is "right"; the content-addressed capsule is the durable hand-off.
  • Contract-declared poll_budget_seconds (edge-4's original defect: fixed 180s).
  • Liveness-aware running markers (TODO fix: drop duplicate dnf key from fragment parameters #12) — observed live: session teardown killed the API mid-execution of 209fc5df, orphaned marker would have blocked resubmission forever. has_running_marker now self-heals on dead PID / over-age / corrupt, with loud warnings.

Tests

  • 31 orchestrator tests (refusal terminal + cheap; budget flows to poller; suspend-not-fail; resume completes/re-suspends/surfaces failures)
  • 6 marker-staleness regression tests incl. the exact 209fc5df scenario
  • Pre-push green
  • CI green (no --admin)

rctruta added 2 commits July 6, 2026 09:04
…cks resubmission (TODO #12)

Observed live 2026-07-06: session teardown killed the API mid-execution
of 209fc5df, leaving an orphaned running.json. check_registry would have
refused every resubmission of that config forever ("already running —
poll instead"); the marker had to be removed by hand.

has_running_marker is now liveness-aware (single choke point;
check_registry and the API's is_running inherit):
  - unreadable/corrupt marker -> stale
  - older than MAX_MARKER_AGE_SECONDS (6h; also caps PID-reuse window)
    -> stale
  - same host + recorded PID dead -> stale
  - different host within age -> conservatively alive (can't probe)
Stale markers are removed on detection (self-heal) with a loud [WARN].

6 regression tests incl. the exact 209fc5df scenario.
Edge-4's rerun proved no poll budget is "right" for open-ended
workloads: fable-5 correctly chose a 16GB out-of-memory sort that
outlived even a 30-minute budget while executing legitimately.
Synchronous waiting conflates "slow" with "dead".

- Poll timeout while the experiment still runs -> outcome "suspended"
  (non-error, experiment_id preserved). "poll_failed" now means the
  experiment actually failed or polling errored.
- Orchestrator.resume(exp_id): skip config_builder, poll, run analyzer.
  Callable minutes later, next session, or after a crash — the
  content-addressed capsule is the durable hand-off point.
- CLI: multi_agent.py --resume <exp_id> + --poll-budget-seconds.

4 new tests (31 total): suspend-not-fail on timeout, resume completes
when ready, resume re-suspends while running, resume surfaces real
execution failures.

Ships with (same branch): structured REFUSAL state, contract-declared
poll_budget_seconds, liveness-aware self-healing running markers
(TODO #12). Decisions-log entry added.
@rctruta rctruta merged commit 9cfb7df into main Jul 6, 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