Skip to content

ilo run: cap runtime + stdout to prevent runaway programs#563

Merged
danieljohnmorris merged 6 commits into
mainfrom
feature/exec-time-guard-v2
May 21, 2026
Merged

ilo run: cap runtime + stdout to prevent runaway programs#563
danieljohnmorris merged 6 commits into
mainfrom
feature/exec-time-guard-v2

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

Reimplements PR #544 (conflict-magnet) from scratch against current main. Two production-safety guards on ilo run:

  • --max-runtime SECS (default 60) — watchdog thread aborts with ILO-R016 when wall-clock exceeds the budget.
  • --max-output-bytes BYTES (default ~100 MB) — every prnt call across tree / VM / JIT charges its output; once the total trips the budget the next write aborts with ILO-R017.

Both write a structured diagnostic to stderr and exit 1. Set either to 0 to disable. Defaults are well above any legitimate program (real agent tasks finish under 10 s and produce kilobytes); the caps exist to turn a runaway loop into a clean signal the agent can learn from.

Origin: a mandelbrot persona (2026-05-20) missed a col=col+1 loop increment, spun in an infinite loop, and produced 165 MB of stdout before the harness intervened. The agent had nothing to learn from -- the transcript was just a wall of dots. With these caps in place the next runaway aborts inside a single agent turn with a diagnostic that names the override flag and points at the most likely cause.

Repro

Before (would spin forever / fill disk):

ilo 'main>n;n=0;wh true{n=+n 1};n'

After:

