首页
/ 用 Docker 本地托管 Claude Agent SDK 研究型 Agent:临时单次运行与持久会话的两种落地模式

用 Docker 本地托管 Claude Agent SDK 研究型 Agent:临时单次运行与持久会话的两种落地模式

2026-09-07 21:44:56作者:裴麒琰

本文介绍 claude-cookbooks 仓库中 hosting/dockerTier 1 本地 Docker 托管方案:它复用研究 Agent 的共享镜像,通过 docker run 实现「一个提示、一个进程、跑完即退」的临时(Ephemeral)模式,或通过 docker compose 拉起 FastAPI + SSE 服务,把会话目录挂载到宿主机,让多轮对话在容器重建后依然可续。读完你将掌握基于 Claude Agent SDK 打包 Agent 镜像、用环境变量注入 API Key 与提示词、以及利用 CLAUDE_CONFIG_DIR + resume= 机制做会话持久化的完整实操方法。

背景:这个 Docker 方案在整个托管体系中的位置

这份 README 是 hosting 目录 中「三层托管」的第一层。整体设计围绕 00_The_one_liner_research_agent.ipynb 里构建的研究 Agent 展开:WebSearch 搜集信息、按要求输出带来源引用的研究结论。三层托管(本地 Docker / Modal / Kubernetes)共享同一份 Agent 代码、同一个容器镜像、同一个 HTTP 接口契约,只是容器外围的运维机制不同,Docker 正是其中最轻、最适合本机验证与批处理的一层。

镜像的构建上下文是 claude_agent_sdk/(hosting 的父目录)而非 hosting/,因为 Agent 会 import 与 hosting/ 平级的 research_agent/utils/ 模块。所有部署命令都假定你先进入该目录:

cd claude_agent_sdk/

准备工作:构建共享镜像与准备密钥

在尝试两种运行模式之前,先用 hosting/Dockerfile 构建一次镜像:

cd claude_agent_sdk/
docker build -f hosting/Dockerfile -t research-agent .

该镜像值得拆开看几个关键点,它们是后面两种模式成立的前提:

  • 运行时缺一不可的依赖:基于 python:3.11-slim;由于 Agent SDK 底层会把任务交给 Claude Code CLI 执行,因此镜像内先安装 Node 20 再 npm install -g @anthropic-ai/claude-code@2.1.140(版本被硬锁定在 requirements.txt 注释所说的 pin 集合内,升级需连同 claude-agent-sdk==0.1.50 一起刻意为之)。
  • Python 依赖固定版本claude-agent-sdkfastapiuvicorn[standard]sse-starlettepython-dotenv 全部硬 pin,避免未来版本悄然破坏 notebook 的端到端流程。
  • 工作目录被钉死WORKDIR /app。原因正如 Dockerfile 注释所述:SDK 生成的会话记录存放在 $CLAUDE_CONFIG_DIR/projects/<编码后的cwd>/ 下,只有 cwd 稳定,resume= 才能在容器重启后找回旧会话
  • 会话存储重定向ENV CLAUDE_CONFIG_DIR=/data。镜像内不声明 VOLUME,而是由每一层托管显式地往 /data 挂载持久化存储(compose 的 bind mount、Modal Volume、k8s PVC),避免 docker run 时泄漏匿名卷。

镜像的构建还依赖同目录下的 Dockerfile.dockerignore,它排除了 .env、notebook、会话目录以及 hosting/docker|modal|kubernetes 各层专属文件,让镜像只包含 Agent、utils 与共享服务代码。该忽略文件采用了带前缀的名字,需要 BuildKit(Docker 23.0+ 与 Docker Desktop 的默认 builder);老引擎需 DOCKER_BUILDKIT=1

镜像入口统一为 entrypoint.sh,它按第一个参数分发到两种模式,正好对应 README 的两条路径:

# 默认:临时模式——用 $PROMPT 跑一次 Agent 后退出
exec python -m hosting.run_once
# 传入 serve:启动 FastAPI 服务
exec uvicorn hosting.server:app --host 0.0.0.0 --port 8000

临时模式:一个 Prompt、一个进程、跑完即退

README 把临时模式定义为 "One prompt, one process, then exit",适合没有对话需要恢复的任务型(job-shaped)工作,例如批处理、一次性分析。

cd claude_agent_sdk/
docker build -f hosting/Dockerfile -t research-agent .
docker run --rm \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  -e PROMPT="What is the Claude Agent SDK?" \
  research-agent

命令要点逐条说明:

  • --rm:容器在进程退出后自动清理,契合"跑完即退"的无状态语义;
  • -e ANTHROPIC_API_KEY:接口契约要求的最小环境变量集合之一;
  • -e PROMPT:本次运行要交给 Agent 的提示词,与下面的 entrypoint.sh 分发逻辑联动。

