Capabilities

核心能力

这一页写的不是"支持哪些功能",而是每项能力的真实实现方式与边界:中间件按什么顺序执行、 检索参数默认是多少、技能文件如何投影到沙箱、MCP 会话什么时候建立、命令在哪台机器上跑。 内容全部来自 docs/ 下已对照代码核对过的文档,文末保留"当前做不到什么"一节。

01 · Agent

Agent 运行:一条固定的十二层中间件栈

Agent 由 LangGraph create_agent(langchain 1.x)+ deepagents 中间件构建,主循环是 模型推理 → 产生 tool_calls → ToolNode 执行 → 结果回填 → 继续推理,直到无工具调用或触到 recursion_limit(DEFAULT_MAX_EXECUTION_STEPS = 300)。 docs/02-agent-execution-flow.md · agents/context.py#L24

中间件栈(按执行顺序)

顺序即洋葱层的包裹顺序,来自 chatbot/graph.py#L52-L122;"必选性"列写的是该层是否总在此 Agent 的栈里。docs/02-agent-execution-flow.md

#中间件职责必选性
1SteerMiddleware在安全生命周期边界提前结束当前 Run,让位于用户 steer 消息接管(命中则返回 {"jump_to": "end"})必选
2ZhiwuFilesystemMiddleware提供 ls / read_file / write_file / edit_file / glob / grep / execute 七个工具,没有 delete;基于 CompositeBackend 隔离 user / thread;工具结果超过 tool_token_limit(默认 3K tokens)触发驱逐到 large_tool_results/必选
3SkillsMiddleware技能摘要注入 prompt、读取 SKILL.md 触发激活、按依赖动态放开 tools / mcp必选
4ZhiwuMemoryMiddleware注入记忆提示,并挂上 remember_memory / search_thread_messages / read_thread_messagesuser_config.enable_memory
5Subagent tasktask 工具派生子智能体;subagent 禁用 present_artifacts / ask_user_question / install_skill,default 模式再禁用敏感工具配置了子智能体时
6ZhiwuSummarizationMiddleware继承 deepagents SummarizationMiddleware,超过阈值压缩历史必选
7TodoListMiddleware待办规划(write_todos 工具 + TODO_MID_PROMPT)必选
8PatchToolCallsMiddlewaredeepagents 的工具调用参数纠错必选
9ModelRetryMiddleware模型调用失败重试,max_retries 默认 2必选
10ImageInputCompatibilityMiddlewareOpenAI 工具调用里的图片路径转 OCR 解析必选
11TokenUsageMiddleware每次主模型调用后写用量快照,按模型分桶必选
12HumanInTheLoopMiddleware敏感工具审批,白名单固定为 write_file / edit_file / execute(SENSITIVE_BACKEND_TOOLS);当前 Project workdir 内的写入豁免tool_approval_mode

审批白名单与豁免条件见 agents/tool_approval.py#L13,L36-L57,L116-L121;文件工具七件套无 delete 见 agents/backends/composite.py#L21-L31。

四类记忆:谁负责什么、活多久

◷短期 · Run 内

就是 LangGraph state(ChatBotState,含 subagent_runs 的 merge reducer)加 checkpoint。Run 结束不残留,恢复靠同一 thread_id 的 checkpoint 而不是内存对象。docs/02-agent-execution-flow.md

≡会话摘要

阈值 DEFAULT_SUMMARY_THRESHOLD_K = 100(100K tokens)触发自动摘要,保留最近 DEFAULT_SUMMARY_KEEP_MESSAGES = 10 条,工具结果截断至 DEFAULT_SUMMARY_TOOL_RESULT_TOKEN_LIMIT = 300 tokens;被驱逐的超大会话历史/工具结果写成文件(conversation_history/、large_tool_results/)。常量定义在 agents/context.py#L18-L26。

✦长期 · 用户级

默认关闭。user_config.enable_memory 打开后,第 4 层 Memory 中间件才挂上 remember_memory 写入工具与跨线程检索工具(agents/middlewares/memory.py)。

▤工作区注入

项目级 AGENTS.md 与用户级 USER.md 每次进 system prompt,工作区上下文整体上限 64KB;解析发生在 Worker 构建 ChatBotContext 时,不在模型调用里。agents/context.py

控制能力:Steer、取消、中断、重试

Steer(转向)

