Agent-friendly contract
| Field | Value |
|---|---|
| Status | Accepted |
| Authors | commixon (commixon), with Codex |
| Created | 2026-11-06 |
| Accepted | 2026-11-06, approved by the maintainer |
| Linear | COM-199 |
| Supersedes | None |
| Superseded by | None |
History
- 2026-11-13 (venom-spec#174, normative; explicitly accepted by the maintainer on 2026-11-13): section 7 lets a product trust its UID when the user turns isolation off (RFC-0005 section 11.1). venomx defaults to off. Every other product keeps unknown callers read-only.
Summary
Every Venom product binary implements the same agent-facing contract: one
daemon/CLI binary, schema-defined verbs, generated MCP/docs/completions,
structured output, skills, ZON configuration, diagnostics and scoped grants.
This RFC specifies the shared behavior and a conformance checklist
that downstream repos can link to. venom-std supplies the shared machinery;
products supply their verb handlers. Library-only repos (venom-std and
venom-term) implement the relevant machinery rather than shipping daemons.
Motivation
An agent must be able to discover, invoke and diagnose every operation
without driving a TUI or parsing human text. Independently maintained CLI,
MCP and keybinding implementations would disagree on validation and rights.
The ecosystem contract
already requires a common surface; this RFC makes it testable for the
CLI/MCP generator (COM-213), venom-echo (COM-216), and every product.
Design
1. One binary, every action a verb
For an installed product <tool>, the same executable MUST implement:
venom-echo serve
In another shell, once the daemon is listening:
venom-echo echo --text hi --json
venom-echo skill --json
venom-echo config get --json
venom-echo doctor --json
serve MUST run the daemon in the foreground without a TUI. Socket
selection, ownership, locking, peer checks and hello MUST follow
RFC-0002. Multiple instances use
--socket; there is no additional instance-name mechanism in v1.
Startup and lifetime policy are product-specific. Each product MUST document
which invocations start a missing daemon, whether explicit serve is
required, and when the daemon exits. A product MAY offer multiple modes.
Every mode MUST preserve the explicit foreground serve entry point and
RFC-0002’s socket locking and authorization checks. Concurrent auto-starting
clients MUST NOT create competing daemons. If no daemon can be reached or
started under the selected policy, the client MUST report unavailable
(exit 6), as defined by RFC-0002.
For venomx, the intended modes are persistent operation (remaining available independently of session count) and tmux-style operation (starting on demand and remaining alive while sessions exist, including detached sessions). Its product specification will define mode selection, defaults, the exact startup triggers and shutdown behavior after the last session ends. Other products choose explicit startup, automatic startup or multiple modes in their own specifications; this contract imposes no ecosystem-wide default.
Every user-visible application action MUST have a schema-declared verb. A keybinding MUST map to a verb and typed parameters, invoking the same validation, handler and authorization as the CLI/MCP route. A binding MUST NOT be a privileged alternate implementation. A product MUST describe the effect of each verb independently of any particular keybinding or TUI.
2. One schema, generated interfaces
The complete normalized schema MUST follow RFC-0004. It generates the CLI parser/help, MCP tool catalog and adapter, Markdown/man documentation, bash/zsh/fish completions, and skill text. Products MUST NOT maintain separate command or permission tables for those surfaces. Generated artifacts MUST describe result shapes, stream items, grants, errors and exit policies, as well as input flags.
Dotted wire names map to spaced CLI paths, with the exact dotted alias also
accepted. Parameters use named flags; examples MUST NOT rely on positional
arguments. pane.set_title thus maps to pane set-title --title ....
Shared declarations MUST merge with product declarations before validation
and schema-digest calculation; products MUST NOT override shared names.
| Shared verb / CLI spelling | Dispatch | Required right |
|---|---|---|
serve |
Local bootstrap | Local user’s authority to create the socket |
mcp |
Local MCP stdio bootstrap | Per upstream verb |
ping |
Daemon | none |
skill |
Daemon | read |
skill.install / skill install |
Daemon | files, scoped to destination |
doctor |
Daemon | read |
config.get, config.check, config.schema |
Daemon | read |
config.set, config.reload |
Daemon | admin |
events |
Daemon stream | read, plus event resource scope |
hello and connection-local cancel are RFC-0002 transport operations,
not MCP tools. serve and mcp MUST NOT be remote daemon/MCP tools.
All other entries use RFC-0004’s normal mappings, including bounded MCP
stream buffering. The MCP adapter MUST preserve the upstream caller’s
rights, never elevate calls to the authority of its own process.
3. JSON, streams and exit codes
Every CLI command MUST accept --json, including usage failures and
--help. The accepted wire and CLI mapping in
RFC-0002 section 7 is
authoritative; the table is repeated here for implementers:
| Class | Exit |
|---|---|
| Success | 0 |
failed (also unknown classes) |
1 |
invalid_params (also CLI usage) |
2 |
not_found |
3 |
denied |
4 |
conflict |
5 |
unavailable |
6 |
timeout |
7 |
cancelled |
8 |
protocol |
9 |
unsupported |
10 |
internal |
11 |
Codes 12–63 are reserved. A schema-declared successful result exit policy MAY pass through a result status (for example a child’s exit); errors always use the table. Scripts MUST match error codes, never English messages.
$ venom-echo echo --text hi --json
{"text":"hi"}
$ venom-echo echo --json
{"error":{"code":"invalid_params.missing","message":"missing required param 'text'","data":{"param":"text"}}}
$ echo $?
2
For non-streaming verbs, stdout MUST contain only the final ok payload
or {"error":...}, followed by a newline. Diagnostics MUST go to stderr.
Streaming --json MUST emit one compact JSON item per line, no array,
banner or pretty printing, suppressing the final successful ok. A failed
stream MUST end with the error wrapper and the appropriate nonzero exit.
Cancellation follows RFC-0002. events --json MUST use this NDJSON mapping;
event envelope, filters, cursors, replay and event-specific grants are owned
by RFC-0003 (COM-202), not independently invented by products.
serve --json is a long-running local command: it emits no success record
until clean termination, then {}; startup/runtime failure emits the error
wrapper. It MUST NOT mix logs into stdout. mcp --json accepts the flag but
stdout remains MCP protocol traffic, because wrapping it would break MCP.
MCP startup errors go to stderr with the standard process exit code.
--help --json returns {"help":"..."} even for these bootstrap commands.
4. Skill discovery and installation
skill MUST return the deterministic, credential-free SKILL.md generated
by RFC-0004 section 6.
Human output is the text; JSON output is {"content":"<complete text>"}.
It MUST describe the read-only default and MUST NOT run its examples.
The v1 installer selects a user skill root automatically from target,
with an optional explicit root override:
venom-echo skill install --target claude-code --json
venom-echo skill install --target codex --json
venom-echo skill install --target opencode --json
venom-echo skill install --target codex --directory /tmp/codex-skills --json
target is required and limited to claude-code, codex, opencode.
directory is an optional absolute path to a skill root. When supplied, it
MUST take precedence over the target’s default. Otherwise the installer MUST
use the following user-level root on the daemon’s host:
| Target | Default skill root |
|---|---|
claude-code |
$HOME/.claude/skills |
codex |
$HOME/.agents/skills |
opencode |
$HOME/.config/opencode/skills |
These are deterministic installation defaults, not a search for existing
directories: the installer MUST NOT choose a different destination merely
because another root exists. $HOME refers to the daemon user’s absolute
home directory; if it is unavailable, the caller MUST provide directory
or receive invalid_params.skill_directory. Custom consumer profiles,
configuration roots and project-local installations use --directory.
The target names identify SKILL.md consumers, not permission profiles.
The installer MUST document the resolved path and that a custom root must
be discoverable by the intended consumer; /tmp above is a staging example.
The installer MUST create <resolved-root>/<tool>/SKILL.md with the exact bytes
returned by skill. Result fields are target, path (absolute file path)
and changed (boolean). Identical existing content is a successful no-op.
Different content MUST fail with conflict.skill_exists unless the optional
boolean replace is true (default false). Replacement MUST be atomic.
An invalid target/path is invalid_params; denied access is denied;
other filesystem failures are failed. The installer MUST enforce files
scope for the destination, refuse symlink traversal, and MUST NOT execute
the skill, install an agent, edit agent settings or grant itself rights.
Calling it via CLI or MCP changes files on the daemon’s host.
5. ZON configuration verbs
Configuration MUST be data-only ZON. No imports, executable expressions,
embedded scripts or runtime code loading are allowed. Each product MUST
publish its config location, defaults, key types, secret fields and which
settings require restart. The shared cfg implementation supplies parsing,
validation and atomic persistence; the runtime config schema is distinct
from the build-time verb manifest.
The following shared signatures keep arbitrary product config inside a ZON string, representable by RFC-0004 format 1 without arbitrary JSON maps. No positional arguments are used.
| Verb | Parameters | Success payload |
|---|---|---|
config get |
Optional string key, default "" (whole config) |
{"content":"<ZON value>"} |
config set |
Required strings key, value (one ZON value) |
{"changed":true,"restart_required":false} |
config check |
None | {"valid":true} |
config reload |
None | {"changed":true,"restart_required":false} |
config schema |
None | {"content":"<JSON Schema document>"} |
Keys are dotted object-field paths; empty key is permitted only for get.
Array elements are replaced by setting their containing field, not indexed
paths. get reads the persisted configuration with defaults applied;
it MUST redact secret values as the ZON string "<redacted>".
schema MUST describe the unredacted types and identify secret fields and
restart requirements in its generated documentation. The JSON Schema inside
content describes the JSON-equivalent config model, not ZON source syntax.
set MUST parse value, validate the entire resulting config and atomically
persist it, leaving the old file intact on failure. It MUST NOT implicitly
reload. check MUST validate persisted configuration without mutating it.
reload MUST validate before replacing the active snapshot; failure MUST
leave that snapshot intact. Non-reloadable settings remain active at their
previous values and set restart_required: true until a restart applies
them. For set, changed means persisted bytes changed; for reload, it
means the active snapshot changed. A repeated equivalent operation is a no-op.
Malformed ZON, unknown keys and invalid values use invalid_params;
authorization uses denied; I/O failures use failed. check failure is an
error wrapper, never a successful {"valid":false}. Missing config uses
documented defaults. The shared schema MUST enumerate refinement codes and
their data shapes before implementation. Agents MUST use config verbs
rather than manually editing the file.
Illustrative product config (the key is not a mandatory shared setting):
.{
.appearance = .{ .theme = "dark" },
}
venomx config set --key appearance.theme --value '"dark"' --json
venomx config check --json
venomx config reload --json
6. Doctor: checks and runnable fixes
doctor MUST be read-only and noninteractive, returning an object with
status and checks. Each check MUST have stable id, status (exactly
ok, warn or fail), human message, and fix, a runnable POSIX-shell
command string. A successful check’s fix is a harmless recheck command.
Commands MUST quote variable paths safely, omit credentials and never run
automatically. The human renderer MUST display the same statuses and fixes.
{"status":0,"checks":[{"id":"config.valid","status":"ok","message":"Configuration is valid","fix":"venom-echo config check --json"}]}
The schema MUST declare RFC-0004’s result exit policy on integer status,
bounded 0–255. Its value is 1 if any check fails, otherwise 0 (warnings alone
do not fail). A completed diagnosis with failing checks is still a wire
ok result so the report is preserved; inability to run the verb uses the
normal error mapping. MCP returns status as data.
Checks MUST cover config validity, runtime/socket permissions and path
length, plus CLI/daemon digest mismatch when a digest is advertised.
doctor accepts optional string parameter client_schema (absent by
default). The generated CLI MUST supply its compiled digest if the caller
does not supply one; MCP callers MAY supply their expected digest. The
daemon compares that value with its own advertised digest and reports a
mismatch as warn. Absence of either digest is not a failure. Product checks
SHOULD cover required executables and agent adapters. A failing check’s
fix MUST name an actionable repair or diagnostic command; unsafe repair
MUST instead direct the caller to a command that explains the needed steps.
doctor remains a remote shared verb under RFC-0004: if connection or path
validation fails before dispatch, the CLI MUST return the usual error and
exit (for example unavailable, 6), with a runnable diagnostic/fix command
in error.data.fix. It MUST NOT pretend a daemon report was received.
7. Grants and headless operation
The daemon MUST authorize before invoking a handler, regardless of whether
the caller used CLI, MCP, TUI, a keybinding or another daemon. Unknown callers
MUST receive at most read-only rights; they MUST NOT acquire mutation rights
merely by sharing a UID or socket access, unless isolation is off
(RFC-0005 section 11.1). That is a documented switch whose default each product
picks; venomx defaults to off, like tmux, and other products to on. read is still subject to resource
scope and secret redaction. none MUST be explicit. A protocol capability
from hello.caps negotiates features; it is never an authorization grant.
Identity, tokens, resource scopes, issuance, delegation, expiry and revocation
are deferred to RFC-0005 (COM-204); this RFC does not invent token formats.
All non-presentation operations MUST work with no TTY, display server or
attached TUI, and with stdin closed unless the documented transport requires
it (mcp). They MUST NOT open prompts or depend on terminal escape sequences
for completion. Missing rights MUST return denied (4); an approval request
MAY be represented by a documented verb/event, never by blocking for stdin.
The daemon MUST continue operating when all TUI clients detach.
8. OSC awareness and v1 extension boundary
Products that ingest PTY bytes (principally venomx through venom-term) MUST
recognize OSC 7501 program-status and OSC 133 shell-integration sequences.
Their parser, payload validation and conversion to events belong to RFC-0003
(COM-202). Other products consume those events through events; they are
not required to create PTYs solely for conformance. Raw PTY fan-out and
snapshot behavior remain RFC-0006 (COM-221).
OSC payloads MUST be treated as untrusted observations, never as grants or automatic verb invocations. Malformed or unknown sequences MUST NOT crash the daemon or corrupt control NDJSON. JSON output MUST NOT contain raw terminal control sequences; text values use JSON escaping.
v1 MUST NOT embed a scripting language, JIT or dynamically loaded plugins. Extension uses compiled verbs, events and explicitly configured hooks that launch executables under the caller’s grants. Hooks MUST NOT bypass grant checks. Running an external agent or executable is permitted; implementing a new agent runtime or spoofing a vendor CLI is outside the ecosystem goal.
Conformance checklist
Downstream repos SHOULD link to this section and record their version, verification command and evidence for each item. A Draft checklist supports prototypes; claiming the Accepted contract requires maintainer acceptance of this RFC and resolution of its Open questions.
- AC-01 — Binary: the same executable serves and invokes verbs; a second instance obeys RFC-0002 locking and socket selection. Verify each documented startup/lifetime mode, including concurrent auto-start where supported, and exit 6 when the selected policy cannot reach/start a daemon.
- AC-02 — Action parity: each application action/keybinding resolves to a declared verb with the same validation and rights as CLI/MCP.
- AC-03 — Generation: regenerate CLI/help, MCP, docs, completions and skill from one schema; check agreement on params, grants and errors.
- AC-04 — JSON: exercise success, usage/validation error and denied
call with
--json; parse stdout and check exits against section 3. - AC-05 — Streams: parse
events --jsonline by line; verify clean completion, terminal error and cancellation without a final success line. - AC-06 — Skills: compare displayed/generated/installed bytes for all three targets; test default roots and explicit override precedence, identical no-op, conflict and explicit replacement.
- AC-07 — Config: exercise all five verbs, invalid ZON/unknown keys, no-op writes, failed reload preserving active config, and secret redaction.
- AC-08 — Doctor: exercise ok/warn/fail, runnable quoted fixes, digest mismatch and daemon-unavailable error; verify JSON and exit status.
- AC-09 — Grants: verify read-only defaults, denied mutation before handler invocation, destination scope for skill installation and MCP parity.
- AC-10 — Headless: run verbs without a TTY and detach all TUIs; ensure there are no interactive prompts or loss of daemon operation.
- AC-11 — OSC: PTY consumers test valid/malformed OSC 7501/133 using RFC-0003 fixtures; other products record their event-consumer coverage.
- AC-12 — v1 boundary: inspect dependencies and extension entry points for scripting/JIT/dynamic plugins and grant-bypassing hooks.
Agent-contract impact
- Verbs: establishes the shared bundle and action/keybinding parity; RFC-0004 supplies the manifest and transport mappings.
- JSON / exits: adopts RFC-0002’s table; specifies bootstrap output and doctor’s schema-declared result status without changing wire errors.
- Events: requires NDJSON consumption; delegates envelope and OSC event definitions to RFC-0003 (COM-202).
- Skill / doctor / config: defines shared behavior and typed signatures; runtime config and verb schemas remain separate.
- Grants: requires enforced read-only defaults and explicit mutation rights; delegates identity and scope details to RFC-0005 (COM-204).
- Headless / OSC: requires terminal-independent execution and untrusted OSC ingestion/event conversion for products that handle PTYs.
Alternatives
- Separate daemon and CLI packages: easier independent releases, but creates avoidable schema/version skew; one executable is the ecosystem rule.
- Hand-maintained CLI/MCP/keybinding routes: duplicates validation and permission decisions, defeating reliable action parity.
- Human-text parsing or ad hoc exits: makes agents depend on rendering and wording; the Accepted wire RFC already defines structured behavior.
- Mandatory explicit skill roots: predictable but adds setup to every install. Deterministic per-consumer defaults cover normal installations; an explicit override handles custom profiles and project-local skills.
- Searching every consumer directory: can install to an unintended scope; select one documented user default rather than guessing from existing files.
- Automatic config reload on set: hides when side effects occur; separate persistence, validation and activation permit deliberate operation.
- Embedded scripts/plugins: increase runtime and authorization surface; compiled verbs and external executables satisfy v1 extension needs.
- Do nothing: leaves downstream repos without a conformance target and leaves shared behavior from RFC-0002/RFC-0004 underspecified.
Open questions
- Daemon auto-start: resolved by the maintainer (2026-11-06): startup
and lifetime policy belong to each product, which may provide multiple
modes. Explicit
serveremains available everywhere. Venomx is intended to support persistent and tmux-style modes; its defaults and detailed lifecycle belong to its product specification. See Design section 1. - Shared signatures: resolved by the maintainer (2026-11-06): use
automatic per-consumer user skill roots with an explicit
directoryoverride, and retain the section 5contentwrappers for ZON config values and JSON Schema text. See Design sections 4–5. - Events / OSC fixtures: deferred to existing RFC-0003 work (COM-202). AC-05/AC-11 require that RFC’s definitions, not invented examples here.
- Caller identity and scopes: deferred to existing RFC-0005 work (COM-204). AC-09 requires that RFC’s identity/scope cases as well as this contract’s denied-mutation checks.