AgentSkillsCN

claude-cli-agent-protocol

指导 Qt/QML 聊天附件行与消息气泡的布局:包括流式换行、图片/文件芯片对齐、左右对齐,以及宽度与高度尺寸上的细微差异。当修复 Qt Quick/QML 中垂直堆叠、换行不当,或整体宽度显示异常的附件布局时,可参考此建议。

SKILL.md
--- frontmatter
name: claude-cli-agent-protocol
description: >-
  Guidance for integrating Claude Code CLI in agent/headless mode: required stream-json flags, NDJSON protocol, control_request/control_response tool approvals, and process management. Use when building a host that drives Claude CLI or troubleshooting permission prompts and tool approvals.

Claude CLI Agent Protocol

Overview

Enable reliable, bidirectional integration with Claude Code CLI via NDJSON, including tool approval handling, permission mode control, and long-lived process management.

Required CLI Flags

bash
claude \
  --output-format stream-json \
  --input-format stream-json \
  --verbose \
  --permission-prompt-tool stdio

Notes:

  • --permission-prompt-tool stdio enables control_request/control_response for tool approvals
  • --verbose includes system messages, hook events, and stream events
  • --replay-user-messages echoes user messages back on stdout for acknowledgment
  • Do not use -p with a prompt; send user messages via stdin

Message Protocol (NDJSON)

All stdin/stdout messages are newline-delimited JSON with a type field.


Request Lifecycles

Normal Request (Init → Result)

code
Host                                      CLI
  │                                         │
  │  ──── Start process with flags ────►    │
  │                                         │
  │  ◄──── system {subtype: "init"} ──────  │
  │                                         │
  │  ──── user message ───────────────►     │
  │                                         │
  │  ◄──── stream_event (deltas) ─────────  │
  │  ◄──── assistant ─────────────────────  │
  │                                         │
  │  ◄──── control_request ───────────────  │  (tool approval)
  │        {subtype: "can_use_tool"}        │
  │                                         │
  │  ──── control_response ───────────►     │
  │       {behavior: "allow"}               │
  │                                         │
  │  ◄──── user {tool_result} ────────────  │
  │  ◄──── assistant (final) ─────────────  │
  │                                         │
  │  ◄──── result {subtype: "success"} ───  │
  │                                         │
  │  ──── user message (next turn) ────►    │  (process stays alive)

Sequence:

  1. system (init) — session info, tools, model
  2. Host sends user message
  3. stream_event messages (real-time deltas)
  4. assistant message (complete response with tool_use blocks)
  5. control_request for each tool needing approval
  6. Host sends control_response (allow/deny)
  7. user message (tool results echoed back)
  8. More assistant messages if multi-turn
  9. result — turn complete, cost/usage data

Interrupted Request

code
Host                                      CLI
  │                                         │
  │  ◄──── system {subtype: "init"} ──────  │
  │                                         │
  │  ──── user message ───────────────►     │
  │       "Refactor entire codebase"        │
  │                                         │
  │  ◄──── stream_event ──────────────────  │
  │  ◄──── assistant ─────────────────────  │
  │  ◄──── control_request (tool) ────────  │
  │  ──── control_response (allow) ────►    │
  │  ◄──── user {tool_result} ────────────  │
  │  ◄──── stream_event ──────────────────  │  (working...)
  │                                         │
  │  ════ USER INTERRUPTS ════              │
  │                                         │
  │  ──── control_request ────────────►     │
  │       {subtype: "interrupt"}            │
  │                                         │
  │  ◄──── control_response ──────────────  │
  │        {subtype: "success"}             │
  │                                         │
  │  ◄──── result ────────────────────────  │  (turn ends)
  │                                         │
  │  ──── user message ───────────────►     │  (new instruction)
  │       "Actually, just fix the bug"      │
  │                                         │
  │  ◄──── stream_event ──────────────────  │
  │  ◄──── assistant ─────────────────────  │
  │  ◄──── result {subtype: "success"} ───  │

Key points:

  • Interrupt is graceful — process stays alive, context preserved
  • CLI acknowledges with control_response
  • result emitted after interrupt (partial work visible in prior messages)
  • Send new user message to redirect agent

Interrupt During Tool Approval

When a control_request (can_use_tool) is pending and you want to interrupt, use deny with interrupt flag:

code
Host                                      CLI
  │                                         │
  │  ◄──── control_request ───────────────  │  (waiting for approval)
  │        {request_id: "req_001"}          │
  │                                         │
  │  ──── control_response ───────────►     │  (deny + interrupt)
  │       {behavior: "deny",                │
  │        message: "...",                  │
  │        interrupt: true}                 │
  │                                         │
  │  ◄──── result ────────────────────────  │  (turn ends immediately)

This denies the pending tool AND aborts the current turn in a single message.


Stdout Message Types (CLI → Host)

system - Session and Status Messages

SubtypeDescription
initSession initialization with model, tools, session_id, cwd
api_errorAPI error occurred
informationalInformational message
statusStatus update
hook_startedHook execution started
hook_progressHook execution progress
hook_responseHook execution response
stop_hook_summarySummary when hooks are stopped
task_notificationBackground task notification
turn_durationTurn timing information
compact_boundaryContext compaction boundary
microcompact_boundaryMicro-compaction boundary
local_commandLocal slash command execution

Example init:

json
{
  "type": "system",
  "subtype": "init",
  "session_id": "abc123",
  "cwd": "/path/to/project",
  "model": "claude-sonnet-4-20250514",
  "tools": [{"name": "Bash", "type": "computer_use"}, ...]
}

assistant - Claude's Response

json
{
  "type": "assistant",
  "message": {
    "id": "msg_xxx",
    "role": "assistant",
    "content": [
      {"type": "text", "text": "Here's the solution..."},
      {"type": "tool_use", "id": "toolu_xxx", "name": "Bash", "input": {"command": "ls"}}
    ],
    "usage": {"input_tokens": 100, "output_tokens": 50}
  },
  "parent_tool_use_id": null,
  "session_id": "abc123"
}

Content block types:

  • text - Text content with text field
  • tool_use - Tool invocation with id, name, input
  • thinking - Extended thinking content (when enabled)

user - Tool Results

Echoed when tool execution completes:

json
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [
      {"type": "tool_result", "tool_use_id": "toolu_xxx", "content": "output here"}
    ]
  }
}

stream_event - Streaming Events

Real-time content deltas during generation:

json
{
  "type": "stream_event",
  "event": {
    "type": "content_block_delta",
    "index": 0,
    "delta": {"type": "text_delta", "text": "chunk"}
  }
}

Event types:

  • message_start - Message generation started
  • content_block_start - New content block
  • content_block_delta - Content chunk (text_delta, input_json_delta)
  • content_block_stop - Content block complete
  • message_delta - Message-level updates (stop_reason, usage)
  • message_stop - Message complete

result - Completion Message

SubtypeDescription
successSuccessful completion
error_during_executionError during tool execution
error_max_turnsMaximum turns exceeded
error_max_budget_usdBudget limit exceeded
error_max_structured_output_retriesStructured output validation failed

Example:

json
{
  "type": "result",
  "subtype": "success",
  "duration_ms": 5000,
  "duration_api_ms": 4500,
  "num_turns": 3,
  "total_cost_usd": 0.05,
  "usage": {"input_tokens": 1000, "output_tokens": 500},
  "session_id": "abc123",
  "is_error": false
}

control_request - Approval Required

Sent when CLI needs host approval (tool execution, etc.):

json
{
  "type": "control_request",
  "request_id": "req_abc123",
  "request": {
    "subtype": "can_use_tool",
    "tool_name": "Bash",
    "input": {"command": "rm -rf /tmp/test", "description": "Delete test files"},
    "tool_use_id": "toolu_xyz"
  }
}

keep_alive - Connection Ping

json
{"type": "keep_alive"}

Stdin Message Types (Host → CLI)

user - Send User Message

json
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [{"type": "text", "text": "Your prompt here"}]
  },
  "uuid": "optional-unique-id"
}

control_request - Host Control Commands

SubtypeDescriptionResponse
interruptAbort current operationsuccess
initializeInitialize with SDK MCP serverssuccess with init info
set_permission_modeChange mode (plan, default, etc.)success
set_modelChange modelsuccess
set_max_thinking_tokensSet thinking token limitsuccess
mcp_statusGet MCP server statussuccess with mcpServers
mcp_messageSend message to MCP serversuccess
mcp_set_serversConfigure MCP serverssuccess with result
mcp_reconnectReconnect to MCP serversuccess or error
mcp_toggleEnable/disable MCP serversuccess or error
rewind_filesRewind file changessuccess with result

