Skip to content

Latest commit

 

History

History
606 lines (412 loc) · 20.5 KB

external-agent-protocol.md

File metadata and controls

606 lines (412 loc) · 20.5 KB

External Agent Plugin Protocol

Overview

The Entire CLI supports external agent plugins — standalone binaries that implement the Agent interface via a subcommand-based protocol over stdin/stdout. This allows third-party agents to integrate with the CLI without modifying the main repository.

Discovery

The CLI discovers external agents by scanning $PATH for executables matching the pattern entire-agent-<name>. For example, entire-agent-cursor would register as the "cursor" agent.

Two rules bound that scan, both because discovery executes what it finds (it calls each binary's info subcommand):

  • It is off unless external_agents is set in an untracked .entire/settings.local.json. The committed .entire/settings.json cannot grant it — see Why external_agents is local-only. Interactive setup flows scan regardless, so you can still pick a plugin before the setting exists. Commands that name one agent (entire enable --agent <name>, entire agent add <name>, entire agent remove <name>) resolve only that binary, also regardless of the setting; entire agent list names none, so it scans only when the setting is on.

  • Only absolute $PATH entries are scanned. A relative entry resolves against the working directory, which for a git hook is whatever repository invoked it.

  • Binaries whose <name> conflicts with an already-registered built-in agent are skipped.

  • Discovery runs once during CLI initialization (before building the hooks command tree).

  • The binary must be executable and respond to the info subcommand.

Environment

Every subcommand invocation except generate-text sets:

Variable Description
ENTIRE_REPO_ROOT Absolute path to the git repository root
ENTIRE_PROTOCOL_VERSION Protocol version (1)

The working directory is set to the repository root.

generate-text is the exception. Its stdin is a summary prompt carrying untrusted transcript content, so it runs from a fresh empty temporary directory, without ENTIRE_REPO_ROOT and without GIT_* variables, the same isolation Entire gives its built-in summary generators. A plugin should not need the repository to generate text; if its model has tools, it should run them with none enabled, as the built-in generators do where their CLI allows.

Communication Model

  • Subcommand-based: Each Agent interface method maps to a CLI subcommand.
  • JSON over stdin/stdout: Structured data uses JSON. Transcripts use raw bytes.
  • Stateless: Each invocation is independent — no persistent connection.
  • Exit codes: 0 = success, non-zero = error. Error messages go to stderr.

Subcommands

Always Required

info

Returns agent metadata and declared capabilities.

Arguments: None

Output (stdout): JSON

{
  "protocol_version": 1,
  "name": "cursor",
  "type": "Cursor",
  "description": "Cursor - AI-powered code editor",
  "protected_dirs": [".cursor"],
  "hook_names": ["session-start", "session-end", "stop"],
  "capabilities": {
    "hooks": true,
    "transcript_analyzer": true,
    "transcript_preparer": false,
    "token_calculator": false,
    "text_generator": false,
    "hook_response_writer": false,
    "subagent_aware_extractor": false
  }
}

The capabilities object determines which optional subcommands the CLI will call. If a capability is false or missing, the CLI will never invoke the corresponding subcommands.

Optional: "caller_env_vars": ["MYAGENT_SESSION_ID"] lists environment variables the agent sets for the commands it runs (uppercase names only). entire review uses them to recognise the agent as the caller, so it refuses to approve reviews of someone else's code on the user's behalf and names the agent when one passes --trust-target. Agents that set the cross-tool AI_AGENT variable are recognised without declaring it.

detect

Checks whether the agent is present/usable in the current environment.

Arguments: None

Output (stdout): JSON

{"present": true}

get-session-id

Extracts the session ID from a hook input event.

Input (stdin): JSON — the HookInput object

Output (stdout): JSON

{"session_id": "abc123"}

get-session-dir --repo-path <path>

Returns the directory where agent sessions are stored.

Arguments:

  • --repo-path — Absolute path to the repository

Output (stdout): JSON

{"session_dir": "/path/to/sessions"}

resolve-session-file --session-dir <dir> --session-id <id>

Resolves the session file path from a session directory and ID.

Arguments:

  • --session-dir — Session directory path
  • --session-id — Session identifier

Output (stdout): JSON

{"session_file": "/path/to/session/file.jsonl"}

read-session

Reads session data from a hook input event.

Input (stdin): JSON — the HookInput object

Output (stdout): JSON — AgentSession object

{
  "session_id": "abc123",
  "agent_name": "cursor",
  "repo_path": "/path/to/repo",
  "session_ref": "/path/to/file.jsonl",
  "start_time": "2026-01-13T12:00:00Z",
  "native_data": null,
  "modified_files": ["src/main.go"],
  "new_files": [],
  "deleted_files": []
}

write-session

Writes/updates session data.

Input (stdin): JSON — the AgentSession object

Output: Exit 0 on success.

read-transcript --session-ref <path>

Reads a transcript file and returns its raw bytes.

Arguments:

  • --session-ref — Path to the transcript file

Output (stdout): Raw transcript bytes.

chunk-transcript --max-size <n>

Splits a transcript into chunks for storage.

Input (stdin): Raw transcript bytes.

Arguments:

  • --max-size — Maximum chunk size in bytes

Output (stdout): JSON

{"chunks": ["<base64-encoded-chunk>", "..."]}

reassemble-transcript

Reassembles transcript chunks back into a full transcript.

Input (stdin): JSON

{"chunks": ["<base64-encoded-chunk>", "..."]}

Output (stdout): Raw transcript bytes.

format-resume-command --session-id <id>

Returns the command a user can run to resume a session.

Arguments:

  • --session-id — Session identifier

Output (stdout): JSON

{"command": "cursor --resume abc123"}

Capability: hooks

These subcommands are required when capabilities.hooks is true.

parse-hook --hook <name>

Parses a raw agent hook payload into a structured event.

Arguments:

  • --hook — Hook name (e.g., "stop", "session-start")

Input (stdin): Raw agent hook payload bytes.

Output (stdout): JSON — the parsed Event object, or null if the payload is not relevant.

install-hooks [--force]

Installs agent hooks for Entire integration.

Arguments:

  • --force — Overwrite existing hooks (optional)

--local-dev was removed: it asked the agent to point hooks at a build inside the working tree, which meant installed hooks ran whatever the checked-out branch contained. The CLI never sends it. Agents that still accept the flag should treat it as a no-op; hook commands must name the entire binary.

Output (stdout): JSON

{"hooks_installed": 3}

uninstall-hooks

Removes installed agent hooks.

Arguments: None

Output: Exit 0 on success.

Only your binary can remove your hooks, so entire disable --uninstall cannot finish without you. A non-zero exit fails the whole uninstall: the command exits non-zero, reports that Entire was not fully removed, and prints the exact command line the user can run to invoke this subcommand by hand. Write anything the user needs in order to act to stderr — the CLI captures it and shows it verbatim.

are-hooks-installed

Checks whether hooks are currently installed.

Arguments: None

Output (stdout): JSON

{"installed": true}

A non-zero exit or unparseable stdout is not read as "no hooks" — it means the state is unknown, which the CLI must treat as "hooks may still be installed". entire disable --uninstall then exits non-zero and hands the user the manual uninstall-hooks command rather than claiming Entire was fully removed. Report "installed": false when you know there are none; fail only when you genuinely could not find out.

Capability: transcript_analyzer

Required when capabilities.transcript_analyzer is true.

get-transcript-position --path <path>

Returns the current byte position/size of a transcript file.

Arguments:

  • --path — Path to the transcript file

Output (stdout): JSON

{"position": 12345}

extract-modified-files --path <path> --offset <n>

Extracts the list of files modified by the agent from a transcript.

Arguments:

  • --path — Path to the transcript file
  • --offset — Byte offset to start reading from

Output (stdout): JSON

{"files": ["path/to/file1.go", "path/to/file2.go"], "current_position": 12345}

The current_position field returns the transcript position after extraction, allowing the caller to resume from that point on subsequent calls.

extract-prompts --session-ref <path> --offset <n>

Extracts user prompts from a transcript.

Arguments:

  • --session-ref — Path to the transcript file
  • --offset — Byte offset to start reading from

Output (stdout): JSON

{"prompts": ["first prompt text", "second prompt text"]}

Capability: transcript_preparer

Required when capabilities.transcript_preparer is true.

prepare-transcript --session-ref <path>

Prepares/processes a transcript file (e.g., converting from raw format).

Arguments:

  • --session-ref — Path to the transcript file

Output: Exit 0 on success.

Capability: compact_transcript

Required when capabilities.compact_transcript is true.

compact-transcript --session-ref <path>

Converts a native transcript into Entire Transcript Format (the transcript.jsonl stored next to full.jsonl in each checkpoint).

Arguments:

  • --session-ref — Path to a transcript file in the agent's native format. It may be a CLI temp file outside the agent's session store, so accept any regular file here.

Output (stdout): JSON

{"transcript": "<base64-encoded transcript.jsonl>", "assets": [{"name": "img-001.png", "media_type": "image/png", "data": "<base64>"}]}

assets is optional. The CLI calls this on each checkpoint write with a temp file of the already-redacted transcript, and redacts the result again. It also compacts the lines before the checkpoint's start to scope the checkpoint: only leading output lines identical in both runs count as earlier checkpoints' content, so a line that changes once later input arrives (for example a tool call that absorbs its result) stays in this checkpoint. Each run has a 5 second limit, and stdout may be up to the base64 size of a 50 MB transcript. On failure, timeout, empty output, or assets, the CLI uses its built-in compactor instead. entire explain also uses it for summaries.

Capability: token_calculator

Required when capabilities.token_calculator is true.

calculate-tokens --offset <n>

Calculates token usage from a transcript.

Input (stdin): Raw transcript bytes.

Arguments:

  • --offset — Byte offset to start calculating from

Output (stdout): JSON

{
  "input_tokens": 1500,
  "cache_creation_tokens": 0,
  "cache_read_tokens": 200,
  "output_tokens": 500,
  "api_call_count": 3,
  "subagent_tokens": null
}

Only input_tokens and output_tokens are required. The optional fields (cache_creation_tokens, cache_read_tokens, api_call_count) default to 0 if omitted. subagent_tokens is an optional nested object with the same structure, for agents that spawn subagents.

Capability: text_generator

Required when capabilities.text_generator is true.

External agents that declare capabilities.text_generator: true can be used as summary providers for entire explain --generate and checkpoint auto-summarize. Configure them with:

entire configure --summarize-provider <agent-name> [--summarize-model <model>]

The provider name is derived from the executable name entire-agent-<agent-name>. The CLI passes Entire's summary prompt on stdin to generate-text --model <model> and expects {"text":"..."} on stdout.

generate-text --model <model>

Generates text using the agent's underlying LLM.

Input (stdin): Prompt text.

Arguments:

  • --model — Model to use for generation

Output (stdout): JSON

{"text": "Generated response text"}

Capability: hook_response_writer

Required when capabilities.hook_response_writer is true.

write-hook-response --message <message>

Writes a message in the agent's native hook response format.

Arguments:

  • --message — Message to write

Output (stdout): Agent-native format bytes (e.g., JSONL for Claude Code).

Capability: subagent_aware_extractor

Required when capabilities.subagent_aware_extractor is true.

extract-all-modified-files --offset <n> --subagents-dir <dir>

Extracts modified files from both the main transcript and any subagent transcripts.

Input (stdin): Raw main transcript bytes.

Arguments:

  • --offset — Byte offset for the main transcript
  • --subagents-dir — Directory containing subagent transcripts

Output (stdout): JSON

{"files": ["file1.go", "file2.go"]}

calculate-total-tokens --offset <n> --subagents-dir <dir>

Calculates total token usage across main transcript and subagent transcripts.

Input (stdin): Raw main transcript bytes.

Arguments:

  • --offset — Byte offset for the main transcript
  • --subagents-dir — Directory containing subagent transcripts

Output (stdout): JSON

{
  "input_tokens": 5000,
  "cache_creation_tokens": 0,
  "cache_read_tokens": 1000,
  "output_tokens": 2000,
  "api_call_count": 8,
  "subagent_tokens": {
    "input_tokens": 2000,
    "output_tokens": 800
  }
}

Same token usage format as calculate-tokens. The subagent_tokens field aggregates usage from all subagents.

Shared Object Definitions

HookInput Object

The HookInput object is passed via stdin to get-session-id and read-session.

{
  "hook_type": "stop",
  "session_id": "abc123",
  "session_ref": "/path/to/transcript.jsonl",
  "timestamp": "2026-01-13T12:00:00Z",
  "user_prompt": "Fix the login bug",
  "tool_name": "Write",
  "tool_use_id": "toolu_abc123",
  "tool_input": {"path": "/src/main.go"},
  "raw_data": {"custom_field": "value"}
}
Field Type Description
hook_type string Hook type: session_start, session_end, user_prompt_submit, stop, pre_tool_use, post_tool_use
session_id string Agent session identifier
session_ref string Agent-specific session reference (typically a file path)
timestamp string RFC 3339 timestamp
user_prompt string User's prompt text (from user_prompt_submit hooks). Optional.
tool_name string Tool name (from pre_tool_use/post_tool_use hooks). Optional.
tool_use_id string Tool invocation ID. Optional.
tool_input object Raw tool input JSON. Optional.
raw_data object Agent-specific data for extension. Optional.

AgentSession Object

Used as input to write-session and output from read-session.

{
  "session_id": "abc123",
  "agent_name": "cursor",
  "repo_path": "/path/to/repo",
  "session_ref": "/path/to/transcript.jsonl",
  "start_time": "2026-01-13T12:00:00Z",
  "native_data": null,
  "modified_files": ["src/main.go"],
  "new_files": ["src/new_file.go"],
  "deleted_files": []
}
Field Type Description
session_id string Agent session identifier
agent_name string Agent registry name
repo_path string Absolute path to the repository
session_ref string Path/reference to session in agent's storage. Constrained — see session_ref constraints
start_time string RFC 3339 timestamp of session start
native_data bytes/null Session content in agent's native format (opaque to CLI)
modified_files string[] Files modified during the session
new_files string[] Files created during the session
deleted_files string[] Files deleted during the session

session_ref constraints

session_ref stays agent-defined: a plugin backed by a database may return an opaque key rather than a path, and the CLI forwards such a value to write-session unchanged. Which checks apply depends on the shape of the value, and one of them also depends on whether repo_path was supplied.

A reference that is absolute, or that carries a volume name (/home/u/.agentx/sessions/abc.jsonl, C:\Users\u\...) is treated as a filesystem path. It must contain no . or .. component — always, whether or not repo_path is set. When repo_path IS set, it must additionally resolve inside the directory the plugin itself reported from get-session-dir; without repo_path there is no session directory to resolve against, so that containment check does not run and the reference is forwarded.

Any other reference is treated as an agent-defined key. It must not be rooted, and once cleaned it must not escape its own base, so ../outside.jsonl and nested/../../outside.jsonl are refused. A key that merely contains a dot segment without escaping, such as tenant/../session-key, is forwarded as given. Note that the rooted check is reachable only on Windows: on Unix a leading separator makes the reference absolute, so it takes the filesystem branch above.

This is a preflight, not a sandbox. The plugin runs as its own process and can write wherever its own permissions allow; the check exists so the CLI does not hand it a reference that leaves the store the plugin named.

Event Object

Returned by parse-hook. Represents a normalized lifecycle event.

{
  "type": 3,
  "session_id": "abc123",
  "session_ref": "/path/to/transcript.jsonl",
  "prompt": "Fix the login bug",
  "model": "claude-sonnet-4-20250514",
  "timestamp": "2026-01-13T12:00:00Z"
}
Field Type Description
type int Required. Event type (see table below)
session_id string Required. Agent session identifier
previous_session_id string Non-empty when this event represents a session continuation/handoff. Optional.
session_ref string Agent-specific transcript reference. Optional.
prompt string User's prompt text (on TurnStart events). Optional.
model string LLM model identifier. Optional.
timestamp string RFC 3339 timestamp. Optional.
tool_use_id string Tool invocation ID (for SubagentStart/SubagentEnd). Optional.
subagent_id string Subagent instance ID (for SubagentEnd). Optional.
tool_input object Raw tool input JSON (for subagent type/description extraction). Optional.
subagent_type string Kind of subagent (for SubagentStart/SubagentEnd). Optional.
task_description string Subagent task description. Optional.
response_message string Message to display to the user via the agent. Optional.
metadata object Agent-specific state preserved across events. Optional.

Event types:

Value Name Description
1 SessionStart Agent session has begun
2 TurnStart User submitted a prompt, agent is about to work
3 TurnEnd Agent finished responding to a prompt
4 Compaction Agent is compressing its context window (triggers save + offset reset)
5 SessionEnd Session has been terminated
6 SubagentStart A subagent (task) has been spawned
7 SubagentEnd A subagent (task) has completed

Error Handling

  • Exit code 0 indicates success.
  • Any non-zero exit code indicates an error.
  • Error messages should be written to stderr.
  • The CLI captures stderr and wraps it in a Go error.
  • If the binary is not found in PATH, the agent is simply not registered.
  • If info fails or returns invalid JSON, the binary is skipped during discovery.

Versioning

The protocol version is declared in the info response (protocol_version field) and set via ENTIRE_PROTOCOL_VERSION environment variable. The CLI checks that the binary's protocol version matches its expected version before registering the agent.

Future protocol versions may add new subcommands or capabilities. Existing subcommands will maintain backwards compatibility within a major version.