执行链路为:entrypoint 未收到 servepython -m hosting.run_oncehosting/run_once.py 读取 $PROMPT,缺失时向 stderr 打印错误并返回退出码 2,随后调用 research_agent.agent.send_query(prompt, model=..., display_result=False) 并把最终结果打印到 stdout、返回 0。

与常驻服务相比,run_once.py 有两处值得注意的取舍:

  • 默认模型DEFAULT_MODEL = "claude-sonnet-4-6",与 server.py 一致,"让测试跑得更便宜";可用 -e MODEL=claude-opus-4-6 覆盖,以完全对齐 research_agent/agent.py 中 notebook 00 的默认配置(其默认是 claude-opus-4-6)。
  • 保留完整工具集:run_once.py 沿用了 agent.pyallowed_tools=["WebSearch", "Read"] 的默认工具。这是安全的——调用方掌握 $PROMPT,容器里也没有其他会话的状态,恶意网页结果没有可让 Read 偷读的敏感目标;而常驻服务恰恰因此主动移除了 Read(见下文服务模式的说明)。

混合模式:docker compose 拉起 FastAPI 服务并持久化会话

临时模式跑完即退,无法多轮对话。README 的第二条路径是混合模式(Hybrid):启动 FastAPI 服务,并把宿主机的 ./sessions 挂载到容器 /data,让对话在容器重启后依然存活。

cd claude_agent_sdk/hosting/docker/
docker compose up --build

这条命令背后是 docker-compose.yml,其设计比表面看起来更讲究,逐项拆解:

services:
  research-agent:
    build:
      # 构建上下文是 claude_agent_sdk/,这样 research_agent/ 与 utils/ 都在范围内
      context: ../..
      dockerfile: hosting/Dockerfile
    image: research-agent
    command: ["serve"]            # 覆盖 entrypoint 参数,进入服务模式
    env_file:
      - ../.env                   # 从 hosting/.env 读取 ANTHROPIC_API_KEY 等
    ports:
      # 仅回环地址。服务默认无鉴权,默认不暴露到局域网——
      # `curl localhost:8000` 仍然可用。生产部署必须在前方加带鉴权的反向代理。
      - "127.0.0.1:8000:8000"
    volumes:
      # 让会话记录跨容器重启持久化
      - ./sessions:/data

配置里隐藏着三个关键工程决策:

  1. 端口只绑回环127.0.0.1:8000:8000 意味着服务只在本机可达。原因是 hosting/server.py 明文警告:这个服务默认没有任何鉴权,它信任一切能摸到 8000 端口的人。因此 README 明确建议生产环境必须在前面加一个能做「调用者鉴权 + 只转发属于该调用者的 session_id」的网关/反向代理,绝不要把 8000 直接暴露到公网。
  2. .env 注入密钥:compose 从 ../.env(即 hosting/.env)读取环境变量。仓库在 hosting/.env.example 中给出了模板——把 ANTHROPIC_API_KEY 填成你的真实密钥并另存为 .env.env 已被 gitignore,绝不能把真实密钥提交进仓库);可选 MODEL 覆盖默认模型。
  3. 会话持久化挂载./sessions:/data 把宿主机上 hosting/docker/sessions/ 目录映射到容器内 CLAUDE_CONFIG_DIR 指向的 /data。会话记录(包括 SDK 内部会话 ID 映射)都落在这里,容器删了、重建了,记录还在。

服务模式下的 Agent 配置与 notebook 00 略有不同,体现为 server.py_build_options() 的几点收紧:

  • 系统提示复用:直接 import research_agent.agentRESEARCH_SYSTEM_PROMPT,保证「部署的就是 notebook 00 那个 Agent」;
  • 工具裁剪为仅 WebSearch:常驻服务没有上传入口,Read 唯一能读到的是容器内部——其他会话在 /data 的对话记录、/proc/self/environ 里的 API Key。被注入的恶意网页结果可能诱导 Agent 用 Read 把这些敏感内容外带,所以托管形态下研究 Agent = 只有 WebSearch;
  • 缓冲与请求体上限MAX_BUFFER_SIZE = 10 * 1024 * 1024(与 notebook 一致),MAX_BODY_BYTES = 256 * 1024 在请求体到达 JSON 解析器之前拦截超大请求(仅对带 Content-Length 的请求生效,生产应在网关处再加真上限);
  • 会话 ID 白名单校验session_id 必须匹配 ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$。这并非装饰性约束——该 ID 最终会进入持久化映射并参与文件系统查找,非法字符会导致路径穿越,因此非法 ID 直接返回 400。

从另一个 shell 发起多轮对话

服务起来后,README 建议从另一个终端验证。发送第一轮:

