Rio
adr/0083-agent-run-command.mdRaw Markdown · GitHub ↗

ADR-0083: The agent runs commands: gated, asynchronous, time-bounded

  • Status: Accepted; amended by ADR-0084
  • Date: 2026-09-09
  • Deciders: jka
  • Decision: D83

Context

The last deferred part of the agent's tools was running commands: tests, linters, builds, git. ADR-0053 had placed this in scope provided a human approves. The existing exec.run blocks the single-threaded core until the child exits, so a timer-based timeout could never fire while it waited.

Decision

A run_command tool, of a new kind exec, goes through the same propose-and-approve gate as writes.

  • Always gated. Auto-accept applies to edits only. A command always waits for a human, and no separate "auto-run commands" setting exists.
  • Asynchronous. rio::exec::start {argv cwd stdin timeout_ms donecmd} spawns the child and returns immediately. Standard output is read as it arrives, standard error goes to a temporary file, and the exit code is taken from close. The agent's turn yields until completion. The core stays responsive while a command runs.
  • Time-bounded. The default timeout is 120 seconds and the maximum 600; there is no unlimited setting. An overrunning child is killed (kill -TERM on Unix, taskkill /F /T on Windows) and reported as timed out. Resetting or superseding a turn kills a running command.
  • Constraints. Argument vector only, no shell. The working directory is confined to the project root. Any argument Tcl's exec would interpret as redirection or a pipe (<, >, 2>, |, & and similar) is refused. A non-zero exit is a successful run whose code is data; only a failure to launch or a timeout is an error.

agent.propose gains a kind (edit or command), and a command proposal carries the command, its display form and a project-relative working directory instead of a diff.

Consequences

  • The agent can run tests with a human watching, and no provider needed changes.
  • Command output is returned when the command ends. Streaming output and a per-command cancel are deferred.
  • exec.run remains synchronous for git's short commands.
  • Re-approving the same test command every few minutes was tedious, which led to the allow-list of ADR-0084.