Skip to content

test(itmux): live acceptance battery over itmux run + --timeout passthrough (Plan B Task 8)#252

Open
NeuralEmpowerment wants to merge 6 commits into
feat/itmux-run-contractfrom
feat/itmux-eval
Open

test(itmux): live acceptance battery over itmux run + --timeout passthrough (Plan B Task 8)#252
NeuralEmpowerment wants to merge 6 commits into
feat/itmux-run-contractfrom
feat/itmux-eval

Conversation

@NeuralEmpowerment

Copy link
Copy Markdown
Contributor

Plan B Task 8 (codex plan-reviewed, all 7 revisions adopted). Replaces the old Python standalone_eval.py (which drove the superseded Python orchestrator on #240) with a pure-Rust live acceptance battery driving the itmux run binary end-to-end. Stacked on #247.

What's in it

  • itmux run --timeout <secs> passthrough (R6) -> AgentRunSpec.limits.timeout_s via a pure build_run_spec helper (unit-tested); omitted => limits=None, orchestrator default (300s) unchanged. This proves the timeout terminal reason, previously unreachable from the CLI.
  • tests/live_eval.rs - argv builder, AgentRunEvent JSONL parser, AgentRunResult extractor, orphan-container sweep, and an event-contract validator (R5: monotonic seq from 0, exactly one session_end, consistent run_id, result via type:"result" OR --result-file, zero non-JSON stdout lines).
  • Recipe fixtures tests/fixtures/eval/recipe-{claude,codex}-hello/ (EXP-0005 shape).
  • 17 unit tests (run in cargo test, no docker): argv x3, parser x2, result+sentinel extraction, result-file round-trip, R5 validator x6, cancel-boundary detection, fixture-load x2, build_run_spec x2.
  • 8 gated-live tests (#[ignore] + early-return unless AGENTIC_LIVE_EVAL=1): E1 claude happy-path (asserts EXPERIMENT_OK in result.session_log per R1, no new field needed - session_log already carries the pane), E2 codex, E3 live-events ordering, E4 graceful+hard cancel (R4: trigger at tool_end submit / tool_start await; assert terminal reason + no orphan), E5b bad-image orphan-guard, E6 timeout, E7 result-file purity. E5a is unit-proven (R2) via the orchestrator's partial-startup/no-orphan tests.
  • just eval-live (R3) runs the gated battery. experiments/README.md documents the auth prereq (host creds, claude setup-token on expiry), the E1-E7 table, E5a-unit-proven, and the R7 unproven surfaces.

Documented unproven-by-this-battery surfaces (R7)

inline credential materialization, --json false, output_artifacts, observability bundle, token_usage/token_budget, bundled-skill staging (#249, unit-only), subagent execution (validated-only v1).

Incidental finding -> tracked

Truncating itmux run stdout (| head) SIGPIPE-kills the process before terminalize -> orphan container (uncovered by the SIGINT/SIGTERM two-tier cancel). Filed as #251 (fix alongside #248 teardown-by-name).

Gates: cargo test all green (17 pass, 8 ignored), clippy -D warnings clean, fmt clean, R8 neutrality guard holds. Tracks okrs-o78. The live battery runs green once a valid claude/codex host token is present.

Add itmux run --timeout <secs> mapping to AgentRunSpec.limits.timeout_s via a
pure build_run_spec helper. Additive: when omitted, limits stays None so the
orchestrator's default await bound is unchanged. Proves the timeout terminal
reason at the CLI (plan B Task 8, R6).

Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
EXP-0005 recipe directories for the live acceptance battery (Plan B Task 8.1):
recipe-claude-hello (claude, opus-4-8, effort low) and recipe-codex-hello
(codex, gpt-5.5, effort low). Each SYSTEM prompt drives the agent to create
/workspace/hello.txt and print the EXPERIMENT_OK sentinel so E1/E2 can verify
task completion off the captured session_log. Also vendors a well-formed
sample event stream (sample_run_good.jsonl) for the R5 contract-validator
unit tests (no docker).

Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
Rust live-eval harness driving the real itmux run binary end-to-end (Plan B
Task 8.2/8.3), replacing the retired Python standalone_eval.py.

Non-live plumbing (runs in cargo test, no docker): argv builder, JSONL event
parser, AgentRunResult extractor (stream + --result-file), R5 event-contract
validator (seq monotonic-from-0, one session_end, consistent run_id, result
delivery channel, stdout purity), docker orphan sweep, PASS reporter. 17 unit
tests cover the parser, argv builder, validator (good + corrupted streams),
sentinel-from-session_log (R1), result-file round-trip, and eval-recipe load.

Live cases E1-E7 are each #[ignore] AND early-return unless AGENTIC_LIVE_EVAL=1
(double safety): E1 claude happy-path (success + EXPERIMENT_OK in session_log),
E2 codex graceful terminal, E3 incremental event arrival, E4 graceful/hard
cancellation at the submit/await boundary (R4), E5b bad-image no-container,
E6 timeout terminal reason (R6), E7 --result-file stdout purity (R7). E5a is
unit-proven per R2 (orchestrator.rs orphan-guard tests), referenced in a
comment, no live case added.

R1 finding: AgentRunResult.session_log already carries the captured pane, so
no contract field was added - E1 asserts the sentinel against it.

Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
Add a just eval-live wrapper (R3) that runs the gated live battery
(AGENTIC_LIVE_EVAL=1 cargo test --test live_eval -- --ignored --test-threads=1
--nocapture), and an experiments/README pointing at the Rust live-eval as the
acceptance harness. Documents the host-credential auth prereq (claude
setup-token on expiry), the E1-E7 coverage table, E5a being unit-proven (R2),
and the R7 unproven-by-this-battery surfaces (inline creds, --json false,
artifacts, observability, token usage, bundled-skill staging #249, subagents).

PR #240 (superseded Python orchestrator + standalone_eval.py) to be closed by
the maintainer after this and the thin Python client land - not closed here.

Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
…undness)

Fix 1 (codex must-fix): --timeout took an unvalidated f64, so --timeout=-1 /
=0 / =NaN / =inf reached Duration::from_secs_f64 and panicked before docker.
Add a clap value_parser (parse_positive_timeout) rejecting non-finite and
non-positive values with a clean CLI error (exit 2, no panic). Unit tests
cover -1/0/NaN/inf/garbage -> error and 2.5 -> Some(2.5).

Fix 2 (codex must-fix): the event-contract validator dropped blank stdout
lines at collection, so a blank line in --json mode slipped past the purity
check. parse_lines no longer filters blank lines; a blank/whitespace-only line
surfaces as NonJson and the purity rule rejects it. Negative test added.

Fix 3 (codex must-fix): the validator counted exactly one session_end but did
not enforce ordering, so session_end -> tool_start -> result (OnStream) and
lifecycle events after session_end (ResultFile) wrongly passed. Enforce
session_end as the terminal lifecycle event: only the OnStream result line may
follow it, nothing else, in both modes. Negative tests added for both modes.

Nits: removed the dead StreamedRun.exit_ok field (+ its allow(dead_code));
documented the E3 lexicographic-ts assumption (emitter always uses fixed-width
second-precision UTC Z timestamps) rather than adding a datetime dependency.

Claude-Session: https://claude.ai/code/session_019TtekhJft4s2qBGRGgxToG
@NeuralEmpowerment

Copy link
Copy Markdown
Contributor Author

Dual-review gate: PASS

  • Claude adversarial review: APPROVE - traced every R5 validator rule + every E1-E7 assertion against real orchestrator behavior; confirmed load-bearing (not tautological), gating verified empirically (8 live tests never run under plain cargo test).
  • codex cross-model review: CHANGES REQUESTED (3 must-fix: --timeout NaN/-1 panic, blank-line dropped before purity check, session_end not enforced terminal) -> fixed in bbee944 -> re-review APPROVE, no regression.

Fixes: --timeout clap value_parser rejects non-finite/<=0 (no panic reaches Duration::from_secs_f64); parse_lines no longer drops blank lines so a blank line fails the purity check; the validator now enforces that only a Result payload may follow the single session_end (both stream + result-file modes). 20 unit + 8 gated-live tests, clippy -D warnings + fmt clean, R8 neutrality guard holds.

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