claude-cli-agent-protocol
Claude CLI Agent Protocol
Overview
Integrate Claude Code CLI via NDJSON in a way that preserves true runtime state in the host UI:
- parse all relevant stdout message types
- send correctly shaped stdin control messages
- handle replay and partial-stream behavior explicitly
- keep interrupt and approval flows consistent
This skill is optimized for host builders who need reliable liveness, queueing, replay dedup, and approval UX.
Required CLI Flags
claude \
-p \
--output-format stream-json \
--input-format stream-json \
--verbose \
--permission-prompt-tool stdio
Optional but important:
--replay-user-messages- Re-emits user stdin messages on stdout for acknowledgment.
- Echoed user messages include
isReplay,uuid, andparent_tool_use_id.
--include-partial-messages- Required if your host expects
stream_eventpartial deltas (message_start,content_block_delta, etc.). - Without this flag, many runs emit only final
assistant+resultturn outputs.
- Required if your host expects
Important flag constraints:
--input-format=stream-jsonrequires--output-format=stream-json.--replay-user-messagesrequires stream-json input and output.--include-partial-messagesworks with--printand stream-json output.- Do not rely on prompt positional arg when building a long-lived host; send
userenvelopes via stdin.
Protocol Shape (NDJSON)
All messages are newline-delimited JSON objects with top-level type.
For streaming, the runtime envelope is:
{
"type": "stream_event",
"event": {
"type": "content_block_delta",
"index": 0,
"delta": {"type": "text_delta", "text": "chunk"}
}
}
Do not assume top-level content_block_delta envelopes in host parsing.
Emission Semantics (Critical)
Partial output behavior
- With
--include-partial-messages: host receivesstream_eventpayloads. - Without it: host often receives only final
assistantandresultmessages.
Host impact:
- If UI "Thinking/Writing" depends only on partial deltas, it may appear idle/blank unless
--include-partial-messagesis enabled.
Replay behavior
With --replay-user-messages, echoed user messages include replay markers:
{
"type": "user",
"message": {"role": "user", "content": [{"type": "text", "text": "ping"}]},
"session_id": "...",
"parent_tool_use_id": null,
"uuid": "...",
"isReplay": true
}
Host rule:
- Deduplicate using
uuid(plus turn/session context if needed).
Result semantics
result.subtype == "success" does not mean no error by itself.
Always evaluate is_error as authoritative:
{
"type": "result",
"subtype": "success",
"is_error": true,
"result": "Not logged in - Please run /login"
}
This can also accompany non-zero process exit status.
Request Lifecycles
Normal request (init -> response -> result)
- Host starts process.
- CLI emits
systemsubtype:init. - Host sends
userstdin message. - CLI may emit
stream_eventmessages (if partial enabled). - CLI emits
assistantmessage(s). - CLI may emit
control_request(tool approval). - Host sends
control_response. - CLI emits
usertool_result echoes. - CLI emits terminal
result.
Interrupted request
Preferred:
- Host sends stdin
control_requestwithrequest.subtype: "interrupt". - CLI emits
control_responsesuccess. - CLI emits
resultfor terminated turn. - Host sends next queued
usermessage.
Interrupt during pending tool approval
Two valid strategies:
- Cancel then interrupt:
- Send
control_cancel_requestfor pendingrequest_id. - Send
control_requestinterrupt.
- Deny + interrupt in one response:
- Send
control_responsewithbehavior: "deny"andinterrupt: true.
Pick one strategy and apply it consistently.
Stdout Message Types (CLI -> Host)
These are the host-relevant types to parse.
system
Common subtypes:
initstatusinformationalapi_errorhook_startedhook_progresshook_responsestop_hook_summarytask_notificationturn_durationcompact_boundarymicrocompact_boundarylocal_command
Typical init fields include: cwd, session_id, tools, mcp_servers, model, permissionMode, slash_commands, claude_code_version, agents, skills, plugins, uuid.
assistant
Contains complete assistant message blocks:
texttool_usethinking(if enabled)
May include parent_tool_use_id for subagent/tool-threaded messages.
user
Used for:
- tool result echo blocks (
type: tool_result) - replayed user echo events when replay flag is enabled (
isReplay: true)
stream_event
Contains nested event payload.
Event types:
message_startcontent_block_startcontent_block_deltacontent_block_stopmessage_deltamessage_stop
Observed delta types:
text_deltainput_json_deltathinking_deltasignature_deltacitations_delta
control_request
Approval or control prompt from CLI, including:
request.subtype: "can_use_tool"request_id- tool details (
tool_name,input,tool_use_id, optionaldecision_reason)
control_response
Control command acknowledgment emitted by CLI (for host-issued control requests like interrupt/set mode/model).
control_cancel_request
Cancellation signal path exists in protocol surface for pending control requests. Treat as control-plane event.
result
Terminal turn summary with:
subtypeis_error- token/cost/turn metadata
Result subtypes:
successerror_during_executionerror_max_turnserror_max_budget_usderror_max_structured_output_retries
keep_alive
Heartbeat ping message.
tool_use_summary
Optional summarized tool-use event stream for condensed status rendering.
auth_status
Optional authentication status updates.
streamlined_text and streamlined_tool_use_summary
Optional condensed output channels. Useful for thin UIs; safe to ignore in fully structured hosts.
Observed Type and Subtype Surface (2.1.37)
Observed host-facing top-level type values (runtime + binary scan):
systemassistantuserstream_eventresultcontrol_requestcontrol_responsecontrol_cancel_requestkeep_alivetool_use_summaryauth_statusstreamlined_textstreamlined_tool_use_summary
Observed protocol-relevant subtype values from local 2.1.37 binary:
api_errorcan_use_toolcompact_boundaryerrorerror_during_executionerror_max_budget_usderror_max_structured_output_retrieserror_max_turnshook_callbackhook_progresshook_responsehook_startedinformationalinitinterruptlocal_commandmcp_messagemicrocompact_boundarystatusstop_hook_summarysuccesstask_notificationturn_duration
Stdin Message Types (Host -> CLI)
user
{
"type": "user",
"message": {
"role": "user",
"content": [{"type": "text", "text": "Your prompt"}]
},
"uuid": "optional-client-uuid"
}
control_request
{
"type": "control_request",
"request_id": "req_123",
"request": {
"subtype": "interrupt"
}
}
Supported subtypes (host-facing):
interruptinitializeset_permission_modeset_modelset_max_thinking_tokensmcp_statusmcp_messagemcp_set_serversmcp_reconnectmcp_togglerewind_files
control_response
Use for answering can_use_tool approval requests.
Allow:
{
"type": "control_response",
"response": {
"subtype": "success",
"request_id": "req_abc123",
"response": {
"behavior": "allow",
"updatedInput": {"command": "ls -la"}
}
}
}
Deny:
{
"type": "control_response",
"response": {
"subtype": "success",
"request_id": "req_abc123",
"response": {
"behavior": "deny",
"message": "User denied"
}
}
}
Deny + interrupt:
{
"type": "control_response",
"response": {
"subtype": "success",
"request_id": "req_abc123",
"response": {
"behavior": "deny",
"message": "User denied and wants to stop",
"interrupt": true
}
}
}
control_cancel_request
Cancel pending control request by request_id.
{
"type": "control_cancel_request",
"request_id": "req_abc123"
}
Tool Names and Input Fields
Common tool names and key inputs:
| Tool | Input Fields |
|---|---|
Bash |
command, description, timeout, run_in_background |
Read |
file_path, offset, limit |
Write |
file_path, content |
Edit |
file_path, old_string, new_string, replace_all |
Glob |
pattern, path |
Grep |
pattern, path, glob, type, output_mode, -A, -B, -C, -i, -n |
WebFetch |
url, prompt |
WebSearch |
query, allowed_domains, blocked_domains |
Task |
subagent_type, description, prompt, model, run_in_background, resume |
TaskOutput |
task_id, block, timeout |
TaskStop |
task_id |
NotebookEdit |
notebook_path, new_source, cell_id, cell_type, edit_mode |
AskUserQuestion |
questions |
EnterPlanMode |
no params |
ExitPlanMode |
allowedPrompts, pushToRemote |
Skill |
skill, args |
TaskCreate |
subject, description, activeForm, metadata |
TaskGet |
taskId |
TaskUpdate |
taskId, status, subject, description, owner, addBlocks, addBlockedBy |
TaskList |
no params |
Liveness Mapping for Host UI
Recommended runtime state mapping:
process started->startingsystem:init->readystream_eventor in-flight assistant turn ->thinking/streamingcontrol_request can_use_tool->awaiting_approvalresult->idle(orerrorifis_error=true)- process exit / fatal stderr / repeated auth failures ->
disconnected/error
Always expose last event timestamp so users can trust the session is alive.
Process Interruption
Preferred: protocol interrupt (control_request subtype:interrupt).
Fallback: OS signal only if protocol interrupt path fails or times out.
kill(processId, SIGINT);
process.terminate();
process.waitForFinished(500);
if (process.state() != NotRunning) process.kill();
Replay and Partial Handling Rules for Hosts
- Treat replayed user messages as acknowledgments, not new user intent.
- Deduplicate by
uuidbefore rendering persistent chat bubbles. - Merge
stream_eventdeltas into one in-progress assistant draft per turn. - Replace/complete draft on final
assistantorresult. - Do not infer completion from lack of partial events if partial streaming is disabled.
Debugging
DEBUG_CLAUDE_AGENT_SDK=1 claude --debug ...
Capture debug logs:
claude ... --debug --debug-file /tmp/claude-debug.log
Protocol Discovery (Installed Binary)
Inspect local type and subtype surfaces for the exact installed version:
strings /path/to/claude | grep -o 'type:"[a-z_][a-z_]*"' | sort -u
strings /path/to/claude | grep -o 'subtype:"[a-z_][a-z_]*"' | sort -u
Sanity-check stream-json runtime quickly:
tmpid=$(uuidgen | tr '[:upper:]' '[:lower:]')
printf '%s\n' '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"ping"}]},"uuid":"'"$tmpid"'"}' \
| claude -p --output-format stream-json --input-format stream-json --verbose --replay-user-messages --permission-prompt-tool stdio --session-id "$tmpid"
References
- Headless mode: https://docs.anthropic.com/en/docs/claude-code/headless
- CLI reference: https://docs.anthropic.com/en/docs/claude-code/cli