Skip to content

Latest commit

 

History

History
111 lines (79 loc) · 8.65 KB

File metadata and controls

111 lines (79 loc) · 8.65 KB

Agent Forge plugin protocol v1

This protocol is authority separation and robust local IPC, not an OS sandbox. Plugins are trusted same-UID executables installed by the Worker operator. One fresh process handles one operation.

Transport and session

Each frame is one compact UTF-8 JSON object followed by LF. Stdout contains frames only; stderr is private diagnostics. Duplicate or unknown fields, invalid UTF-8, blank lines, CRLF, malformed JSON, frames over 1 MiB, and a final frame without LF are invalid.

The Worker generates a lowercase 32-hex id. Every frame has exactly version, id, type, and the fields shown below. version is exactly v1; IDs must match.

  1. Worker sends initialize, plugin sends initialized.
  2. Worker sends one execute.
  3. Plugin may send negotiated progress frames, then exactly one result or failure.
  4. Worker closes stdin and requires clean stdout EOF and exit 0 within 250 ms. Any trailing byte/frame or nonzero exit is a protocol failure.
{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"initialize","capabilities":["text"],"limits":{"frame_bytes":1048576,"progress_frames":128,"text_bytes":65536,"progress_text_bytes":1024,"commit_subject_bytes":256}}
{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"initialized","capabilities":["text"]}

Capabilities are closed: text, workspace_edit, progress, cancel, commit_subject, agent_report. initialized.capabilities is a duplicate-free subset of the offer and must include the operation capability. Unknown or unoffered selections fail.

Operations and terminals

Text execution and success:

{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"execute","operation":"text","input":"hello"}
{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"result","output":"HELLO"}

input and output are at most 65,536 UTF-8 bytes.

Workspace execution and success:

{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"execute","operation":"workspace_edit","workspace":"/operator/path","instruction":"edit the files","timeout_ms":60000}
{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"result","commit_subject":"feat: describe the edit"}

agent_report is an optional v1 capability. Workers offer it; plugins select it only when offered. Workspace results may contain summary and changes only when selected. Otherwise plugins omit both fields and receivers strictly reject either field, including null. Legacy commit_subject results remain valid. The v1 limits and version are unchanged.

The workspace result has the common fields, optional commit_subject, and an optional report pair: summary and changes. Legacy results without the report remain valid. A report has a non-empty summary of at most 1024 UTF-8 bytes and 1–12 non-empty change strings of at most 256 UTF-8 bytes each. Report strings must be trimmed and contain no Unicode control/format characters or U+2028/U+2029. Unknown, duplicate, malformed Unicode, or partially supplied report fields fail closed. The report is advisory and self-reported, never check evidence. Worker encodes it canonically into the existing candidate result only after successful checks and candidate creation; Gate persists it with the producing attempt. Progress frames remain transient and are not stored. If present, commit_subject requires negotiation, is 1..256 UTF-8 bytes, has no leading/trailing Unicode whitespace, Unicode control or format characters, U+2028/U+2029, or logical second line. When absent, Worker uses chore: apply coding task. Worker passes it as one argv element.

Progress requires negotiation, is limited to 128 frames, and has monotonically consecutive sequence numbers starting at 1. stage is one of started, working, finalizing; text is at most 1,024 UTF-8 bytes:

{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"progress","sequence":1,"stage":"started","text":"editing"}

Failure has category equal to invalid_request, incompatible, execution_failed, or cancelled. It describes only the plugin operation; Worker owns task disposition:

{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"failure","category":"execution_failed"}

If local context expires after negotiated execution, Worker sends exactly one cancel and allows 250 ms to drain. On Linux, each invocation runs under a child-subreaper wrapper that performs bounded, PID-identity-safe termination and reaping of invocation descendants after successful, failed, or cancelled completion, including ordinary and setsid descendants that remain attached or are reparented to the wrapper. This is best-effort cleanup for trusted local same-UID plugins, not hostile-plugin containment; without a cgroup or PID namespace, a deliberately adversarial process may escape tracking or interfere with the Worker. Other supported systems retain bounded ordinary process-group cleanup on cancellation. Cancellation is authoritative once observed before Run returns, including over concurrent success, exchange errors, or cleanup-induced wrapper exit. A hard output-limit violation wins only when Worker selects it while the context is still live; an already-ready cancellation wins instead of relying on random select choice.

{"version":"v1","id":"0123456789abcdef0123456789abcdef","type":"cancel"}

Progress and stderr stay Worker-local. Plugins cannot lease, retry, complete attempts, create candidate refs, or declare authoritative checks/state.

Conformance

Build forge-plugin-conformance, then pass the plugin executable after flags. It runs one valid session plus fresh-process malformed-request rejection scenarios and exits nonzero on any failure:

Invalid initialization must produce no stdout before a bounded nonzero exit. Invalid execution may produce exactly the deterministic initialized frame, then no other stdout, before a bounded nonzero exit. The suite rejects blank, malformed, invalid UTF-8, CRLF, non-compact, oversized, and unterminated frames; invalid IDs, fields, capabilities, limits, versions, operations, payloads, and frame order; and contradictory or duplicate execute fields.

go build -o /tmp/forge-plugin-conformance ./cmd/forge-plugin-conformance
/tmp/forge-plugin-conformance ./examples/forge-plugin-v1.py
/tmp/forge-plugin-conformance ./bin/forge-ref-plugin
CODEX_BIN=/path/to/fake-codex /tmp/forge-plugin-conformance -operation workspace_edit ./bin/forge-codex-plugin

scripts/plugin-conformance-e2e.sh builds and runs that suite against the shipped reference plugin, the dependency-free Python example, and the Codex workspace plugin with a local fake Codex binary.

Live operator activity

The progress capability under plugin protocol v1 retains exactly started, working, and finalizing. Old strict peers remain supported. ServeWithProgress serializes concurrent callbacks and closes progress before the terminal frame. The Codex plugin emits fixed lifecycle text only; executor stdout/stderr is never forwarded. Receivers validate negotiated capability, UTF-8, sequence, frame count and byte limits before invoking the progress callback.

A Worker separately offers activity_version=live_activity/v1 on its Gate connection. Only a Gate that recognizes that offer selects activity_version in its lease. An absent selection disables activity. Unknown lease versions and leases carrying an activity payload are rejected before execution. Activity payloads belong only to activity messages, with exact job, attempt and Worker identity and the selected version.

The Worker maps started to analyzing and working to editing, discarding plugin text. Legacy finalizing does not advance the operator stage before Worker checks. The Worker emits testing once before a nonempty declared-check batch and preparing_result once after the batch ends, including its existing stop-on-failure path. Empty check lists emit no testing stage. Stages advance strictly, with at most four events per attempt; no command names, output, prompts, JSON payloads or plugin timestamps enter activity.

Gate stamps receive time and projects the exact Run ID, Attempt ID/ordinal and Worker on each event. Storage is transient, bounded to 1,024 attempt entries and four closed-stage events per entry; terminal, disconnected and expired activity is removed. Current lease deadlines are consulted because heartbeats extend them. The open drawer refreshes every five seconds independently of backlog loading, uses text-only rendering, and keeps activity separate from Agent report, Forge verification and delivery evidence. There is no subagent telemetry. This activity is neither scoped-check evidence nor delivery acceptance evidence.