A local runtime for delegating one-shot tasks between agent CLIs
Project description
CLIExec
Delegate one-shot work between Agent CLIs through one local interface.
CLIExec lets the Agent CLI you are using hand a bounded task to another installed Agent CLI and receive a normalized final result. The current CLI is the controller; the delegated CLI is the worker. Each worker is described once in TOML, so controllers do not need a separate integration for every target.
sequenceDiagram
participant Controller as Controller Agent CLI
participant Runtime as CLIExec
participant Worker as Worker Agent CLI
Controller->>Runtime: Delegate task
Runtime->>Worker: Launch task
Worker-->>Runtime: Final result
Runtime-->>Controller: Normalized result
[!IMPORTANT] CLIExec is alpha software. After installing or upgrading a worker, run its targeted check, such as
cliexec doctor codex, because upstream flags and output formats can change.
What CLIExec handles
- Normalizes text, JSON, and JSONL worker output into one versioned result.
- Runs short tasks in the foreground or supervises longer tasks in the background.
- Applies permission policy, timeouts, output limits, cancellation, and process-group cleanup.
- Keeps prompt transport, output parsing, permission flags, and environment access in per-worker adapters.
CLIExec runs locally and does not require a daemon or hosted control plane. Worker installation, authentication, account selection, and model selection remain under the target CLI's control.
Supported Agent CLIs
Built-in presets are included for these workers:
| Worker | Prompt transport | Explicit local file | Explicit local image |
|---|---|---|---|
| Claude Code | stdin | No | No |
| Codex CLI | stdin | No | Yes |
| Antigravity CLI | argv | No | No |
| OpenCode | stdin | Yes | Yes |
| Grok Build | argv | No | No |
The last two columns describe explicit attachment paths mapped by the built-in adapter. They do not describe workspace access. A worker can still inspect files under its working directory with its native tools, subject to the selected permission mode.
Claude Code's current --file option expects an existing API file ID in file_id:relative_path form, and its preset has no compatible local image-path flag. Grok Build's --prompt-json expects structured content blocks rather than a local path. Supporting these forms requires an upload or structured-input transport, which the current version does not implement.
Install
Requirements: Linux, Python 3.11+, uv, and at least one supported Agent CLI.
uv tool install cliexec
cliexec init
cliexec doctor codex
Replace codex with the worker you installed. doctor checks its executable, version, and required flags in the help output; it does not make an authenticated model call. The authenticated smoke check is documented in the packaged Skill.
init creates the user configuration only when it is missing. It does not overwrite existing settings. Built-in presets are enabled by default. Install and authenticate only the workers you plan to use.
Install the packaged Skill if Claude Code or Codex will use CLIExec as a controller:
cliexec skill install --target all
The Skill is optional for direct terminal use. It contains the controller workflow, task commands, result contract, exit codes, and failure-handling rules. See the packaged CLIExec Skill or run cliexec --help when using the CLI manually.
Configure CLIExec
User configuration lives at ${XDG_CONFIG_HOME:-~/.config}/cliexec/config.toml. Repository configuration is never loaded implicitly, and unknown fields are rejected.
Policy and preset overrides
The policy values below are the defaults. Durations accept a positive number of seconds or a string with an s, m, h, or d suffix.
# Configuration schema. The current version accepts 1.
version = 1
[policy]
# Maximum number of active tasks. Integer >= 1. Default: 4.
max_concurrency = 4
# Default task timeout. Must not exceed max_timeout. Default: "30m".
default_timeout = "30m"
# Largest timeout a controller may request. Default: "2h".
max_timeout = "2h"
# Permission ceiling: "read_only", "workspace_write", or "unrestricted".
# Individual tasks still default to "read_only".
max_permission = "workspace_write"
# Keep terminal run records for this many days. Integer >= 1. Default: 30.
retention_days = 30
# Maximum final or partial result embedded in command output, in bytes.
# Integer >= 1024. Larger results remain available in the run files.
inline_result_bytes = 262144
# Combined stdout and stderr limit per task, in bytes.
# Must be >= inline_result_bytes. Reaching it terminates the task.
max_output_bytes = 67108864
# Override one packaged preset by its config key.
[agents.grok]
# Boolean literal: true or false. Do not quote it. Packaged presets default to true.
enabled = false
max_permission is an upper bound. Each task still starts with read_only unless the controller requests and policy permits a higher mode.
Configure provider, account, and model choices in the worker itself. For example, OpenCode's default model belongs in OpenCode's user configuration, not in a CLIExec preset.
Packaged presets
The source definitions are in src/cliexec/presets/. A release installs them inside the Python package as cliexec/presets/*.toml; the physical path depends on the uv tool environment. CLIExec loads these files as package resources on every run. init writes references for detected executables, not full copies or an allowlist; every packaged preset is still loaded.
Do not edit the installed files. An upgrade can replace them. Override a preset in the user configuration instead:
# Same-name tables are merged over the packaged preset.
[agents.claude]
enabled = false
# A new local worker name can inherit a packaged preset.
[agents.review_codex]
# Options: "agy", "claude", "codex", "grok", or "opencode".
preset = "codex"
enabled = true
Nested tables are merged recursively. Scalars and arrays replace the packaged value, so overriding command, env.pass, or an argument list replaces that whole array. preset can reference only a packaged preset and starts from its original definition, not from user overrides made to that preset name. There is no field-deletion syntax; use a new worker name for an entirely custom adapter. User configuration takes precedence over packaged presets, and an explicitly supplied configuration is added as the final layer.
Connect another Agent CLI
"Connect" means describing how CLIExec should translate a task into one noninteractive worker process. CLIExec starts that process in the requested working directory, sends one prompt, waits for it to finish, and extracts its final answer. It does not embed the provider's SDK or automate an interactive TUI.
The target CLI needs a headless or noninteractive mode that:
- accepts one prompt through stdin or argv;
- exits on its own when the task ends;
- returns stable text, JSON, or JSONL output;
- exposes any required permission modes as command arguments.
Complete annotated adapter
# "reviewer" is the local worker name. It must be 1 to 64 lowercase
# letters, digits, hyphens, or underscores, and must start with a letter or digit.
[agents.reviewer]
# Optional: inherit a packaged preset before applying the fields below.
# Options: "agy", "claude", "codex", "grok", or "opencode".
# Omit this when defining an adapter from scratch.
# preset = "codex"
# Enable or disable this worker. Boolean literal, not a string. Default: true.
enabled = true
# Executable and fixed arguments. Required after preset merging.
command = ["reviewer-cli", "run"]
# Exit codes that mean the worker succeeded. Integer array. Default: [0].
success_exit_codes = [0]
# Allow unrestricted mode at the adapter level. Boolean literal. Default: false.
# Global policy and a modes.unrestricted table must also allow it.
allow_unrestricted = false
[agents.reviewer.input]
# Prompt transport: "stdin" or "argv". Default: "stdin".
mode = "stdin"
# Used only with mode = "argv". Must contain {prompt}. Default: "{prompt}".
# prompt_arg = "--prompt={prompt}"
# Repeat these templates for every attached local file or image.
# A non-empty array must contain {path} in at least one item; default [] means unsupported.
file_args = ["--file", "{path}"]
image_args = ["--image", "{path}"]
# Optional target-specific cwd flag. CLIExec already starts the process in cwd.
# A non-empty array must contain {cwd} in at least one item. Default: [].
cwd_args = ["--cwd", "{cwd}"]
[agents.reviewer.output]
# Worker stdout format: "text", "json", or "jsonl". Default: "text".
# "json" expects one document; "jsonl" expects one JSON value per nonblank line.
format = "jsonl"
# JSON/JSONL only: keep objects matching every entry. Dotted object keys are supported;
# array indexes and escaped dots are not.
# Default: {}, which matches every object.
match = { type = "result" }
# JSON/JSONL only: dotted object path to the final text. No default;
# required for these formats.
field = "result.text"
# Multiple matches: "first", "last", or "concat". Default: "last".
# "concat" joins matches with newlines. With format = "text", CLIExec returns
# all trimmed stdout and ignores these selectors.
collect = "last"
# A mode table declares support for that permission; an absent table is unsupported.
# args is a string array and may be empty.
[agents.reviewer.modes.read_only]
args = ["--sandbox", "read-only"]
[agents.reviewer.modes.workspace_write]
args = ["--sandbox", "workspace-write"]
# Optional. Keep allow_unrestricted = false unless the target is externally isolated.
# [agents.reviewer.modes.unrestricted]
# args = ["--sandbox", "danger-full-access"]
[agents.reviewer.env]
# Extra environment variable names inherited by the child. String array. Default: [].
# CLIExec also passes its fixed basic environment described in the security section.
# This allowlist does not apply to doctor probes.
pass = ["REVIEWER_API_KEY"]
[agents.reviewer.probe]
# Used only by doctor. Task execution ignores this table. Each probe has a 5-second limit.
# Probes run command[0] plus the args below; fixed command[1:] arguments are not included.
# Arguments used to read the version. Default: ["--version"].
# [] skips only when version_regex is also omitted.
version_args = ["--version"]
# Optional regex with no default. A named group called "version" is recommended.
# Without it, CLIExec uses the first dotted number in the output.
version_regex = 'reviewer-cli (?P<version>\d+\.\d+\.\d+)'
# Optional comma-separated range with no default, using <, <=, >, >=, or ==.
# An out-of-range version produces a warning rather than a hard failure.
tested_versions = ">=1.0.0,<2.0.0"
# Arguments used to read help. Default: ["--help"].
# [] skips only when help_contains is also empty.
help_args = ["--help"]
# Every token is a case-sensitive substring of combined stdout and stderr.
# A missing token makes doctor fail. Default: [].
help_contains = ["--sandbox", "--file"]
Add the adapter to the user configuration shown above. Validation and task commands are documented in the packaged Skill.
CLIExec executes command directly without a shell. The current version does not load custom parser scripts. If a target requires an upload handshake, a persistent session, TUI keystrokes, or a custom wire protocol, TOML alone is not enough for that CLI. builtin is internal metadata and should not be set in user configuration.
Security model
| Area | CLIExec behavior | What you should know |
|---|---|---|
| Permissions | Tasks default to read_only. Global max_permission sets the ceiling. unrestricted also requires the selected adapter to allow it. |
Modes map to the worker's native flags. CLIExec is not an independent OS sandbox. |
| Concurrent writes | Write-capable tasks with overlapping resolved working directories cannot run at the same time. | This does not isolate CLIExec from unrelated local processes. |
| Process lifecycle | Timeout, cancellation, or output overflow terminates the worker process group. | Files changed before termination are not rolled back. |
| Environment | A child receives a fixed basic environment plus variables allowed by its adapter. CLIEXEC_DEPTH blocks accidental nested delegation. |
The recursion check is a guard, not a security boundary. Pass only variables the worker needs. |
| Stored data | Run directories use mode 0700 and files use 0600. Plaintext records are kept under ${XDG_STATE_HOME:-~/.local/state}/cliexec for 30 days by default. |
Records are not encrypted at rest. The same OS user and root can read them. |
| Prompt exposure | stdin keeps prompts out of the controller argv. Antigravity CLI and Grok Build presets use argv transport. | Their prompts can be visible to local process inspection. Avoid secrets. |
| Provider traffic | CLIExec's control plane is local. | A worker may send prompts, files, images, or workspace content to its configured cloud service. |
| Worker results | Results are marked as untrusted data. | The controller should verify important findings and file changes before relying on them. |
Scope
The current version handles local, one-shot execution. It does not manage multi-step workflows, long-lived conversations, isolated worktrees, automatic merging, or remote workers. A Web UI and protocol endpoints such as MCP, ACP, or A2A are also outside the current scope.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cliexec-0.1.0.tar.gz.
File metadata
- Download URL: cliexec-0.1.0.tar.gz
- Upload date:
- Size: 54.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
160a82dba5157dd2b3037812cc8461adef9e12500beef0b4b150ba25b349e6b5
|
|
| MD5 |
04e8228c92f6ab856696ba7fa916262f
|
|
| BLAKE2b-256 |
0a56e500dfeb1ca8e3323ebea10349fc696ae576df14f83e01b09b2d5b14913c
|
File details
Details for the file cliexec-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cliexec-0.1.0-py3-none-any.whl
- Upload date:
- Size: 43.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
469d9b1e4bc0d60c40b750b2e391b9d8d5c2a07b3d86d1501744f6240a82f747
|
|
| MD5 |
b144c40967edd4df1daa67d76ce24818
|
|
| BLAKE2b-256 |
cebaae0da018a9812de5ac2bef02404b00bbf95b4a6bf3055953faaf15c49781
|