$ ilo --max-runtime 1 --json 'main>n;n=0;wh true{n=+n 1};n'
{\"error\":{\"code\":\"ILO-R016\",\"hint\":\"infinite loop is the most common cause ...\",\"message\":\"wall-clock runtime exceeded 1000 ms (--max-runtime 1)\"}}
$ echo $?
1

What's in the diff

Six commits, one logical change each:

  1. add runtime_guard module for ilo run safety caps -- new src/runtime_guard.rs with install(), record_output(), abort_with(). Off until install() is called, so library use is unaffected.
  2. wire --max-runtime / --max-output-bytes into CLI and main dispatch -- adds the two flags to the Global args struct and installs the guard from both the explicit Cmd::Run arm and the bare-positional dispatch.
  3. charge stdout bytes at every prnt site across tree, VM, JIT -- Builtin::Prnt, OP_PRT, and jit_prt each call record_output(s.len() + 1).
  4. register ILO-R016 + ILO-R017 with --explain long-form docs -- registry entries with the override flag and likely-cause hint.
  5. test: cross-engine coverage for runtime + output guards -- subprocess tests exercise both diagnostics on both the default VM path and --jit, plus the happy path (well-behaved program unaffected) and --max-runtime 0 (disable). Adds examples/runtime-guard.ilo as a documentation example the engine harness exercises across every engine.
  6. doc: sync runtime guard caps across SPEC, ai.txt, and skill docs -- SPEC.md CLI table + production-safety paragraph, skills/ilo/ilo-agent.md runtime caps section, skills/ilo/ilo-errors.md R016/R017 lines. ai.txt regenerates from SPEC.md. Site docs (cli.md, diagnostics.md) already landed via PR ilo run: cap runtime + stdout to prevent runaway programs (#5ar) #544's site sync (commit ff1c7e4) and remain accurate.

Closes #544.

Test plan

  • cargo test --release --features cranelift --test runtime_guard -- 6 tests pass (VM + JIT for both codes, happy path, --max-runtime 0).
  • cargo build --release --features cranelift clean (only pre-existing warnings).
  • cargo fmt --check clean.
  • cargo test --release --features cranelift --test examples_engines -- our examples/runtime-guard.ilo passes; pre-existing seed example failures unrelated to this PR.
  • ilo --explain ILO-R016 / ILO-R017 renders the long-form docs.
  • Pre-existing JIT lib test failures (22 of them, all seed-related) and pre-existing clippy warnings exist on bare main too -- not introduced here.

Follow-ups

None. Caps default to safe values; opt-out is --max-runtime 0 / --max-output-bytes 0.

Watchdog thread fires ILO-R016 when wall-clock exceeds --max-runtime
(default 60 s); record_output charges every print and fires ILO-R017
when stdout exceeds --max-output-bytes (default ~100 MB). Both write
a structured diagnostic to stderr and exit 1. Caps are off until
install() is called, so library use is unaffected.

Origin: mandelbrot persona ran an infinite loop and wrote 165 MB
of stdout before the harness intervened (2026-05-20).
Adds the two global flags to the Global args struct and installs the
guard from both the Cmd::Run arm and the bare-positional dispatch.
Both ultimately execute user code and so both need the watchdog
armed before the program enters its first instruction.
record_output is called from the Builtin::Prnt path in the tree
interpreter, OP_PRT in the VM, and jit_prt in the Cranelift JIT.
Each charges the formatted value's byte length plus 1 for the
trailing newline, so a runaway wh true{prnt 0} loop trips the
budget honestly regardless of engine selection.
Subprocess tests exercise the VM and JIT paths for both ILO-R016
(infinite-loop runtime abort) and ILO-R017 (stdout overflow abort)
plus the happy path (well-behaved program unaffected) and
--max-runtime 0 (disable). Adds examples/runtime-guard.ilo as a
documentation example the engine harness exercises across every
engine.
@danieljohnmorris danieljohnmorris merged commit 025389a into main May 21, 2026
1 of 4 checks passed
@danieljohnmorris danieljohnmorris deleted the feature/exec-time-guard-v2 branch May 21, 2026 16:26
@codecov
Copy link
Copy Markdown

codecov Bot commented May 21, 2026

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
4727 2 4725 0
View the top 2 failed test(s) by shortest run time
ilo::examples::examples
Stack Traces | 7.58s run time
thread 'examples' (41927) panicked at tests/examples.rs:158:9:
9/1162 example test(s) failed:

crypto-primitives.ilo (line 42): `ilo .../ilo/examples/crypto-primitives.ilo sha-empty`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (42992) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo (line 44): `ilo .../ilo/examples/crypto-primitives.ilo sha-abc`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43000) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo (line 46): `ilo .../ilo/examples/crypto-primitives.ilo hmac-rfc2`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43006) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo (line 48): `ilo .../ilo/examples/crypto-primitives.ilo b64-roundtrip`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43012) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo (line 50): `ilo .../ilo/examples/crypto-primitives.ilo b64-padded`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43017) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo (line 52): `ilo .../ilo/examples/crypto-primitives.ilo hex-abc`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43025) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo (line 54): `ilo .../ilo/examples/crypto-primitives.ilo ct-eq-yes`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43031) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo (line 56): `ilo .../ilo/examples/crypto-primitives.ilo ct-eq-no`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43037) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo (line 58): `ilo .../ilo/examples/crypto-primitives.ilo ct-eq-len`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43043) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
ilo::examples_engines::examples_all_engines
Stack Traces | 7.7s run time
thread 'examples_all_engines' (41930) panicked at tests/examples_engines.rs:235:9:
9/1148 multi-engine example test(s) failed:

crypto-primitives.ilo [vm] (line 42): `ilo .../ilo/examples/crypto-primitives.ilo --vm sha-empty`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43191) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo [vm] (line 44): `ilo .../ilo/examples/crypto-primitives.ilo --vm sha-abc`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43197) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo [vm] (line 46): `ilo .../ilo/examples/crypto-primitives.ilo --vm hmac-rfc2`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43201) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo [vm] (line 48): `ilo .../ilo/examples/crypto-primitives.ilo --vm b64-roundtrip`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43205) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo [vm] (line 50): `ilo .../ilo/examples/crypto-primitives.ilo --vm b64-padded`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43211) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo [vm] (line 52): `ilo .../ilo/examples/crypto-primitives.ilo --vm hex-abc`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43216) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo [vm] (line 54): `ilo .../ilo/examples/crypto-primitives.ilo --vm ct-eq-yes`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43221) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo [vm] (line 56): `ilo .../ilo/examples/crypto-primitives.ilo --vm ct-eq-no`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43225) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

crypto-primitives.ilo [vm] (line 58): `ilo .../ilo/examples/crypto-primitives.ilo --vm ct-eq-len`
  FAILED (exit exit status: 101)
  stderr: thread 'main' (43229) panicked at src/vm/mod.rs:1877:13:
auto-unwrap on a non-Result tree-bridge builtin slipped past verify
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

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