ilo run: cap runtime + stdout to prevent runaway programs#563
Merged
Conversation
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.
❌ 2 Tests Failed:
View the top 2 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 withILO-R016when wall-clock exceeds the budget.--max-output-bytes BYTES(default ~100 MB) — everyprntcall across tree / VM / JIT charges its output; once the total trips the budget the next write aborts withILO-R017.Both write a structured diagnostic to stderr and exit 1. Set either to
0to 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+1loop 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):
After:
What's in the diff
Six commits, one logical change each:
add runtime_guard module for ilo run safety caps-- newsrc/runtime_guard.rswithinstall(),record_output(),abort_with(). Off untilinstall()is called, so library use is unaffected.wire --max-runtime / --max-output-bytes into CLI and main dispatch-- adds the two flags to theGlobalargs struct and installs the guard from both the explicitCmd::Runarm and the bare-positional dispatch.charge stdout bytes at every prnt site across tree, VM, JIT--Builtin::Prnt,OP_PRT, andjit_prteach callrecord_output(s.len() + 1).register ILO-R016 + ILO-R017 with --explain long-form docs-- registry entries with the override flag and likely-cause hint.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). Addsexamples/runtime-guard.iloas a documentation example the engine harness exercises across every engine.doc: sync runtime guard caps across SPEC, ai.txt, and skill docs-- SPEC.md CLI table + production-safety paragraph,skills/ilo/ilo-agent.mdruntime caps section,skills/ilo/ilo-errors.mdR016/R017 lines.ai.txtregenerates 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 craneliftclean (only pre-existing warnings).cargo fmt --checkclean.cargo test --release --features cranelift --test examples_engines-- ourexamples/runtime-guard.ilopasses; pre-existing seed example failures unrelated to this PR.ilo --explain ILO-R016/ILO-R017renders the long-form docs.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.