# Claude Code Agent SDK 运行时模式

> Claude Code: Agent SDK 的 custom tools、system prompt、streaming、structured output、tool search、用户审批和 slash commands 运行时设计。

URL: https://docs.passion8.cc/docs/claude-code/sdk-runtime-patterns
Language: zh-CN
Publisher: Passion8

Agent SDK 不只是把一句 prompt 发给模型。它会启动 Claude Code agent loop,加载工具、权限、settings、CLAUDE.md、hooks 和 MCP。生产应用要把这些运行时模式设计清楚,否则很容易出现工具暴露过多、缓存命中差、审批卡死或结构化输出不可控。

先看 [Agent SDK](https://docs.passion8.cc/docs/claude-code/sdk) 理解基本接入。TypeScript/Python API、session API、settings 解析和迁移差异见 [Agent SDK API 参考速查](https://docs.passion8.cc/docs/claude-code/sdk-api-reference)。Agent loop、settingSources、skills、subagents、todo tracking 和 checkpointing 见 [Agent SDK Agent 能力](https://docs.passion8.cc/docs/claude-code/sdk-agent-features)。多租户和观测继续看 [Agent SDK 生产部署](https://docs.passion8.cc/docs/claude-code/sdk-production)。

## 运行时决策表

| 目标 | 推荐能力 | 关键风险 |
| --- | --- | --- |
| 让 Claude 调应用内函数 | Custom tools, in-process MCP server | schema 太泛、错误直接 throw、未限制 allowed tools |
| 让 agent 像 Claude Code | `claude_code` system prompt preset | 自定义 prompt 覆盖了安全与工具指导 |
| 长任务实时 UI | streaming output | 没有处理中间 tool call 和错误 result |
| 写数据库或任务系统 | structured output | schema 不稳定、重试失败未处理 |
| 几百个工具 | tool search | Provider 不支持 `tool_reference` 时回退 |
| 用户审批 | `canUseTool`, hooks, defer | callback 长时间 pending 或被 auto-approved 绕过 |
| SDK 中执行 slash command | 发送 `/compact` 等可调度命令 | 只有 `system/init` 中列出的命令可用 |

## Custom tools

Custom tools 用 SDK 的 in-process MCP server 暴露应用内能力。它适合内部 API、数据库、检索、工单系统和业务动作。

| 部分 | 要求 |
| --- | --- |
| Name | 唯一,清晰,最好带动词和对象 |
| Description | 写给 Claude 判断何时调用,不要只写 "run query" |
| Input schema | TypeScript 用 Zod,Python 可用类型 dict 或 JSON Schema |
| Handler | 返回 `content`,可选 `structuredContent` 和 `isError` |
| Server | 用 `createSdkMcpServer` 或 `create_sdk_mcp_server` 包装 |
| Approval | 放进 `allowedTools` 才能无提示执行 |

错误建议返回 `isError: true`,让 Claude 看到可恢复的工具失败。不要把普通业务错误都 throw 出去,否则 agent loop 可能直接中断。

```typescript
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const lookupInvoice = tool(
  "lookup_invoice",
  "Look up an invoice by ID and return billing status, amount, and due date",
  {
    invoiceId: z.string().describe("Invoice ID, for example inv_123")
  },
  async ({ invoiceId }) => {
    const invoice = await getInvoice(invoiceId);
    if (!invoice) {
      return {
        isError: true,
        content: [{ type: "text", text: "Invoice not found" }]
      };
    }
    return {
      content: [{ type: "text", text: `Status: ${invoice.status}` }],
      structuredContent: invoice
    };
  }
);

const billingServer = createSdkMcpServer({
  name: "billing",
  version: "1.0.0",
  tools: [lookupInvoice]
});

for await (const message of query({
  prompt: "Check invoice inv_123 and summarize the next action",
  options: {
    mcpServers: { billing: billingServer },
    allowedTools: ["mcp__billing__lookup_invoice"]
  }
})) {
  console.log(message);
}
```

## System prompt 选择

| 选择 | 适合 | 保留 Claude Code 默认能力 |
| --- | --- | --- |
| 不设置 | 极薄工具调用 loop | 否,只保留最小工具调用支持 |
| `claude_code` preset | CLI/IDE 类 coding agent | 是 |
| `claude_code` 加 `append` | coding agent 加产品规则 | 是,风险最低 |
| 自定义字符串 | 非 coding agent、不同身份或权限模型 | 否,你要自己写完整工具和安全指令 |

如果产品仍然是 coding agent,优先用 preset 加 `append`:

```typescript
{
  systemPrompt: {
    type: "preset",
    preset: "claude_code",
    append: "When changing billing code, always mention migration and rollback risks."
  },
  settingSources: ["project"]
}
```

`CLAUDE.md` 不是 system prompt,而是作为项目上下文注入。它可以和任何 system prompt 配合。多租户产品如果不想加载宿主机规则,设置 `settingSources: []`。

## Streaming output

流式输出适合做终端、WebSocket、任务面板或日志 UI。不要只等最终 result。

| 消息 | UI 处理 |
| --- | --- |
| `system/init` | 记录 session id、可用 slash commands、模型和权限模式 |
| assistant 文本 | 追加到消息流 |
| tool use | 显示正在读文件、跑命令或调用 MCP |
| tool result | 展示摘要,敏感内容默认折叠 |
| `result` success | 结束任务,记录成本和 usage |
| `result` error | 展示可恢复错误,保留 transcript |

单次输入模式适合简单任务。连续输入模式适合聊天 UI,但要显式处理取消、超时和用户离开页面。

## Structured output

结构化输出让最终结果通过 JSON Schema、Zod 或 Pydantic 校验,适合写入数据库或驱动 UI。

| 设计点 | 建议 |
| --- | --- |
| schema | 字段少而稳定,避免深层任意对象 |
| fallback | 校验失败时返回错误状态,不要写半成品 |
| prose | 需要给用户看的解释保留在 `result` 文本或单独字段 |
| retry | 允许 SDK 重试,但要设置总 turn/budget 上限 |
| audit | 保存 schema version,方便后续迁移 |

```typescript
outputFormat: {
  type: "json_schema",
  schema: {
    type: "object",
    properties: {
      risk: { enum: ["low", "medium", "high"] },
      summary: { type: "string" },
      actions: {
        type: "array",
        items: { type: "string" }
      }
    },
    required: ["risk", "summary", "actions"]
  }
}
```

## Tool search

工具定义会占上下文。工具多时,让 Claude 先搜索工具,再加载最相关的几个。

| 值 | 行为 |
| --- | --- |
| `ENABLE_TOOL_SEARCH=true` | 强制启用 |
| `ENABLE_TOOL_SEARCH=auto` | 工具定义超过上下文约 10% 时启用 |
| `ENABLE_TOOL_SEARCH=auto:5` | 超过约 5% 时启用 |
| `ENABLE_TOOL_SEARCH=false` | 禁用,每轮加载全部工具定义 |

命名和描述会直接影响搜索质量:

| 弱 | 强 |
| --- | --- |
| `query` | `search_slack_messages` |
| `Get data` | `Search Slack messages by keyword, channel, user, or date range` |
| `run` | `create_jira_ticket` |

在 Vertex、Bedrock、Foundry 或自定义 `ANTHROPIC_BASE_URL` 下,要确认网关是否支持 tool reference。如果不支持,SDK 可能回退为 upfront 工具定义,缓存和上下文占用都会变化。

## 用户审批与提问

SDK 用 `canUseTool` 处理两类暂停:

| 触发 | 说明 |
| --- | --- |
| 工具审批 | Claude 想用未被提前批准的工具 |
| `AskUserQuestion` | Claude 需要用户选择或澄清 |

注意:如果某个工具已经被 allow rule、permission mode 或 `allowedTools` 自动批准,`canUseTool` 不会再被调用。必须每次都检查的逻辑放到 `PreToolUse` hook。

| 场景 | 推荐处理 |
| --- | --- |
| 用户在线 | 弹出审批 UI,返回 allow 或 deny |
| 用户可能离线 | 返回 defer,持久化 session,稍后恢复 |
| 高风险操作 | hook 阻断或强制人工审批 |
| 外部通知 | 用 `PermissionRequest` hook 发 Slack/邮件/推送 |

## Slash commands in SDK

SDK 中可以把 slash command 当 prompt 发送,但只限可非交互调度的命令。可用列表来自 `system/init` 的 `slash_commands`。

| 命令 | SDK 场景 | 缓存影响 |
| --- | --- | --- |
| `/compact` | 长会话压缩历史 | 会重写上下文前缀,下一轮可能 cache miss |
| `/context` | 诊断上下文占用 | 低影响,主要增加一条消息 |
| `/usage` | 展示用量和缓存情况 | 低影响,适合 UI 的用量面板 |
| `/clear` | 通常不适合 SDK 继续会话 | 会丢弃上下文,等同新前缀 |
| bundled skills | 取决于当前 `slash_commands` | 技能 prompt 会追加到会话 |

发送 `/compact` 前需要已有对话历史。单轮任务刚启动就发 `/compact` 没有意义。

## Prompt cache 5m/1h 策略

Agent SDK 会自动使用 prompt caching。缓存命中取决于模型、effort、system prompt、tools、MCP、settings、CLAUDE.md 和历史前缀是否稳定。

| 场景 | TTL 建议 |
| --- | --- |
| 单次任务、CI、临时修复 | 默认 5 分钟足够 |
| 用户在一个工作区频繁对话 | 可选开启 1 小时 TTL,更可能回本 |
| 大量工具 upfront 加载 | 优先 tool search,再考虑 1 小时 |
| 频繁改 system prompt 或工具集 | 1 小时也难命中 |
| Claude subscription 会话 | 通常计划内自动 1 小时 |

默认不写时走 5 分钟 TTL。需要长间隔复用同一前缀时,再请求 1 小时 TTL:

```bash
export ENABLE_PROMPT_CACHING_1H=1
```

观测时分开看:

| 字段 | 说明 |
| --- | --- |
| `cache_creation_input_tokens` | 本轮写入缓存,高代表前缀大或不稳定 |
| `cache_read_input_tokens` | 本轮读缓存,高代表复用有效 |
| `input_tokens` | 未命中缓存的标准输入 |
| `total_cost_usd` | 成本估算,成功和失败都要记录 |

## 官方参考

- [Custom tools](https://code.claude.com/docs/en/agent-sdk/custom-tools.md)
- [Modifying system prompts](https://code.claude.com/docs/en/agent-sdk/modifying-system-prompts.md)
- [Streaming output](https://code.claude.com/docs/en/agent-sdk/streaming-output.md)
- [Streaming Input](https://code.claude.com/docs/en/agent-sdk/streaming-vs-single-mode.md)
- [Plugins in the SDK](https://code.claude.com/docs/en/agent-sdk/plugins.md)
- [Structured outputs](https://code.claude.com/docs/en/agent-sdk/structured-outputs.md)
- [Tool search](https://code.claude.com/docs/en/agent-sdk/tool-search.md)
- [User input](https://code.claude.com/docs/en/agent-sdk/user-input.md)
- [Slash commands in the SDK](https://code.claude.com/docs/en/agent-sdk/slash-commands.md)

## 相关页面



- [Agent SDK Streaming 与插件](https://docs.passion8.cc/docs/claude-code/sdk-streaming-plugins): Streaming input、single message、插件加载、技能命名空间和缓存影响。
- [Agent SDK Agent 能力](https://docs.passion8.cc/docs/claude-code/sdk-agent-features): agent loop、sessions、skills、subagents 和 checkpointing。
- [Agent SDK 生产部署](https://docs.passion8.cc/docs/claude-code/sdk-production): 租户隔离、SessionStore、OTEL、成本追踪和结构化输出。