Interrupt Example

Send:

json
{
  "type": "control_request",
  "request_id": "int_001",
  "request": {"subtype": "interrupt"}
}

Receive:

json
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "int_001",
    "response": {}
  }
}

Set Permission Mode Example

json
{
  "type": "control_request",
  "request_id": "mode_001",
  "request": {
    "subtype": "set_permission_mode",
    "mode": "plan"
  }
}

Modes: default, plan, bypassPermissions, acceptEdits

Set Model Example

json
{
  "type": "control_request",
  "request_id": "model_001",
  "request": {
    "subtype": "set_model",
    "model": "claude-opus-4-20250514"
  }
}

control_response - Tool Approval Response

When CLI sends control_request with subtype: "can_use_tool":

Allow (must include updatedInput):

json
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "req_abc123",
    "response": {
      "behavior": "allow",
      "updatedInput": {"command": "ls -la", "description": "List files"}
    }
  }
}

Deny (must include message):

json
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "req_abc123",
    "response": {
      "behavior": "deny",
      "message": "User denied this action"
    }
  }
}

Deny and Abort Turn (add interrupt: true to also abort the current turn):

json
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "req_abc123",
    "response": {
      "behavior": "deny",
      "message": "User denied and wants to stop",
      "interrupt": true
    }
  }
}

When interrupt: true is set, the CLI will:

  1. Deny the tool execution
  2. Call abortController.abort() to stop the current turn
  3. Emit a result message

This is useful when the user wants to both reject the tool and redirect Claude to do something else.

Error:

json
{
  "type": "control_response",
  "response": {
    "subtype": "error",
    "request_id": "req_abc123",
    "error": "Timeout waiting for user"
  }
}

Tool Names and Input Fields

ToolInput Fields
Bashcommand, description, timeout, run_in_background
Readfile_path, offset, limit
Writefile_path, content
Editfile_path, old_string, new_string, replace_all
Globpattern, path
Greppattern, path, glob, type, output_mode, -A, -B, -C, -i, -n
WebFetchurl, prompt
WebSearchquery, allowed_domains, blocked_domains
Tasksubagent_type, description, prompt, model, run_in_background, resume
TaskOutputtask_id, block, timeout
TaskStoptask_id
NotebookEditnotebook_path, new_source, cell_id, cell_type, edit_mode
AskUserQuestionquestions array
EnterPlanMode(no params)
ExitPlanModeallowedPrompts, pushToRemote
Skillskill, args
TaskCreatesubject, description, activeForm, metadata
TaskGettaskId
TaskUpdatetaskId, status, subject, description, owner, addBlocks, addBlockedBy
TaskList(no params)

Process Interruption

Two mechanisms exist:

1. Protocol-based Interrupt (Recommended)

Send control_request with subtype: "interrupt":

json
{
  "type": "control_request",
  "request_id": "int_001",
  "request": {"subtype": "interrupt"}
}

This aborts the current operation and receives a control_response confirmation.

2. OS Signal (Fallback)

cpp
// Send SIGINT for graceful abort
kill(m_process->processId(), SIGINT);

// Or terminate then kill
m_process->terminate();  // SIGTERM
m_process->waitForFinished(500);
if (m_process->state() != QProcess::NotRunning)
    m_process->kill();  // SIGKILL

Subagent Protocol

The Task tool spawns subagents. Track responses via parent_tool_use_id:

json
{
  "type": "assistant",
  "parent_tool_use_id": "toolu_abc",
  "message": {"content": [...]}
}

Cost Tracking

The result message includes:

  • total_cost_usd - Total API cost
  • usage - Token counts (input_tokens, output_tokens, cache_read_input_tokens)
  • permission_denials - List of denied tool actions

Debugging

bash
DEBUG_CLAUDE_AGENT_SDK=1 claude --debug ...

SDK Protocol Discovery

Inspect the TypeScript SDK for undocumented behaviors:

bash
cd /tmp && npm pack @anthropic-ai/claude-code && tar -xzf *.tgz

# Find message types
grep -o 'type:"[^"]*"' package/cli.js | sort -u

# Find subtypes
grep -o 'subtype:"[^"]*"' package/cli.js | sort -u

# Find control request handling
rg 'request\.subtype===' package/cli.js

References