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_LOGor-v/-vvflags)
This separation lets agents parse stdout reliably without filtering log noise.
Installation
Quick Start
Short-lived job (run → wait → tail)
Run a command, wait for it to finish, then read its output:
# 1. Start the job (returns immediately with a job_id)
JOB=
# 2. Wait for completion
# 3. Read output
Example output of tail:
Long-running job (run → status → tail)
Start a background job, poll its status, then read its output:
# 1. Start the job (returns immediately with a job_id)
JOB=
# 2. Check status
# 3. Stream output tail
# 4. Wait for completion
Timeout and force-kill
Run a job with a timeout; SIGTERM after 5 s, SIGKILL after 2 s more:
Commands
run — start a background job
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
Returns running, exited, killed, or failed, plus exit_code when finished.
tail — read output
Returns the last N lines of stdout and stderr.
wait — block until done
Polls until the job finishes or the timeout elapses.
kill — send signal
list — list jobs
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-commandruns the provided argv without a shell and writes the event JSON to stdin.--notify-fileappends the event as a single NDJSON line.completion_event.jsonis also written in the job directory with the event plus sink delivery results.- Notification delivery is best effort; sink failures do not change the main job state.
- When delivery success matters, inspect
completion_event.json.delivery_results.
Choose the sink based on the next consumer:
- Use
--notify-commandfor small, direct reactions such as posting to chat or forwarding the event back to the launching OpenClaw session with eitheropenclaw message sendoropenclaw agent --session-id ... --deliver. - Use
--notify-filewhen you want a durable queue-like handoff to a separate worker that can retry or fan out. - Prefer checked-in helper scripts over large inline shell or Python snippets.
Example:
Command sink example:
OpenClaw examples
Notify a Telegram chat directly
Use a small checked-in helper so the notify command stays easy to review. openclaw message send can be appropriate for either user-facing notifications or lightweight delivery back to an agent-facing session.
Example helper shape:
#!/usr/bin/env bash
tmp=
job_id=
state=
exit_code=
Return the event to the launching OpenClaw session
This pattern is often more flexible than sending a final user message directly from the notify command. The launching session can inspect logs, decide whether the result is meaningful, and summarize it in context. Depending on the workflow, either openclaw message send or openclaw agent --session-id ... --deliver may be the better fit.
SESSION_ID="oc_session_123"
Example helper shape using openclaw message send:
#!/usr/bin/env bash
session_id=""
tmp=
With this pattern, the receiving OpenClaw session can read the event payload, inspect stdout_log_path or stderr_log_path, and decide whether to reply, retry, or trigger follow-up work.
If you want explicit agent re-entry instead of lightweight message delivery, use a helper like this:
#!/usr/bin/env bash
session_id=""
tmp=
In practice, both message send and agent --deliver can target either a user-facing or agent-facing flow; pick the one that matches the downstream behavior you want.
Durable file-based worker
Use --notify-file when you want retries or fanout outside the main job lifecycle:
A separate worker can tail or batch-process the NDJSON file, retry failed downstream sends, and route events to chat, webhooks, or OpenClaw sessions without coupling that logic to the main job completion path.
Operational guidance
--notify-commandmust be a JSON argv array, not a shell string.- Keep notify commands small, fast, and idempotent.
- Common sink failures include quoting mistakes, PATH or env mismatches, downstream non-zero exits, and wrong chat, session, or delivery-mode targets.
- If you need heavier orchestration, let the notify sink hand off to a checked-in helper or durable worker.
For command sinks, the event JSON is written to stdin and these environment variables are set:
AGENT_EXEC_EVENT_PATH: path to the persistedcompletion_event.jsonAGENT_EXEC_JOB_ID: finished job idAGENT_EXEC_EVENT_TYPE: currentlyjob.finished
Example job.finished payload:
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
Development