curl -N -X POST http://localhost:8000/sessions/demo-1/messages \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"What are the latest AI agent trends?"}'

紧跟一轮追问——Agent 记得第一轮的内容:

# Follow-up — the agent remembers the first turn:
curl -N -X POST http://localhost:8000/sessions/demo-1/messages \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"Tell me more about the second one."}'

健康检查(刻意不设鉴权,供编排器做存活探测):

curl http://localhost:8000/health

接口行为符合 hosting/README.md 定义的接口契约:

  • GET /health200 {"status": "ok"}
  • POST /sessions/{session_id}/messages 请求体为 {"prompt": "..."},响应是 text/event-streamevent: message 携带序列化后的 SDK 消息(SystemMessage | AssistantMessage | ResultMessagetype 字段标注了消息类名),event: done 表示本轮结束,event: error 携带 {"message": "..."}
  • 会话已存在则续接,不存在则新建;流中只含本轮新产生的消息,不含历史
  • 必填环境变量 ANTHROPIC_API_KEY;可选 MODEL(默认 claude-sonnet-4-6)、CLAUDE_CONFIG_DIR(默认 /data)、AGENT_AUTH_TOKEN

重启后上下文为何还在:resume 机制的底层原理

README 用一句话描述了验收标准:"Stop the container, docker compose up again, send another follow-up — the agent still has context because ./sessions persisted /data。" 这背后是 server.py 点明的一个 SDK 设计事实:SDK 会自行生成会话 ID,调用方无法指定

所以服务端维护了一张小的持久化映射(落在 CLAUDE_CONFIG_DIR/hosting_session_map.json,即挂载卷内的 /data/hosting_session_map.json),把调用方 URL 里的 session_id(如 demo-1)映射到 SDK 内部生成的 ID:

  • 首轮:外部 ID 尚无映射,_build_options(resume=None) 开启新会话;当消息流中出现带 session_idResultMessage 时,_remember() 学习这个内部 ID 并原子写入映射(先写 .tmpreplace);
  • 后续轮:从映射取出 SDK 内部 ID,传给 ClaudeAgentOptions(resume=sdk_session_id),配合钉死的 cwd="/app"CLAUDE_CONFIG_DIR=/data,SDK 便能定位到同一次会话的历史记录。

映射文件与会话记录放在同一目录,因此它与会话一起跨重启持久化。README 与源码也坦诚指出了两个边界:其一,同一外部 ID 若并发发起两个首次请求,会各自开启全新 SDK 会话并发生「后写覆盖」,对 cookbook 这种"每会话单调用方"的形态没问题,生产服务应把「读-建-写」整段加锁(Kubernetes 那一层就是用 Redis SET NX 规避的);其二,服务端不做生命周期管理,空闲容器由编排器负责回收。

安全提示:无鉴权服务的正确打开方式

混合模式虽方便,但必须牢记 hosting/README.md 的红色警告:服务默认无鉴权。本地验证时 compose 已把端口绑到 127.0.0.1 回环地址,这本身是一道防线;若要跨机器暴露,二选一:

  • 前面架设能鉴权调用者只转发属于该调用者的 session_id 的网关/反向代理(三层托管的 Kubernetes 网关即按此契约路由);
  • 在没有网关的场景下(例如 Modal 分发出公网隧道时),设置 AGENT_AUTH_TOKEN,服务端随即要求 /sessions/* 携带 Authorization: Bearer <token>,且用 secrets.compare_digest 做常数时间比较避免时序侧信道;/health 保持开放。源码注释强调这只是网关的最小替身而非替代品,因为它并不把 session_id 隔离到调用者。

小结:两种模式如何选

维度 临时模式(Ephemeral) 混合模式(Hybrid)
启动命令 docker run --rm -e PROMPT=... research-agent docker compose up --build
进程形态 一个进程,跑完即退 FastAPI + SSE 常驻服务
会话能力 无(天然无状态) 多轮续接,重启后上下文仍在
典型场景 批处理、一次性分析 需要与人多轮交互的服务形态
数据持久化 不适用 ./sessions:/data bind mount
安全面 调用方自己掌控 $PROMPT 默认无鉴权,端口绑回环 / 需网关或 AGENT_AUTH_TOKEN

选型逻辑一句话即可概括:没有需要恢复的对话就用 docker run 的临时模式,把容器当"一次性的函数"调用;需要对话在容器重启间存活、或要对外提供接口,就切到 docker compose 的混合模式,并把 /data 的持久化挂在显眼的位置。想继续往规模化演进,可以接着阅读同一托管体系中基于 Modal 的 Tier 2 与基于 Kubernetes 的 Tier 3,两者复用本层的同一镜像与同一接口契约,只是把会话持久化从本机 bind mount 换成了 Volume 与 PVC。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388