agent-exec 0.1.1

Non-interactive agent job runner. Runs commands as background jobs and returns structured JSON on stdout.
Documentation

agent-exec

Non-interactive agent job runner. Runs commands as background jobs and returns structured JSON on stdout.

Output Contract

  • stdout: JSON only — every command prints exactly one JSON object
  • stderr: Diagnostic logs (controlled by RUST_LOG or -v/-vv flags)

This separation lets agents parse stdout reliably without filtering log noise.

Installation

cargo install --path .

Quick Start

Short-lived job (runwaittail)

Run a command, wait for it to finish, then read its output:

# 1. Start the job (returns immediately with a job_id)
JOB=$(agent-exec run echo "hello world" | jq -r .job_id)

# 2. Wait for completion
agent-exec wait "$JOB"

# 3. Read output
agent-exec tail "$JOB"

Example output of tail:

{
  "schema_version": "0.1",
  "ok": true,
  "type": "tail",
  "job_id": "01J...",
  "stdout_tail": "hello world",
  "stderr_tail": "",
  "truncated": false
}

Long-running job (runstatustail)

Start a background job, poll its status, then read its output:

# 1. Start the job (returns immediately with a job_id)
JOB=$(agent-exec run sleep 30 | jq -r .job_id)

# 2. Check status
agent-exec status "$JOB"

# 3. Stream output tail
agent-exec tail "$JOB"

# 4. Wait for completion
agent-exec wait "$JOB"

Timeout and force-kill

Run a job with a timeout; SIGTERM after 5 s, SIGKILL after 2 s more:

agent-exec run \
  --timeout 5000 \
  --kill-after 2000 \
  sleep 60

Commands

run — start a background job

agent-exec run [OPTIONS] <COMMAND>...

Key options:

Flag Default Description
--snapshot-after <ms> 10000 Wait N ms before returning (0 = return immediately)
--timeout <ms> 0 (none) Kill job after N ms
--kill-after <ms> 0 ms after SIGTERM to send SIGKILL
--tail-lines <N> 50 Lines of output captured in the snapshot
--cwd <dir> inherited Working directory
--env KEY=VALUE Set environment variable (repeatable)
--mask KEY Redact secret values from JSON output (repeatable)
--wait false Block until the job reaches a terminal state
--wait-poll-ms <ms> 200 Poll interval used with --wait
--notify-command <JSON_ARGV> Run a command when the job finishes; event JSON is sent on stdin
--notify-file <PATH> Append a job.finished event as NDJSON

status — get job state

agent-exec status <JOB_ID>

Returns running, exited, killed, or failed, plus exit_code when finished.

tail — read output

agent-exec tail [--tail-lines N] <JOB_ID>

Returns the last N lines of stdout and stderr.

wait — block until done

agent-exec wait [--timeout-ms N] [--poll-ms N] <JOB_ID>

Polls until the job finishes or the timeout elapses.

kill — send signal

agent-exec kill [--signal TERM|INT|KILL] <JOB_ID>

list — list jobs

agent-exec list [--state running|exited|killed|failed] [--limit N]

Job Finished Events

When run is called with --notify-command or --notify-file, agent-exec emits a job.finished event after the job reaches a terminal state.

  • --notify-command runs the provided argv without a shell and writes the event JSON to stdin.
  • --notify-file appends the event as a single NDJSON line.
  • completion_event.json is also written in the job directory with the event plus sink delivery results.

Example:

agent-exec run \
  --wait \
  --notify-file /tmp/agent-exec-events.ndjson \
  -- echo hello

Command sink example:

agent-exec run \
  --wait \
  --notify-command '["/bin/sh","-c","cat > /tmp/agent-exec-event.json"]' \
  -- echo hello

For command sinks, the event JSON is written to stdin and these environment variables are set:

  • AGENT_EXEC_EVENT_PATH: path to the persisted completion_event.json
  • AGENT_EXEC_JOB_ID: finished job id
  • AGENT_EXEC_EVENT_TYPE: currently job.finished

Example job.finished payload:

{
  "schema_version": "0.1",
  "event_type": "job.finished",
  "job_id": "01J...",
  "state": "exited",
  "command": ["echo", "hello"],
  "cwd": "/path/to/cwd",
  "started_at": "2026-03-15T12:00:00Z",
  "finished_at": "2026-03-15T12:00:00Z",
  "duration_ms": 12,
  "exit_code": 0,
  "stdout_log_path": "/jobs/01J.../stdout.log",
  "stderr_log_path": "/jobs/01J.../stderr.log"
}

If the job is killed by a signal, state becomes killed, exit_code may be absent, and signal is populated when available.

Logging

Logs go to stderr only. Use -v / -vv or RUST_LOG:

RUST_LOG=debug agent-exec run echo hello
agent-exec -v run echo hello

Development

cargo build
cargo test --all
cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings