Skip to content

默认行为规范 ​

本文是 ai.agent.* 默认行为的唯一权威文档(#735)。代码默认值见 packages/im/agent/src/config/zhin-agent-defaults.ts;字段级参考见生成的配置参考。

按场景的默认行为 ​

场景信任级别默认行为
主人私聊owner可使用全部已启用工具;execSecurity: deny 时不执行 shell;masterShell: isolated 时仅在主人私聊中提供隔离 bash;交互任务持续执行,按间隔报告进度,受取消、额度和权限约束
群聊group_member入站队列 groupMode: supersede(新消息替换排队中的旧轮次,最多排队 5 轮);高风险外部内容需确认;外发内容自动脱敏(outboundCheck: redact)
受信成员trusted_member与群成员相同的护栏,但可使用受信工具;非 IM 入口(CLI、HTTP)按 trusted_member 处理
陌生人私聊group_member按群成员处理,不提升权限
无人值守定时任务—maxTokens 32000、maxToolCalls 15、timeoutMs 120000、execPreset: readonly

当前 CLI/Sandbox 的交互运行时及本回合同步子任务不再按固定轮数或 10 分钟结束;沿用自动压缩、失败/重复调用保护与配额。进度使用现有 progressMinIntervalMs(默认 45 秒),不限三次,progressUpdates: off 或 progressMaxUpdates: 0 可关闭。配额仍默认关闭且主人默认豁免,并不构成实时费用硬上限;在途请求可能超过剩余额度。已交给平台的进度消息无法撤销,可能晚到,但不会阻塞最终结果。定时、无人值守、独立后台 worker 与旧 ZhinAgent.process()/prompt() 兼容入口保留原预算。

核心默认值 ​

  • 循环:maxIterations 15、timeout 120000 ms
  • 执行:execSecurity deny、execApprovalMode auto;网络默认关闭,network.enabled: true 时开放 HTTPS 出网(network.allowedDomains 可限定域名;未设置 enabled 时兼容 execPreset: network);没有出网权限的回合不提供 web_fetch / web_search
  • 子代理:subagentTurnMode off
  • 工具预算:extendBy 10、progressWindow 5、maxTotalIterations 40、maxWallClockMs 600000、failureStreakLimit 3、repeatLimit 2
  • 用量配额:全部为 0(关闭),exemptOwner true
  • 危险工具硬闸:始终开启;资金类禁用(allowFunds false);审批 5 分钟(approvalTimeoutMs 300000)未批即拒绝;每群同时最多 3 张待审卡(maxPendingPerScene 3),超出直接拒绝
  • 群聊合流:开启(mailbox.merge.enabled true,仅群聊;窗口 quietMs 1500 / maxWaitMs 6000 / maxSenders 5)
  • 弹幕模式:仅对声明 liveDanmaku 的直播适配器生效(mailbox.live:mode pick-one / windowMs 10000 / minIntervalMs 15000;只读工具、无审批卡)
  • 上下文压缩:thresholdRatio 0.7
  • 历史窗口(ADR 0043):按 token 预算而不是条数,ai.context.historyTokens(不是 ai.agent 开关)——群聊 8000、普通私聊 12000、主人私聊 24000;可写数字(所有场景)或 { group, private, masterPrivate },0 表示不按 token 限制。ai.context.maxRecentMessages 仍是条数上限,两者取先到者。预算按真正发给模型的内容(投影后)计,窗口起点对齐回合边界,只在超出预算时整段前移并留 25% 余量(保持提示词缓存前缀稳定);被挤出的部分仍留在存储与压缩输入里。已结束回合的工具结果只给模型看前 200 字符 + …(已省略 N 字),当前回合和存储保持完整。
  • 请求轨迹:recordRequestBodies true,在 Host 本地保存脱敏后的模型输入文本与工具定义;默认最多保留 7 天、总量 64 MiB。达到容量会先清理最旧轨迹,单条正文仍放不下时仅保留统计和未保存状态。设置为 false 停止记录新请求正文,不影响用量统计,也不删除已有记录。

危险工具硬闸 ​

execApprovalMode 决定「普通审批」由谁回答(ask 问人、auto 交给审核 Agent、bypass 跳过)。危险工具硬闸是另一道门,独立于它,不能关闭(ADR 0036 §3):

execApprovalMode 审批危险工具硬闸
管哪些工具requiresApproval 要求审批的工具、Shell / 文件策略声明 risk: 'dangerous' 的工具、核心名单、toolHardGate.dangerousTools
谁能放行人、审核 Agent,或 bypass 直接放行;可以「本会话 / 始终允许」只有本次的新鲜人工批准,或 Console 已批准的同参数卡片;bypass、审核 Agent、主人身份、记住的授权都不算
被拒之后模型收到拒绝说明本次调用不执行(fail closed);模型只收到「已拒绝、需人工审批、不要重试」;本回合内同一工具 / 同类危险工具直接拒绝,不再弹审批
无人值守回合按策略拒绝直接拒绝,并生成一张 Console 审批卡片;批准后,下一次同会话同参数的调用执行一次

核心名单按类别默认危险:成员管理(踢人、禁言、设管理员、角色、拉人)、内容发布(群公告、频道发帖、精华、置顶、B 站评论)、资金(红包、转账、付款,默认禁用,allowFunds: true 后仍需审批)、身份(改 Bot 资料、头像、签名、解绑账号)。

另有一类按本次调用的目标判定,而不是按工具名:改宿主自身(self 硬闸)。node_modules/**、锁文件、.env*、.zhin/、根 data/ 等路径直接拒绝写入;项目根目录的 zhin.config.*、package.json、schema.json、插件入口、AGENTS.md、skills/ 等受保护文件走 self 硬闸,必须每次获得新鲜人工批准。关于受控自我变更(change_draft → 沙箱试跑 → 人工审批 → 生效与自动回滚)的用法见让 Bot 改自己。

架构演进记录(Slice A–E2:宿主保护与自我变更细节)

另有一类按本次调用的目标判定,而不是按工具名:改宿主自身,分两档,路径都经过 realpath、../ 折叠和大小写折叠后再匹配。直接拒绝(不弹审批):node_modules/**(任意层)、锁文件(pnpm-lock.yaml、package-lock.json、yarn.lock、bun.lock(b)、npm-shrinkwrap.json)、ZHIN_PLUGIN_LIFECYCLE_FILE 指向的文件,以及文件策略本来就拒绝的 .env*、.zhin/、根 data/、config.json、.npmrc;任何角色都不能写。self 硬闸:写项目根下的根配置(zhin.config.*、config.yml|yaml)、package.json、schema.json、根插件入口(package.json#zhin.entry)、AGENTS.md、skills/、.agents/skills/、.git/、.pnpmfile.cjs、assistant.profile.yml;只匹配项目根,plugins/x/package.json 这类嵌套文件不受影响,pnpm-workspace.yaml 与 tsconfig*.json 本阶段不管。项目根和根插件入口由宿主注入;宿主没注入时按文件名在任意层级匹配(宁可多拦)。bash 命令通过重定向、tee、rm/cp/mv 等写这些路径时按同样两档处理,用 pnpm / npm / yarn / bun 增删改依赖按 self 处理。self 写入可能放松硬闸、改变宿主运行的代码或触发重启,所以和其他危险类别一样,每次都要新鲜的人工批准,bypass、自动审核、主人免审、danger-full-access 和"记住授权"都不能放行。只读查询(查成员、查群信息)不受硬闸限制,照常记审计。

隔离 shell 的只读挂载(#1030 P0-c)。 上面对 bash 的字符串识别只是第一道(报错更友好),真正的强制是挂载:隔离容器以 workspace-write 挂载工作目录时,再把上述两档路径中已存在的部分叠加成只读绑定(--mount type=bind,...,readonly),所以 sed -i、软链写入、mv、rm -rf node_modules 都会失败(Read-only file system / Device or resource busy),与 danger-full-access 无关。覆盖范围:挂载目录内的项目根配置、package.json、schema.json、插件入口、AGENTS.md、skills/、.agents/skills/、.git/、.pnpmfile.*、assistant.profile.*、锁文件、ZHIN_PLUGIN_LIFECYCLE_FILE、.env*、.zhin/、.npmrc、data/ 下除 memory/media 外的子项,以及挂载目录下至多 4 层、最多 64 个的 node_modules(扫描不进入 node_modules、.git 与符号链接)。不存在的目录(如 skills/、node_modules)用只读 tmpfs 占位,Docker 会在宿主上临时建出空挂载点,运行结束后若仍为空即删除。不存在的文件不挂占位:Docker 会在宿主上留下空文件,空的 zhin.config.* / 锁文件 / .pnpmfile.cjs 对热重载和包管理器更危险,所以"新建一个原本不存在的受保护文件"仍只靠字符串识别拦截。宿主没注入受保护路径时,按挂载目录自身解析惯用文件名(宁可多拦)。残余风险: danger-full-access(isolation: none)没有容器,无法挂载,只剩字符串识别,换成间接写法(脚本、python -c、git 钩子之类)可能绕过;指向受保护文件的符号链接本身可被替换(链接目标仍是只读)。需要硬保证时请用隔离 shell,不要给 danger-full-access。

自我变更暂存区(ADR 0040 §6a,切片 A)。 宿主在 .zhin/changes/<id>/ 暂存 Agent 起草的变更(change.json 元数据 + files/ 草稿 + snapshot/ 回滚快照);该目录属 P0 A 名单,Agent 的文件工具与 shell 都不能写。可写目标只有插件约定目录(commands/、middlewares/、handlers/、tools/、components/、skills/、agents/)、根 AGENTS.md 与 skills/**;根配置只能走字段级 configPatch,且补丁触及放松安全或扩大工具面的配置键(SELF_CHANGE_SECURITY_CONFIG_KEYS:ai.agent 的硬闸 / 执行审批 / 执行安全 / 主人 shell / 注入防护 / 配额豁免 / 定时安全,ai.decisions.approval、ai.workroom.trustedPackPublishers / disclosure、http.token(s)、a2a.workroomRemoteExecutors / workroomCallbacks、assistant.home.policy、mcp、ai.mcpServers、ai.githubMcp;宿主 schema 中标 x-zhin-config.security)或整体覆盖 / 删除其祖先,直接拒绝;生效前按当前名单再复核一次,名单扩充前批准的旧变更同样拒绝。磁盘有界:进入终态(applied / rolled_back / rejected / expired / trial_failed)立即删除 files/ 与 snapshot/,只留 change.json,14 天后删除整个目录;draft / ready / pending 闲置 24 小时即 expired;failed(已急停)保留现场直到人标记已处理。单个变更 ≤ 5 MB / 50 个文件,整个暂存区 ≤ 50 MB,超限在写入前拒绝(change_quota_exceeded)。清理在启动时与之后每 6 小时各跑一次,只删暂存区内的目录且不跟随符号链接。状态迁移写入 ai_decision_events,kind=self_change,payload 只含路径与摘要,不含文件正文与配置值。

起草变更:change_draft(ADR 0040 §2,切片 B)。 用户要「加个指令 / 改个配置」时,Agent 调 change_draft(risk: safe)提交完整文件内容与可选的字段级 config_patch(值用 value_json 传 JSON 字符串);宿主校验后写入暂存区,不碰生产目录、不生效。只在 IM 会话中可用,只能改自己发起的 draft(change_id),改草稿会作废旧预览。写入后宿主生成预览挂到 change.json 的 plan:每个文件的新建 / 修改 / 删除与增删行数(按行比较,移动的行不计);影响面复用开发态文件监听的 InvalidationPlanner(当前代的源码归属索引),得到 none / generation(列出将热重载的插件子树与能力槽)/ process(需重启);配置补丁按 Console config:patch 同一口径并入——宿主配置段(http、database、ai 等)需重启,插件段热重载对应插件。拿不到归属索引时按保守估计(文件 → 热重载,配置 → 重启)并标「估计」。预览本身 ≤ 64 KB,计入暂存区配额。审批见切片 D(下文)。

试跑:静态检查(ADR 0040 §3,切片 C1)。 change_draft 写入并生成预览后,宿主立即试跑:draft → trialing → ready(通过)或 trial_failed(失败,终态,立即删除暂存正文)。静态检查全在内存里完成、不写临时文件:syntax——.ts / .mts / .cts / .js 用 Node 原生类型剥离解析(与生产加载 .ts 同一实现,enum 等 strip-only 不支持的语法也算失败),.tsx / .jsx 用 esbuild(与 tsx 加载器同源);default_export——约定目录的能力文件须默认导出对应的 defineCommand / defineMiddleware / defineHandler / defineComponent / defineAgentTool(Skill / Agent 私有工具 .../tools/<name>/index.ts 同理);forbidden_import——直调 bot.$sendMessage、直接 import koa、插件直连 @zhin.js/kernel、已删除的 zhin.js/node / bootstrapNode;skill / agent_manifest——SKILL.md 须能被 Skill 解析器接受,agent.json 须是 JSON 对象。结果写进 change.json 的 trial(绑定 content_hash,每条消息 ≤ 300 字符);失败时错误直接回给 Agent,不出审批卡,Agent 修好后重新起草一个新变更(已试跑的变更不能再改)。指令同目录里非 index.* 的文件视为辅助模块,不要求默认导出 defineCommand。

试跑:沙箱加载(ADR 0040 §3.3,切片 C2)。 静态检查通过、且变更改到了约定目录时,宿主把草稿所在的插件根拷进系统临时目录(不拷 node_modules、.git、.zhin、data、lib / dist、.env*,不跟随符号链接;node_modules 以链接指回原依赖),在拷贝上应用草稿,再在一次性子进程里用试跑运行时装配:只装 IM 运行时,不加载任何已配置的插件实例与端点(不连真实平台),不配置模型;子进程环境只保留 PATH / 区域设置等,不带任何令牌;60 秒超时即杀整个进程组。装配抛错(模块加载失败、默认导出形状不对等)→ sandbox_load 失败;装配成功后,草稿里每个顶层约定目录的能力文件都必须被发现(例如 commands/ping.ts 不会被发现,应为 commands/ping/index.ts),否则 capability_discovered 失败。若不带草稿的插件根本身就装配不起来、插件根超过拷贝上限(5000 个文件 / 100 MB)或本机没有构建好的试跑运行时,记 skipped(不算失败,静态检查仍然把关)。只改根 AGENTS.md 或只有 configPatch 的变更不做沙箱加载(配置补丁的效果在生效后的健康检查里校验)。磁盘:同一时间只跑一个试跑,拷贝目录 zhin-change-trial-* 在试跑结束(含失败与超时)时立即删除,宿主崩溃留下的超过 1 小时的残留在下次创建试跑器时清理。沙箱加载通过后接着跑插件自带测试(见下)。

试跑:插件自带测试(ADR 0040 §3.2,切片 C3)。 沙箱加载通过(或未注入加载器)后,宿主对每个改到约定目录的插件根再做一次同样的拷贝(应用草稿,不拷 node_modules / .env* 等),在拷贝里找测试文件(*.test.* / *.spec.*,跳过 node_modules、lib / dist 等),然后在隔离容器里运行,与 P0-c 隔离 shell 同口径:拷贝是唯一可写挂载,插件根最近的 node_modules 以只读挂载,--network none、只读根文件系统、丢弃全部 capabilities、no-new-privileges、内存 1 GB / 1 CPU / 256 进程,不传入宿主任何环境变量(只有 NODE_ENV=test、CI=1、HOME=/tmp),120 秒超时按容器名强删。装了 vitest 就跑 vitest run,否则用 node --test。测试失败 → plugin_tests 失败,输出尾部(≤ 1000 字符)回给 Agent;不带草稿也不通过的测试(包括宿主编译的原生依赖在容器里跑不起来)、插件没有测试、本机没有 Docker 或镜像(默认 node:22-bookworm-slim,试跑不会联网拉取)、插件根超过拷贝上限时记 skipped——草稿代码不会在容器外跑测试。全部试跑执行器都注入时 trial.pendingStages 为空。磁盘:与沙箱加载共用「同一时间一个试跑」的队列与 zhin-change-trial-* 前缀,拷贝在测试结束(含失败与超时)时立即删除,容器以 --rm 运行。

审批(ADR 0040 §4,切片 D)。 试跑通过(ready)的变更立即提交审批:ready → pending,并在硬闸同一个审批队列里建一张类别 self 的卡片(工具名 self_change),卡片带变更 id、content_hash、摘要、每个文件的增删行数、配置路径、影响面、试跑结论与被跳过的检查名(skippedChecks),以及一段有界 diff 摘录(diffExcerpt,≤ 40 行 / 2 KB;键名像密钥的 key = value / key: value 赋值把值换成 [redacted],入队时再逐行按审计口径打码;超出标「完整内容见 Console」并附「未看完整正文请去 Console」的盲批提示,#1142 F3);不带完整文件正文与配置值。摘录在提交与启动 restore() 重建卡片时从暂存正文重新生成。审批人是 Bot 的主人(端点 owner)与 toolHardGate.imApproval.approverIds:卡片总是投递到主人私聊——包括在业务群里发起的变更、包括 IM 审批通道未开启时;开启 IM 审批通道时名单内审批人按其 delivery 另收一份。主人在与 Bot 的私聊里用 /approve <卡片 id> / /deny <卡片 id> 结算(只限 self 卡,主人不会因此能结算其他硬闸卡);业务群里的消息永远不能结算。Console 上 plugin.root.self-changes(读,tool_approvals:read 或 ops:approve;只含元数据与暂存区占用 usageBytes)、plugin.root.self-change-detail({ id },仅 ops:approve:逐文件 unified diff——基线为回滚快照(已开始生效时)或生产目录现有文件,全部文件合计 ≤ 64 KB,按行截断并标 truncated,预算用完的文件标 budget_exhausted;configPatch 值按决策审计口径脱敏;完整 trial.checks 与 skippedChecks;已瘦身时 bodiesAvailable: false,不编造正文)、plugin.root.resolve-self-change({ id, decision: 'approve' | 'reject' },ops:approve)与 plugin.root.mark-self-change-handled(failed 已处理,ops:approve);同一张卡在 plugin.root.tool-approvals 也可见(该列表去掉 diffExcerpt,因为它对 tool_approvals:read 开放),任一渠道先结算算数。审批永远是新鲜人批:只认 Console / IM 私聊 / 审批群这三个人工渠道的批准,bypass、自动审核、记住授权、回合内审批端口都不能代批;批准绑定 content_hash,内容对不上 → rejected(change_hash_mismatch)。同一发起会话同时最多 1 个待审变更:已有待审时 change_draft 直接告诉 Agent 在等审批,不起草新变更。审批时限 24 小时,超时 → expired(终态,删暂存正文)。审批队列只在内存里,每次启动为仍 pending 的变更按剩余时限重建卡片并重新投递。批准后变更停在 approved,生效与回报见后续切片。磁盘:审批不新增落盘内容,结算后 rejected / expired 按 §6a 立即瘦身。

生效、校验与回滚(ADR 0040 §5 / §6,切片 E1)。 生效核心 SelfChangeApplier 接 approved 的变更(审批结算后经 SelfChangeApprovals 的 onApproved 交过来):先逐个暂存文件重算摘要、复核批准绑定的 content_hash 与 configPatch.expectedRevision,任何一项不符 → failed(change_hash_mismatch),不动任何文件,只回报发起会话。通过后先写回滚快照 snapshot/(目标文件原文、原本不存在的记为不存在,带配置补丁时另存根配置源文),再把 applying、回报目标与生效进度落盘,然后才写生产文件(同目录临时文件 + rename,每次写前重新过路径白名单,不跟随符号链接)并应用配置补丁。按预览的影响面:none 直接校验;generation 调宿主热重载(与开发态文件监听同一入口),宿主说需要重启或配置应用明确返回待重启项时升级为 process;process 先落盘 verifying 再请求受控重启(exit 75)。健康检查默认 60 秒窗口,超时按不健康处理。健康 → applied,原会话回报「已生效」(重启过的加「已重启」),暂存区只剩 change.json。不健康 → 用快照回滚一次并按同样方式恢复:恢复健康 → rolled_back,回报「失败,已回滚」与原因;回滚做不了或回滚后仍不健康 → failed,打开控制面实例级 emergency_stop,回报只投主人私聊,保留 files/ 与 snapshot/ 现场,不再自动重启。每个变更最多为它重启两次(正向一次、回滚一次),防止重启循环。recover() 在每次启动时接续:verifying 做健康检查后给结论;applying(生效中途进程没了,文件可能只写了一半)在配置归属有证据且没有未确认写入或应用时按快照回滚;否则保留现场交人工核验。回报结果写审计(change_report_sent / change_report_redirected),被拦不影响变更状态。磁盘:快照放在该变更自己的 snapshot/,applied / rolled_back 时随 files/ 一起删除;failed 保留到人在 Console 标记已处理。

宿主接线(切片 E2)。 zhin runtime start 进程持有生效宿主,最新一代 Agent 绑定生效器,生效跨代串行:热重载走 RootHost.reloadSources()——与开发态文件监听同一条 HMR 路径(模块失效 + InvalidationPlanner),未启用监听时也可用。配置已由 Console 保存并确认应用时不重复重载根配置;已获准执行的纯配置变更若凭证明确全部已应用且无待重启项,直接校验,收敛原先保守的重启预估;混合变更仍按源码影响面处理。前置审批与无守护进程拒绝重启的门禁不变。旧宿主未返回应用凭证时保留原配置重载路径,计划明确需要重启时仍按原流程处理。重启即受控 exit 75(与 Console「重启」同一出口);生效进行中,文件监听触发的重启请求推迟:由生效流程在落盘 verifying 之后发起,流程结束时仍未重启则补发。健康检查:已有提交的 generation、变更文件所属插件都已装配、且从开始生效(重启后为本进程启动)起运行时没有报过错误。配置补丁按顶层段逐段走 Console config:patch(宿主段用宿主白名单、其余段限定在该插件实例内,revision 依次衔接;已知校验失败按原回滚流程处理。生效不确认、写入回执不确认或修订被替代时停止自动续作,记录准确原因并保留暂存与快照供人工核验,不误报已生效或已回滚,也不因此自动急停。应用进度持久化自己最后确认的配置 revision;恢复与回滚只用该 revision 做 CAS,发现他人修改或旧记录缺乏归属证据时不覆盖当前配置)。回报:发起会话过控制面,被拦记 blocked;发不出去(会话不存在、Bot 已不在群里、被禁言)改投该端点主人私聊(change_report_redirected);急停回报只投主人私聊且不受控制面拦截(它要告诉主人刚刚打开的急停)。急停即以 self_change:<id> 打开实例级 emergency_stop。每次启动完成后(--once 除外)先 recover(),再处理「已批准但还没来得及生效」(批准后进程就退出了)的变更(#1051 Q2=B):批准不超过 1 小时的依次生效;更早的转 expired(不动任何文件,审计 change_expired),并私聊主人「批准已过期,请重新起草」(批准时的上下文可能已经过时)。

本地插件根与无守护进程(#1051 F1)。 宿主从当前已提交 generation 的源码归属里取项目内的本地插件根(workspace / 本地路径插件;真实路径须在项目根内、不含 node_modules,所以指向 node_modules 或项目外的符号链接不算),起草、试跑(按插件根分组)与生效三处共用同一份:plugins/<x>/commands/... 等约定目录可以起草,node_modules 里的插件仍拒绝;每次使用时重读,HMR 新一代自动更新。只有 zhin start 守护进程拉起(ZHIN_SUPERVISOR_PID 须为十进制 pid 且等于父进程 pid,镜像里继承的固定值如 ZHIN_SUPERVISOR_PID=1 不算)且不是 --once 时才算能自动重启;无守护进程或 --once 都视为无法自动重启:否则影响面为 process 的变更试跑通过后停在 ready、不提交审批,change_draft 告诉模型本实例没法自动生效(审计 change_restart_unavailable);已批准的变更生效时若计划要重启则不动文件直接 failed,生效中途才升级为重启(热重载要求重启、配置补丁需要重启)则按快照回滚、不重启,结论 rolled_back,原因码同为 change_restart_unavailable。回滚本身需要重启时(恢复配置要重启、恢复后的热重载要求重启、recover() 接续的回滚要重启)不请求重启、也不报「已回滚」,直接急停并私聊主人。

谁能起草与全实例待审上限(#1051 F4)。 只有 Bot 的主人(端点 owner / trigger.masters,回合角色 master)与 IM 审批人(toolHardGate.imApproval 开启时 approverIds 里该平台的人)能起草:其他人的回合里 change_draft 不出现在工具面(模型看不到就不会尝试);万一被调用,直接拒绝、不写暂存,审计 change_draft_forbidden(deny)。群成员、群管理员、trusted、私聊陌生人都不能起草;审批人范围不变。另外全实例同时最多 3 个待审变更:已满时 change_draft 不起草、告诉模型等主人处理掉几张再来(审计 change_pending_cap_reached),挡住多会话刷卡。

禁改配置键的维护方式(#1051 F2)。 上文暂存区一段列出的 SELF_CHANGE_SECURITY_CONFIG_KEYS 与宿主 schema 中标 x-zhin-config.security 的键由守卫单测保持一致:schema 里新加安全标记的键一定进拒绝集,避免名单漏网;拒绝发生在起草时,生效前再按当前名单复核一次。bypass、自动审核、主人身份、记住授权都不能让这些键经 configPatch 进审批——要改只能由人自己编辑配置文件——人在编辑器里直接改不经过 Bot 的任何闸门;让 Bot 用文件工具或 shell 写根配置仍是 self 硬闸,每次都要新鲜人工批准。

收口状态(#1051,ADR 0040 已 Accepted)。 Fin 确认的默认值见 ADR 0040「Fin 已确认的默认值」。要点:试跑里被跳过(skipped)的检查不阻断提交,审批卡的试跑结论标出跳过数并列出被跳过的检查名(「通过 (N skipped);跳过:…」,逐项结论在 change.json 的 trial 与 Console 详情);一次人工批准即发布,不设第二道审批;新建本地插件包与 npm 安装依赖不在本期。回归由脚本化 harness eval(packages/im/agent/evals/cases/self-change-*.case.ts:主人私聊起草 /ping、试跑失败后重新起草、禁改键被拒后不改用文件写入、已有待审时不再起草、群成员不能起草、不直接写约定目录)与 basic/cli/tests/plugin-runtime/self-change-e2e.test.ts(假守护进程 exit 75 → recover() → 回报、回滚一次、急停、生效中途被杀、残留 approved 的 1 小时时限)覆盖,随 pnpm --filter @zhin.js/agent eval 与单测运行。审批可见性(#1142 F3):Host 侧 self-change-detail RPC 与 IM 卡 diff 摘录已提供,见上文「审批」;Console「自我变更」页面(调 self-changes / self-change-detail / resolve-self-change / mark-self-change-handled)随 zhinjs/console 配套版本发布。用户向说明见让 Bot 改自己。

yaml
ai:
  agent:
    toolHardGate:
      dangerousTools: [my_plugin__wipe_album]  # 只能追加,不能把核心名单调低
      allowFunds: false
      approvalTimeoutMs: 300000                # 超时即拒绝
      maxPendingPerScene: 3                    # 每群同时待审卡上限,超出 fail closed

审批卡片在 Console RPC plugin.root.tool-approvals(读,权限 tool_approvals:read 或 ops:approve)和 plugin.root.resolve-tool-approval({ id, decision: 'approve' | 'deny' },权限 ops:approve 或旧的 tool_approvals:resolve,任一即可)上;完整令牌可直接调用。群管冷却(ADR 0037 §3):同一 platform + scene_id + 工具本地名 在 ops.cooldownMs(默认 60 秒,会话运营配置里设置)内只生成一张审批卡,冷却中直接拒绝、不生成新卡(ops_cooldown / ops_cooldown_hit)。每次判定写入 ai_decision_events:tool_approval(tool_approval_requested / tool_approval_approved / tool_approval_denied / tool_approval_timeout)、tool_hard_gate(allow:tool_approval_approved;deny:tool_dangerous_no_approval、tool_approval_denied、tool_approval_timeout、tool_funds_disabled、tool_hard_gate_latched、hard_gate_scene_pending_cap(每群待审卡已满)),以及 safe / 只读工具的 tool_call(allow,tool_safe_allowed)——只读查询放行但仍写审计。审计写失败不影响判定。

IM 审批通道(#1013,默认关) ​

值班人员可以在 IM 里批硬闸卡片,不必登录 Console。它与 Console 共用同一个审批队列,任一渠道先给出结论算数,另一渠道看到已决;仍然只认人工、每次一批、fail closed。

yaml
ai:
  agent:
    toolHardGate:
      imApproval:
        enabled: true
        approverIds:            # 扁平列表对所有平台生效;也可写成 平台 → 用户 id
          icqq: ['10001']
        delivery: dm            # dm(默认)私聊审批人;approver_group 发到 approverGroupId
        # approverGroupId: '123456'   # delivery: approver_group 时必填,且不能是业务群
  • 每张新卡片(复用的同参数卡片不重复发)通过触发调用的那个 Endpoint 私聊发给该平台的每位审批人,内容含工具名、类别、会话、脱敏参数摘要、卡片 id 与过期时间。
  • 审批人在私聊里发 /approve <卡片 id> 或 /deny <卡片 id>(前缀随命令前缀配置)。只认 allowlist 内的用户;群 / 频道里的消息一律不结算(approver_group 模式下仅认配置的审批群),触发调用的会话永远不能结算自己的卡片;业务群里说「同意 / 批准」不会结算。
  • 批准只放行这一次同会话、同参数的调用(沿用 consumeApproved 指纹),没有「本会话 / 始终允许」。IM 通道是宿主命令,不是模型可调用的工具,bypass 和审核 Agent 都碰不到它。
  • 投递失败或不可投递(未开启、该平台没有审批人、approver_group 未配置群或与业务会话相同)时,卡片仍留给 Console,超时照常拒绝;不重试投递。队列在内存中,重启后旧卡片失效。
  • 审计:tool_approval 的结算记录 payload.channel(console / im_dm / im_approver_group,回合内审批提示为 turn_prompt),操作者记在 actor_id:IM 为 im:<platform>:<userId>,Console 暂为 console。tool_approval_requested 的 payload.imApproval 表示该卡片是否可经 IM 投递。

开关清单与处置 ​

处置:keep 保留常用;preset 高级调优,用默认值;merge-candidate 计划合并;debug 诊断;internal 仅代码;deprecated 已弃用。

开关处置说明
personakeep常用开关,保留
maxIterationskeep常用开关,保留
timeoutkeep常用开关,保留
chatModelkeep常用开关,保留
visionModelkeep常用开关,保留
contextTokenskeep常用开关,保留
execSecuritykeep常用开关,保留
execPresetkeep常用开关,保留
networkkeepIM 回合出网权限的独立开关(取代 execPreset: network 兼带出网),保留
execAllowlistkeep常用开关,保留
execApprovalModekeep常用开关,保留
disabledToolskeep常用开关,保留
allowedToolskeep常用开关,保留
mailboxkeep常用开关,保留
reactionskeep表情回应限频:会话内 cooldownMs 600000、放行概率 probability 0.35;probability: 0 关闭表情回应
toolBudgetkeep常用开关,保留
injectionGuardkeep常用开关,保留
toolHardGatekeep危险工具硬闸(ADR 0036);始终开启,只能调严,见下文
usageQuotakeep常用开关,保留
schedulekeep常用开关,保留
masterShellkeep常用开关,保留
masterShellApprovalModekeep仅用于 masterShell: isolated 的主人私聊;默认 ask 询问主人,auto 自动审核(拒绝时转问主人),inherit 沿用 execApprovalMode
compactionkeep常用开关,保留
rateLimitkeep常用开关,保留
preExecTimeoutpreset高级调优,默认值即推荐值,不建议修改
maxToolspreset高级调优,默认值即推荐值,不建议修改
contextTailMessageLimitpreset高级调优,默认值即推荐值,不建议修改
toneAwarenesspreset高级调优,默认值即推荐值,不建议修改
maxHistorySharepreset高级调优,默认值即推荐值,不建议修改
toolExecutionpreset高级调优,默认值即推荐值,不建议修改
maxSubagentIterationspreset高级调优,默认值即推荐值,不建议修改
modelSizeHintpreset高级调优,默认值即推荐值,不建议修改
skillInstructionMaxCharspreset高级调优,默认值即推荐值,不建议修改
modelHarnesspreset高级调优,默认值即推荐值,不建议修改
providerResiliencepreset高级调优,默认值即推荐值,不建议修改
providerTimerspreset高级调优,默认值即推荐值,不建议修改
promptCachepreset高级调优,默认值即推荐值,不建议修改
promptCacheRetentionpreset高级调优,默认值即推荐值,不建议修改
promptCacheKeyPrefixpreset高级调优,默认值即推荐值,不建议修改
deferredToolspreset高级调优,默认值即推荐值,不建议修改
outputSchemapresetIM 默认使用受 Schema 约束的有序消息段;详见结构化回复
steeringModepreset高级调优,默认值即推荐值,不建议修改
followUpModepreset高级调优,默认值即推荐值,不建议修改
gitStatuspreset高级调优,默认值即推荐值,不建议修改
contextPathspreset高级调优,默认值即推荐值,不建议修改
systemPromptMaxCharspreset高级调优,默认值即推荐值,不建议修改
thinkingPreviewpreset高级调优,默认值即推荐值,不建议修改
thinkingPreviewMaxLengthpreset高级调优,默认值即推荐值,不建议修改
phaseTracedebug仅诊断用
promptTracedebug仅诊断用
promptTraceVerbosedebug仅诊断用
recordRequestBodieskeep默认记录本地脱敏请求正文;设为 false 可关闭新增正文留存,保留用量统计
contextHistoryinternal代码字段,由运行时从 ai.context.historyTokens / maxRecentMessages 填充(ADR 0043),YAML 不可配置
onPhaseTraceinternal代码回调,YAML 不可配置
maxSkillsdeprecated运行时无读取,已弃用(#735),--fix 可移除
policyDenialStopAfterdeprecated运行时无读取,已弃用(#735),--fix 可移除
deferredToolMaxResultsdeprecated运行时无读取,已弃用(#735),--fix 可移除
taskExecApprovalModedeprecated运行时无读取,已弃用(#735),--fix 可移除

弃用路径与兼容 ​

  1. 已弃用键仍被接受,不会导致启动失败;
  2. 启动时每个已弃用键打印一次警告;
  3. zhin config check 报告 ai.agent_<key>_deprecated;
  4. zhin config check --fix 自动删除这些键;
  5. 至少保留一个 minor 版本后再从类型中删除。

新增开关的门禁 ​

新增 ai.agent.* 开关必须先登记到上表并在 PR 模板中说明理由;pnpm check:agent-config-inventory(包含在 pnpm check:all 中)会检查清单是否完整,以及非弃用开关数量没有超过基线。