REQ-O-050: tool exec Built-In Command
Tier: Opt-In | Priority: P2
Source: §77 No Batch Command Dispatch
Addresses: Severity: High / Token Spend: High / Time: High / Context: Medium
Description
The framework MUST provide a built-in tool exec command that reads a JSONL stream from stdin and dispatches each line to the matching subcommand in-process — no subprocess fork per line. Each line is a DispatchRequest with a _cmd field (dot-separated subcommand path matching the tool manifest commands map), an optional _opts object of per-line flag overrides, and any remaining fields forwarded as the --input JSON payload to the dispatched command.
Per-line output streams to stdout as JSONL. Each output line is a ResponseEnvelope with _cmd (echoed from the request) and _line (1-based line index) added to meta. Errors on individual lines are structured per ResponseEnvelope.error and never suppress output for subsequent lines.
The command supports two flags:
--ignore-errors: continue dispatching after a line failure (default: stop at first failure)--dry-run: forwarddry_run: trueto every dispatched command whosedanger_levelis"mutating"or"destructive"; commands withdanger_level: "safe"ignore it
Exit codes:
| Code | Condition |
|---|---|
0 |
All lines dispatched and succeeded |
1 |
One or more lines produced a non-zero exit |
2 |
The JSONL stream was malformed (parse error before any dispatch) |
tool exec itself declares danger_level: "safe" — danger is determined per dispatched line, not at the exec call site.
Acceptance Criteria
cat ops.jsonl | tool exec --output jsonldispatches all lines in-process and emits one response line per input line to stdout- Each response line is a valid
ResponseEnvelopewith_cmdand_linepresent inmeta - Without
--ignore-errors, dispatch stops at the first failure and exits1 - With
--ignore-errors, dispatch continues past line failures; exit code is still1if any line failed --dry-runis forwarded to every dispatched command that declaresdanger_level != "safe"- A JSONL parse error on any line emits an error response for that line with
error.code: "DISPATCH_PARSE_ERROR"anderror.phase: "validation"; no side effects for that line - A fully malformed stream (cannot parse any line) exits
2 - The command is available with zero per-command implementation work once enabled at the framework level
Schema
Types: dispatch-request.md · response-envelope.md
Each stdin line is a DispatchRequest. Each stdout line is a ResponseEnvelope extended with _cmd and _line in meta.
Wire Format
$ cat ops.jsonl | tool exec --ignore-errors --output jsonl
Input (stdin, one JSON object per line):
{"_cmd":"account.create","name":"Assets:Bank","open_date":"2024-01-01"}
{"_cmd":"transaction.add","_opts":{"draft":true},"date":"2024-01-15","narration":"Buy BTC"}
{"_cmd":"commodity.create","currency":"INVALID"}
Output (stdout, one JSON object per line):
{"_cmd":"account.create","_line":1,"ok":true,"data":{"id":"acct_123"},"error":null,"warnings":[],"meta":{"duration_ms":4}}
{"_cmd":"transaction.add","_line":2,"ok":true,"data":{"id":"txn_456","draft":true},"error":null,"warnings":[],"meta":{"duration_ms":11}}
{"_cmd":"commodity.create","_line":3,"ok":false,"data":null,"error":{"code":"VALIDATION_FAILED","message":"Invalid currency format","retryable":false,"phase":"validation"},"warnings":[],"meta":{"duration_ms":2}}
Exit code 1 (one line failed). Without --ignore-errors, output stops after the first failed line.
Example
app = Framework("tool")
app.enable_exec() # registers `tool exec`; zero per-command work required
# Agent generates a plan and streams it in one call:
plan = [
{"_cmd": "account.create", "name": "Assets:Bank", "open_date": "2024-01-01"},
{"_cmd": "commodity.create", "currency": "BTC", "name": "Bitcoin"},
{"_cmd": "transaction.add", "date": "2024-01-15", "narration": "Buy BTC"},
]
payload = "\n".join(json.dumps(op) for op in plan)
result = subprocess.run(
["tool", "exec", "--output", "jsonl"],
input=payload, capture_output=True, text=True,
)
results = [json.loads(line) for line in result.stdout.splitlines() if line.strip()]
Related
| Requirement | Tier | Relationship |
|---|---|---|
| REQ-C-001 | C | Composes: exit code declarations applied per dispatched line |
| REQ-C-002 | C | Composes: danger_level declarations used to route --dry-run forwarding |
| REQ-C-004 | C | Composes: --dry-run forwarded per-line to declared mutating/destructive commands |
| REQ-F-004 | F | Enforces: each output line is a ResponseEnvelope |
| REQ-O-032 | O | Provides: --input flag that exec uses to forward per-line payload |
| REQ-O-041 | O | Provides: manifest commands map whose dot-path keys are valid _cmd values |
| schemas/dispatch-request.md | Schema | Provides: per-line JSONL input envelope consumed by tool exec |
| guides/batch-dispatch.md | Guide | Provides: design rationale, protocol details, and safe invocation patterns |