新消息以 queue_policy=steer 入队,SteerMiddleware 在安全边界结束当前 Run 让位。边界被精确定义为两个钩子点:

  • abefore_model:每次模型调用前检查
  • aafter_model:仅当最后一条消息不含 tool_calls 才检查(steer.py#L14-L19)
  • 因此绝不跳过待执行的工具批次;middleware 全文 40 行,只读 state 判定 + 跳转,没有任何消息截断逻辑,已生成输出靠 checkpointer 保留

取消

双轨下发:Redis 取消信号负责快速通知,PG 的 cancel_requested 状态是权威。Worker 在检查点后收敛终态,取消以 PG 为准(run_worker 三条约定之一)。run_worker.py#L863-L875

中断与审批

HumanInTheLoop 命中敏感工具时把 Run 收敛为 interrupted 终态快照;用户 approve / edit / reject 之后由新的 run_type=resume Run 从 checkpoint 续跑,而不是原地唤醒。docs/02-agent-execution-flow.md

重试的三层落点

  • 模型层:ModelRetryMiddleware,max_retries 默认 2
  • 任务层:ARQ max_tries=2、job_timeout = ZHIWU_JOB_TIMEOUT_SECONDS(默认 3600s),Attempt 记 retry_released
  • 启动层:_worker_startup 收敛过期租约、补发任务、恢复定时派发,外加周期 reconcile 循环(run_worker.py#L1642-L1705)
i
子智能体与事件合流。task 工具派生 subagent(run_type=subagent),子智能体禁用集固定为 present_artifacts / ask_user_question / install_skill (_SUBAGENT_DISABLED_TOOLS,buildin/subagent/graph.py#L30-L48)。 Worker 只开一条 astream_events(version="v3") 流:后台 task 收集"namespace → 子线程路由", 主流逐事件反查后把 namespace 与 thread_id 注入 metadata, 所有子智能体事件都走同一个父 Run 的 Redis Stream / SSE,子 Run 不单独发流; values 只在根 namespace 才 yield,所以子图快照不污染父线程终态。 agents/base.py#L229-L293
!
审计侧的已知限制:有 namespace 但算不出 thread_id 的子事件会被丢弃; model_audit / tool_audit 只在根线程记录,子线程不落审计 (services/chat_service.py#L1376-L1410)。前端也不靠 namespace 建树,而是自己复算 child_thread_id = hash_id("subagent_", "{parent_thread_id}:{slug}:{tool_call_id}", 64), 两侧哈希约定必须同步维护。
02 · RAG

知识库 RAG:离线摄取与在线检索是两条链路

工厂注册三种 KB 实现:MilvusKB(本地全链路)、DifyKB、NotionKB(外部只读连接器) (knowledge/runtime.py#L7-L14)。入库是异步批处理,所以不适合强实时数据; 每 KB 一个 Milvus collection,KB 数量极大时需要单独评估(implementations/milvus.py#L322-L375)。 docs/03-rag-pipeline.md

离线摄取链路

文件状态机 uploaded → parsing → parsed → indexing → indexed,失败态 error_parsing / error_indexing(knowledge/base.py#L21-L28);parse_file 用 owner fence(processing_task_id / processing_owner 条件更新)防止双 Worker 抢同一文件(base.py#L246-L300)。

上传

Web 上传 / API 上传 / 会话附件;元数据入 PG knowledge_* 表,文件入 MinIO(buckets:knowledgebases / kb-images / public,storage/minio/client.py#L67-L76)。

解析

Worker process_task 驱动 parse_file,Office 走 docling、PDF 走 pypdf、图片与扫描件走 OCR 引擎工厂。

统一 Markdown

所有格式先转成统一中间表示(knowledge/parser/unified.py),内嵌图片抽取到 kb-images。

分块

引擎版本 ragflow_like_v1(chunking/ragflow/presets.py#L39),6 套 preset 可选,支持分隔符与长度切分、章节层级合并、语义聚类。

Embedding

模型来自 system_options embed_model,默认 siliconflow-cn:Pro/BAAI/bge-m3(config/options.py#L115),向量维度默认 1024。

写 Milvus

每 KB 独立 collection,含稠密向量 + BM25 稀疏字段;chunk 正文与元数据镜像在 PG knowledge_chunks。

(可选)图谱构建

LLM 抽取实体与三元组写 Neo4j + 向量索引 + PG 镜像表;这是长任务,ARQ job_timeout 需按量调大(run_worker.py#L1741-L1743 注释)。

⌗解析与 OCR

docling 处理 docx / pptx / xlsx(parser/unified.py#L160-L241),pypdf 处理 pdf;OCR 引擎工厂注册 6 个(parser/factory.py#L49-L55),默认引擎由 system_options default_ocr_engine 控制。

  • rapid_ocr(默认)
  • mineru_ocr
  • pp_structure_v3_ocr
  • deepseek_ocr
  • paddleocr_vl_1_6
  • paddleocr_pp_ocrv6

▦分块 preset

引擎 ragflow_like_v1 提供 6 个 preset(presets.py#L7-L36),入库时按文档类型选择:

  • general(默认)
  • qa · book · laws
  • semantic(语义聚类)· separator(按分隔符)

重解析/重索引是文件级动作:POST /api/knowledge/databases/{kb_id}/documents/{parse|parse-pending|index|index-pending} 四个异步端点;collection 级"推倒重建"没有入口,只能靠图谱 graph-build/reset 或删 KB 重灌。knowledge_router.py#L916-L1000

在线检索参数

Agent 工具 query_kb / search(或外部 API)触发 MilvusKB.aquery,参数为 dataclass 定义(implementations/milvus.py#L41-L215,默认合并 #L839-L844)。

参数默认说明
search_modevectorvector(ANN 相似度)/ keyword(BM25)/ hybrid(WeightedRanker)
final_top_k10最终返回的 chunk 数,链路末端截断
similarity_threshold0.2相似度过滤,与 KB / 文档范围过滤叠加
vector_weight / bm25_weight0.7 / 0.3hybrid 模式的加权融合
bm25_top_k / bm25_drop_ratio_search—BM25 召回量与 term 丢弃率
use_reranker + reranker_model关;默认 siliconflow-cn:Pro/BAAI/bge-reranker-v2-m3开启后先召回 recall_top_k = 50 再精排(config/options.py#L121)
use_graph_retrieval关配 graph_entity_top_k / graph_triple_top_k / graph_top_k,从 Neo4j 召回实体与三元组并把图上下文并入结果

◈GraphRAG

LLM 抽取实体与三元组 → Neo4j 图存储 + MilvusGraphVectorStore 向量索引,PG 侧镜像 knowledge_graph_entities / knowledge_graph_triples / knowledge_graph_mentions 三张表(knowledge/graphs/、storage/postgres/models_knowledge.py)。图谱构建与文档索引互不影响,另有 /api/knowledge/databases/{kb_id}/graph-build/* 六个入口(knowledge_router.py#L461-L600),其中 reset / reconcile 属高危运维动作。

◎评估指标口径

数据集与运行记录在 evaluation_datasets / evaluation_runs 表,接口在 routers/knowledge_eval_router.py。指标计算有几处反直觉但确定的取舍:

  • 检索侧实现了 precision / recall / f1,但 calculate_retrieval_metrics(k_values=[1,3,5,10]) 只输出 recall@k 与 f1@k——precision 算了不上报
  • 答案侧 judge_correctness 是 LLM judge,二值制 1.0 / 0.0,无部分得分
  • calculate_overall_score:有标注答案取准确率均值,否则退化为 recall@10 均值
  • 条目缺 gold_chunk_ids 不算检索指标、缺 gold_answer 不算答案指标(evaluator.py#L88-L95)
!
当前边界(如实说明)。第一,没有服务端 Query 改写环节:knowledge/ 下无 rewrite 模块, query_kb(kb_id, query_text, file_name) 是单查询直通 retrieve(),不拆分、不并行扩展, 关键词提炼完全交给调用方的模型(grep rewrite|query_expansion|multi_query 在 knowledge/ 与 agents/toolkits/kbs/ 均无命中)。 第二,DifyKB / NotionKB 是只读检索连接器:基类三个类属性 requires_embedding_model = False、supports_documents = False、apply_chunk_defaults = False, 所有写/解析/索引/预览/下载方法直接抛 ValueError(implementations/read_only_connectors.py#L6-L13); DifyKB 只实现了 aquery,NotionKB 额外实现了 open_file_content / find_file_content, 但后者在 Agent 工具链路上不可达——manager 层 open_document / find_in_document 开头就 _require_kb_supports_documents,必然抛错(manager.py#L1188,L1207,L1219-L1231)。
03 · Skills

Skills:以 SKILL.md 为载体的指令包

一个 Skill 不是函数,没有 JSON Schema 接口:frontmatter 声明 name / slug / description, 正文是给 Agent 的操作手册,可附 scripts/ 脚本。索引存 PG skills 表,内容存共享文件系统目录 (models_business.py#L363-L365)。docs/04-skills.md

skills 表关键字段:slug(unique,等于目录名)、source_type(builtin / upload / remote)、三类依赖声明 tool_dependencies / mcp_dependencies / skill_dependencies(JSON 列表,由管理侧 API/服务设置,不是解析 frontmatter 得来的,service.py#L737-L790)、dir_path(相对 Skill 数据根目录)、version + content_hash、share_config + enabled。

三级目录:真源 → 共享源根 → 按 uid 投影

backend/zhiwu/src/zhiwu/agents/skills/buildin/
buildin/
  __init__.py            # BUILTIN_SKILLS: list[BuiltinSkillSpec](#L20-L67)
  deep-research/SKILL.md
  html-preview/SKILL.md
  image-gen/SKILL.md
  knowledge-base/SKILL.md
  mysql-reporter/
    SKILL.md
    scripts/{list_tables,describe_table,query}.py + _mysql_common.py
i
结论先说:skill-sources/ 与 skill-projections/ 都是运行期产物, 在 git 里看不到它们是正常的;改技能内容要么改内置真源(会随启动同步),要么走管理侧上传/远程安装。

投影同步的五步与它的时机

计算可访问集合

Agent 配置绑定的技能 + 用户可访问技能(按 share_config read scope 过滤)。

无符号链接拷贝

拷到临时目录,刻意不使用 symlink——沙箱不应信任宿主侧符号链接。

advisory lock

<uid>.lock 防同一用户并发重刷。

原子 rename

临时目录整体切换到正式投影目录,读者看不到半成品。

失败 fail-closed

任一步失败就清理临时目录并让投影不可用,宁可技能用不了也不降级直读源目录(skills/service.py#L384-L408)。

时机 = Run 构图阶段

sync_agent_context_skills(context) 在 get_graph() 内被调用(chatbot/graph.py#L158、subagent/graph.py#L121),其内部就是 refresh_user_skill_projection_async(uid)(backends/composite.py#L115-L118)。不是每轮模型调用都刷,因此:

  • 正在跑的 Run 不会在后续回合重新拉投影,技能更新从下一个 Run 才生效;
  • 例外:授权变更时服务端会立即重刷已存在投影的 uid(apply_skill_projection_policy_change,skills/service.py#L392,L408),但已开跑的 Run 仍按自己那份 CompositeBackend 视图工作。

内置技能 version + content_hash 变更才会重投影;上传/远程技能以 slug 唯一覆盖式管理。

懒激活与工具门控

  • prompt 里只放摘要:名称 + 描述注入 system prompt,全文不进上下文(middlewares/skills.py#L65-L140)。
  • 读文件即激活:模型 read_file 读取 /home/gem/skills/<slug>/SKILL.md(或投影内的个人技能路径)时,slug 写入 activated_skills(state reducer 合并)。
  • 依赖闭包展开:激活后才把该技能声明的 tools / mcps / skills 依赖放开,技能间依赖递归展开走 DFS expand_skill_closure,并禁止自依赖(service.py#L763)。
  • 门控只管可见性:未激活技能的工具对模型不可见,但仍已在 ToolNode 注册(可执行兜底),所以"看不见"不等于"调不动"。
  • 预加载:_preloaded_skill_contents 可对小技能直接注入全文,跳过一轮 read_file。

内置 5 个技能

slug用途声明依赖(代码真值)version
deep-research深度研究编排:澄清 → 拆解 → 并行子智能体调研 → 对抗核验 → 带引用报告tool_dependencies=("web_search",)、skill_dependencies=("html-preview",)2026.07.29
html-preview以 Markdown html:preview 围栏输出静态 HTML/CSS 可视化无(三类依赖全空)2026.07.23
image-gen沙箱内文生图(Qwen-Image 兼容接口)并保存到 outputstool_dependencies=("present_artifacts",)2026.06.02
knowledge-base知识库检索 / 打开文档 / 文档内定位 / 思维导图tool_dependencies=("list_kbs","query_kb","find_kb_document","open_kb_document","get_mindmap","search_file","download_kb_file")2026.06.24
mysql-reporterMySQL 查询报表 + Charts MCP 可视化,入口含 scripts/query.py --sql --timeout 60mcp_dependencies=("mcp-server-chart",)2026.06.05

来源:agents/skills/buildin/__init__.py#L20-L67(BUILTIN_SKILLS)与各 SKILL.md frontmatter 实测。

!
内置技能的依赖与版本不可在界面里改。init_builtin_skills() 每次启动都会比对 DB 现有值, 不一致就用 repo.update_dependencies(...) / update_builtin_install(...) 回写成代码声明值(skills/service.py#L1839-L1858)——管理界面手改内置技能的依赖或版本, 重启后会被覆盖。只有非内置(上传 / 远程)技能的依赖字段以 DB 为准。

远程安装

⌁一次性沙箱里取清单

执行 npx -y skills add <source> --list,thread_id = remote-skill-<uuid>、inherit_env=False(不泄漏宿主环境变量),CLI_TIMEOUT = 300 秒(agents/skills/remote_install.py#L26-L101)。源地址经 remote_skill_source_policy 白名单校验,默认只允许 github.com、modelscope.cn(HTTPS,config/options.py)。

⇄draft prepare → confirm

两段式:#L256-L290

  • prepare 只取清单返回给用户确认,不落盘
  • confirm 才下载到共享目录并入库
  • draft 可以直接放弃,不产生任何写入

install_skill 工具不是 superadmin 专属:它没有角色参数,唯一硬性限制是子智能体运行时直接报错,作用范围限于当前 uid 的个人技能空间(toolkits/buildin/install_skill.py#L81-L92,L201-L214)。门槛由"Agent 是否绑定该工具 + 个人空间隔离"控制。

04 · MCP

MCP:只做 Client,且只做 Tools

ZhiWu 在后端 Worker 进程内跑一个 MultiServerMCPClient(langchain-mcp-adapters 0.3.2,agents/mcp/service.py#L16), 把 MCP Server 的 tools 包装成 LangChain 工具并入 Agent ToolNode。它不作为 MCP Server 对外提供服务。 docs/05-mcp.md

⇄Transport:三种支持,两种可配

协议层支持 stdio / sse / streamable_http(models_business.py#L724),但用户创建/更新时强制校验 transport ∈ _USER_CONFIGURABLE_TRANSPORTS = ("sse", "streamable_http"),否则 ValueError(service.py#L36,L428-L429,L482-L483)。

  • stdio 只留给内置服务——启动时自动禁用历史遗留的用户 stdio 配置(service.py#L110-L118),杜绝任意命令执行面
  • 内置 mcp-server-chart(npx -y @antv/mcp-server-chart);退役的 sequentialthinking 会在启动时自动删除(service.py#L52)
  • 内置服务连接字段以代码定义为准,DB 只同步展示字段与 disabled_tools(_SYNCED_MCP_FIELDS,service.py#L54-L96)

◇能力支持矩阵

能力状态说明
Tools✅get_mcp_tools 拉取并缓存,模型直接调用
Resources❌全仓无 get_resources / read_resource 调用;adapters 0.3.2 库本身提供该 API
Prompts❌无 get_prompts() / load_mcp_prompt() 调用;同样是"库支持、应用未用"

工具装载路径只调 client.get_tools()(service.py#L301,L326,L377),能力发现响应也不含 resources/prompts 字段。

工具发现、缓存与会话

  • 配置每次现查 DB:get_mcp_tools(server_slugs, disabled_tools) 按 slug 从 mcp_servers 表读配置,配置以数据库为准(service.py#L250-L355)。
  • 工具对象进程内缓存:key = slug:config_hash,配置变了 hash 就变,缓存自然失效;_mcp_tools_stats 统计启用/禁用数供管理端报告。
  • session 不是常驻的:缓存的只是 StructuredTool 对象(内含连接参数),真正的 MCP session 在每次 call_tool 时按需创建、调用完立即关闭(adapters 0.3.2 tools.py#L461,L466,L586,L590)。因此 stdio 内置 chart 服务每次工具调用都会重新 npx 拉一个子进程,initialize / tools/list 握手按次发生,跨调用不复用连接、没有连接池。
  • 工具名不重命名:只在 tool.metadata["id"] 写 mcp__{camelCase(slug)}__{camelCase(tool)} 作展示用唯一 ID(service.py#L303-L311)。
  • 与技能联动:Skill 激活时把其 mcp_dependencies 声明的服务工具动态并入可见工具集(middlewares/skills.py)。

错误语义:没有重试、没有熔断

✕发现阶段:吞掉异常返回空列表

get_mcp_tools 分别捕获 ExceptionGroup 与 Exception,只写日志并返回空列表;get_mcp_client 构造失败返回 None,调用方同样拿到空列表(service.py#L188-L190,L338-L343)。失败的外部表现就是"这个 server 没有工具",不阻断 Run。库层与应用层都没有 backoff / retry / 熔断 / 失败计数,所以不可用的 server 每轮组工具都会重新尝试建连。

↺调用阶段:只吞 server 侧 isError

ZhiWu 显式设 tool.handle_tool_error = True(service.py#L312-L313),MCP 执行错误(isError=True)被转成 ToolMessage 错误内容回填给模型,由模型决定重试或绕行。注意 adapters 的语义边界:transport / session 失败与内容转换错误不受该开关控制、始终抛出。超时靠 mcp_servers 表的 timeout / sse_read_timeout 字段透传,Run 级兜底是 ARQ job_timeout。

!
密钥存储的既成事实:env(stdio 密钥)与 headers(远程 Bearer Token)在 DB 中明文存储, MCPServer.to_dict() 原样输出、管理端接口返回明文不脱敏、无尾号打码。 可见范围只靠角色守卫收敛:GET /api/system/mcp-servers 对 admin / superadmin 给全量明文, 普通用户改走手写的 5 字段白名单(name/description/icon/enabled/tags); 单条详情 GET /api/system/mcp-servers/{slug} 是 get_admin_user,普通用户拿不到这两个字段 (mcp_router.py#L79-L125、models_business.py#L750-L772)。 也就是说:任何管理员都能读到明文密钥。同类问题也存在于模型供应商 api_key,改进建议见 docs/11-security.md 第 6 节。
05 · Sandbox

安全沙箱:把"执行"这件事搬出宿主进程

Agent 的 execute 与文件工具如果直接在 Worker 进程里跑,等于把任意代码执行、宿主文件读写和环境变量泄露 都放给了一个由模型决定的命令串。ZhiWu 的做法是:Worker 只持有路径守卫与一个 HTTP 客户端, 真正的执行落在每个 runtime scope 一个的独立容器里。

CompositeBackend 与路径守卫

  • 文件工具七件套 ls / read_file / write_file / edit_file / glob / grep / execute,没有 delete(agents/backends/composite.py#L21-L31)。
  • 读限定 readable roots、写限定 writable roots,且按 user / thread 做隔离;每 Run 独享一个 CompositeBackend 与图实例。
  • 模型看到的是虚拟路径前缀 /home/gem/user-data 与 /home/gem/skills,由 backend 映射到宿主/容器真实路径(agents/backends/paths.py#L10-L28);投影同步刻意不使用符号链接,因为沙箱不应信任宿主侧 symlink。
  • present_artifacts 只接受 workdir_path/、/home/gem/user-data/…、/home/gem/skills/… 白名单前缀下的沙箱内普通文件(目录与链接拒绝),临时(ephemeral,workdir_path 为空)会话直接拒绝(toolkits/buildin/tools.py#L221-L291)。

Provisioner:一个独立的 FastAPI 服务

端口与 backend

Provisioner 自身监听 :8002(PROVISIONER_PUBLIC_URL 默认 http://sandbox-provisioner:8002,app.py#L420),沙箱容器内 HTTP 服务监听 :8080(SANDBOX_CONTAINER_PORT,app.py#L747)。后端由 PROVISIONER_BACKEND 选择,三种:docker / kubernetes / memory(app.py#L2125)。

每个 runtime scope(runtime_scope_id,来自 Run 快照)对应一个沙箱容器;鉴权统一走 require_provisioner_auth Bearer token,且 SANDBOX_PROVISIONER_TOKEN 与其他两项密钥一样必须 ≥ 32 字符、互不相等。

!
沙箱镜像不是本仓构建的。默认镜像是第三方 enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest (app.py#L48-L50),仓库里只有供给它的 Provisioner 代码, 没有该镜像的 Dockerfile——镜像内有什么服务、监听哪些端口,属于上游镜像的事实。

本机 Docker

通过 Docker SDK 按 scope 建容器,挂载宿主目录、分配 per-sandbox 网络(DOCKER_NETWORK_PREFIX / DOCKER_ADDRESS_POOL / DOCKER_SUBNET_PREFIX 默认 28,app.py#L79)。删除走并发池 SANDBOX_DELETE_CONCURRENCY 默认 16、容器停止 SANDBOX_CONTAINER_STOP_TIMEOUT_SECONDS 默认 2s(app.py#L368,L384)。前提是 provisioner 自己能访问 docker.sock(见下方风险)。

生命周期端点

详细请求/响应见 API 与 CLI 页;这里只列能力面。全部端点在 docker/sandbox_provisioner/app.py,均需 Bearer token。

动作端点行为要点
创建POST /api/sandboxesbackend 内部先发现已运行容器再创建,幂等;容量不足 503、参数非法 400(app.py#L2196-L2235)
查询GET /api/sandboxes/{id}discover() 拿状态,未找到返 404;顺带更新 idle 记录
保活POST /api/sandboxes/{id}/touchWorker 侧按 SANDBOX_KEEPALIVE_INTERVAL_SECONDS(默认 30s,provider.py#L134)周期调用;未被 touch 的沙箱由 idle reaper 回收(SANDBOX_IDLE_TIMEOUT_SECONDS 默认 600,app.py#L2010-L2015)
删除DELETE /api/sandboxes/{id}对 404 视作"已删除",因此重复删除安全(幂等)
反代ANY /api/sandboxes/{id}/proxy/{path}Agent 的所有文件/命令调用实际走这条通道到容器 :8080;上游超时可配(connect 10s / read 600s / write 600s / pool 10s,app.py#L130-L140),跳带头会被剥离
运维GET /api/sandboxes、POST /api/sandboxes/quiesce、GET /health列举、静默(等待在途操作排空)与健康探测
i
客户端容错策略。创建用"较短单次读超时 + 有限重试":create 20s × 最多 4 次、delete 20s × 最多 3 次、退避基数 1s (provisioner_client.py#L26-L29,env 入口 SANDBOX_PROVISIONER_CREATE_TIMEOUT_SECONDS / _ATTEMPTS 等, provider.py#L125-L127)。之所以敢重试,是因为 create/delete 在 provisioner 侧都是幂等的; 单次读超时绝不允许设成 None,否则会在 socket recv 上永久挂起并拖死 idle reaper 的排空等待。

运行时裁剪与挂载

⌥SANDBOX_RUNTIME_PROFILE

三档 profile 通过往容器注入 DISABLE_* 开关来裁剪镜像内的运行时(app.py#L52-L77,默认 core,app.py#L320):

  • core:关掉 browser / MCP browser / VNC / Jupyter / Code Server / Node REPL 全部六项
  • browser:只放开 browser、MCP browser 与 VNC,仍关 Jupyter / Code Server / Node REPL
  • full:六项全开

无效值在启动期直接 RuntimeError,不会静默降级。

⚑bind mount 与 docker.sock

持久挂载根只有 /home/gem/skills、/home/gem/user-data、/home/gem/projects(app.py#L101-L105),docker backend 实际允许的只有前两项(app.py#L916):宿主 UserWorkspace 的 user-data/ 与技能投影 skill-projections/<uid> 被 bind 进容器(app.py#L904,L953-L954,L1290)。

高危权限:docker backend 要求把 /var/run/docker.sock 挂给 provisioner,这等于给该容器宿主 root 级权限——仅限受控主机使用,生产更应选 Kubernetes backend(docs/11-security.md 第 7 节、docker-compose.yml 注释)。远程技能安装用的临时沙箱额外设 inherit_env=False,不继承宿主环境变量。

180s单条命令超时
SANDBOX_EXEC_TIMEOUT_SECONDS
262144输出字节上限
SANDBOX_MAX_OUTPUT_BYTES
30stouch 保活周期
SANDBOX_KEEPALIVE_INTERVAL_SECONDS
600sidle 回收阈值
SANDBOX_IDLE_TIMEOUT_SECONDS

超时与输出上限定义在 agents/backends/sandbox/backend.py#L224-L225(超限即按字节截断,#L673-L674),汇总见 docs/08-configuration.md。

06 · Limits

已知边界与限制

这一节把本页所有"当前做不到 / 需要自己补 / 容易踩"的条目集中起来。全部是已对照代码确认的事实,不是推测;措辞保持中性,但对运维者有实际影响。

4
四项最容易踩的:
  1. 审计没有独立表——生命周期审计写进 messages(message_type = model_audit | tool_audit),Tool 审计额外向 tool_calls 写兼容投影行;36 张表清单里确实没有 audit 表(docs/07-data-model.md 已确认结论、docs/10-observability.md)。要接外部审计系统,只能消费 messages。
  2. 日志级别硬编码 DEBUG、不可配置:setup_logger(name, level="DEBUG", console=True) 的默认值就是最终值,全仓无 LOG_LEVEL / ZHIWU_LOG_* 环境变量,生产与开发一样 DEBUG 落盘、无法降噪;只有 httpx/openai/neo4j/urllib3 被桥接为 WARNING(docs/10-observability.md、utils/logging_config.py#L59)。
  3. 登录锁定阈值 5 次 / 300 秒是代码常量,没有环境变量入口(MAX_LOGIN_FAILED_ATTEMPTS、LOGIN_LOCK_DURATION_SECONDS,models_business.py#L34-L35);仓内也没有管理端解锁端点,只能等过期或改 DB。副作用:锁定期内 JWT 全站返 423,而 API Key 分支提前 return、不受锁定影响(auth_middleware.py#L72-L102)。
  4. /api/agent-invocation/* 通道没有限流与配额:与 /api/agent/runs 一样只挂 get_required_user,全仓唯一的节流逻辑在登录链路;非登录路径无任何 429。大规模并发评测不会被"供应商式限流"拦下,只会被 ARQ_MAX_JOBS(默认 10)的背压排队(docs/06-api.md 已确认结论、docs/14-cli.md)。

按能力线汇总

Agent

  • Steer 只在两个钩子点生效,不会打断待执行的工具批次,长命令期间 steer 不生效
  • Steer 入队侧限制:只允许 source in {chat, channel}(否则 422),同线程只允许一条 pending steer
  • 审批只在 write_file / edit_file / execute 上,当前 Project workdir 内写入豁免;读类工具与 MCP 工具不进审批
  • 子线程不落审计;run_type=subagent 禁用 present/ask/install 三工具
  • 步数上限 DEFAULT_MAX_EXECUTION_STEPS = 300、Job 超时默认 3600s,二者都是配置常量而非按 Agent 动态
  • 降级不对称:内置 MCP 启动失败不阻断,内置 Skills 同步失败中止启动(lifespan.py#L61-L212)

RAG

  • 无服务端 Query 改写 / 多查询扩展,召回质量完全依赖调用方模型的关键词提炼
  • collection 级重建无入口,整体重建只能删 KB 重灌;重解析/重索引只到文件级
  • 检索无结果级缓存(未发现实现);每 KB 一个 collection,KB 数量极大时需评估
  • 评估只上报 recall@k 与 f1@k(precision 计算但不输出),LLM judge 二值制
  • 图谱/评估以本地 knowledge_chunks 为输入,只读连接器不产生 chunk,实际"无料可算"(代码层未按 kb_type 拦截)
  • 入库是异步批处理,不适合强实时数据

Skills

  • 技能更新从下一个 Run 才生效(投影同步只在构图阶段)
  • 投影同步失败 fail-closed:表现为技能整体不可用,而不是回退读源目录
  • 内置技能的依赖与版本每次启动被代码回写,管理界面手改会被覆盖
  • 投影五步(拷贝/锁/原子 rename/fail-closed)没有专项单测:全仓 test/ 下 grep refresh_user_skill_projection 0 命中
  • 远程安装受域名白名单(默认 github.com / modelscope.cn)与 300s CLI 超时约束
  • Skill 没有独立重试器,脚本失败只作为工具错误回填给模型

MCP 与沙箱

  • Resources / Prompts 未接入;Tools 是唯一使用的 MCP 能力
  • 无 backoff / 重试 / 熔断,不可用 server 每轮重建连,失败静默成空工具列表
  • stdio 每次工具调用重新 npx 拉子进程,冷启动开销按次发生
  • env / headers(以及模型供应商 api_key)DB 明文存储、管理端明文返回
  • docker.sock 挂载给 provisioner ≈ 宿主 root 权限,仅限受控主机
  • 沙箱镜像是第三方 all-in-one-sandbox,不由本仓构建,镜像内容不受本仓版本控制
  • 单条命令 180s / 输出 262144 字节截断,长时间任务需自行后台化
i
另两项与能力相关但不属于本页:JWT 有效期硬编码 7 天且无刷新、无吊销(docs/11-security.md 已确认结论); 仓库内只有 docker/sandbox_provisioner/ 两套 compose 清单,不存在一条起全栈的 docker compose up, 详见 部署与配置。