From 77483f61d9ef0294ba35da92eee63d451def87f4 Mon Sep 17 00:00:00 2001 From: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com> Date: Mon, 12 Oct 2026 03:04:08 -0700 Subject: [PATCH] fix(subagent): respect optional runtime context suppression --- ...nt-persona-tool-filter-and-depth.i18n.yaml | 4 ++-- ...-subagent-persona-tool-filter-and-depth.md | 2 +- ...bagent-persona-tool-filter-and-depth.zh.md | 2 +- packages/preset/persona/README.i18n.yaml | 4 ++-- packages/preset/persona/README.md | 2 +- packages/preset/persona/README.zh.md | 2 +- .../subagent/tool-subagent/README.i18n.yaml | 8 +++---- packages/subagent/tool-subagent/README.md | 4 ++-- packages/subagent/tool-subagent/README.zh.md | 4 ++-- packages/subagent/tool-subagent/src/index.ts | 1 - .../tool-subagent/tests/depth-context.spec.ts | 8 ++++++- .../runtime.cordis.yml | 17 ++++++++++++++ .../session.v4.jsonl | 22 +++++++++++++++++++ .../snapshot.yml | 7 ++++++ 14 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 snapshots/session/subagent-depth-context-disabled/runtime.cordis.yml create mode 100644 snapshots/session/subagent-depth-context-disabled/session.v4.jsonl create mode 100644 snapshots/session/subagent-depth-context-disabled/snapshot.yml diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml index 7b072cde51..91dec1fe94 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml @@ -18,8 +18,8 @@ en: a9fe46283ea28722 zh: 7b7d1be7c070b893 /agent-note-configure-subagent-persona-tool-visibility-and-depth/decision/depth-is-an-absolute-tree-cap: - en: 432aeeeec0721802 - zh: fbdfea5be3bc83ed + en: 28816df0fde0ef56 + zh: bf906e6c286645e0 /agent-note-configure-subagent-persona-tool-visibility-and-depth/decision/capability-gating-keeps-providers-honest: en: 5334afafefdb0f59 zh: 7e96c9a08bbc3d1a diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md index ca0d3a2dd1..53582a4009 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md @@ -55,7 +55,7 @@ The effective parent depth is the greater of durable `SessionHeader.delegationDe Every public entry validates the domain rather than relying on one model-facing configuration path. Negative values, fractions, negative zero, non-finite values, unsafe integers, malformed stored parent depth, and derived overflow all reject. A direct `SubagentStartRequest` may omit the cap to leave depth unbounded; `dsh-tool-subagent` instead reads the live Host depth setting (initially `1`), accepts a numeric override, and uses explicit `'provider-managed'` to omit the cap for an out-of-process provider whose deployment owns its recursion budget. The [base profile](../../../../packages/bundle/base/cordis.patch.yml) uses the Host setting. A numeric tool cap fails at provider mount when the provider lacks `depthLimit`. -A deployment can combine depth and filtering, but the numeric cap does not synthesize a filter. The delegation tool stays visible at the cap because authorization may depend on runtime state; every attempted start checks the calling agent's current durable and runtime depth, and a rejected start returns an errored tool result without publishing a child. A deployment may separately deny delegation tools in children when its visibility policy is static. Neither choice changes the provider's conversation-history behavior. At the cap, the tool contributes a required runtime-context notice telling the model to work directly. Reading the live cap and durable depth at request time keeps resumed and reconfigured agents informed without changing their system prompt or tool schemas; unchanged context is not repeatedly appended. +A deployment can combine depth and filtering, but the numeric cap does not synthesize a filter. The delegation tool stays visible at the cap because authorization may depend on runtime state; every attempted start checks the calling agent's current durable and runtime depth, and a rejected start returns an errored tool result without publishing a child. A deployment may separately deny delegation tools in children when its visibility policy is static. Neither choice changes the provider's conversation-history behavior. At the cap, the tool contributes an optional runtime-context notice telling the model to work directly. `includeRuntimeContext: false` suppresses this advisory notice; the runtime depth guard remains active. When enabled, the notice reads the live cap and durable depth at request time to keep resumed and reconfigured agents informed without changing their system prompt or tool schemas; unchanged context is not repeatedly appended. ### Capability gating keeps providers honest diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md index 03635d585e..6d1760d8e9 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md @@ -57,7 +57,7 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma 每个公开入口都自行验证值域,而非依赖单一的面向模型配置路径。负值、小数、负零、非有限值、不安全整数、格式错误的存储父级深度以及推导溢出均被拒绝。直接的 `SubagentStartRequest` 可以省略上限,让此机制不约束深度;`dsh-tool-subagent` 则读取实时 Host 深度设置(初始值为 `1`)、接受数值覆盖,并使用显式的 `'provider-managed'` 来省略由进程外提供方部署拥有递归预算时的上限。[base profile](../../../../packages/bundle/base/cordis.patch.yml)采用 Host 设置。提供方缺少 `depthLimit` 时,数值工具上限会在提供方挂载阶段失败。 -部署可以组合深度与过滤,但数值上限不会合成过滤器。委派工具在上限处仍然可见,因为授权可能依赖运行时状态;每次尝试启动都会检查调用方 agent 当前的持久与运行时深度,被拒绝的启动返回错误工具结果,且不发布子 agent。可见性策略固定的部署可以另外在子 agent 中 deny 委派工具。两种选择都不改变提供方的对话历史行为。达到上限时,工具贡献一条必需的运行时上下文提示,让模型直接完成工作。每次请求读取实时上限与持久深度,使恢复会话或调整配置后的 agent 获知当前限制,同时不改变其系统提示词或工具 schema;未变化的上下文不会重复追加。 +部署可以组合深度与过滤,但数值上限不会合成过滤器。委派工具在上限处仍然可见,因为授权可能依赖运行时状态;每次尝试启动都会检查调用方 agent 当前的持久与运行时深度,被拒绝的启动返回错误工具结果,且不发布子 agent。可见性策略固定的部署可以另外在子 agent 中 deny 委派工具。两种选择都不改变提供方的对话历史行为。达到上限时,工具贡献一条可选的运行时上下文提示,让模型直接完成工作。`includeRuntimeContext: false` 会抑制这条建议性提示;运行时的深度拦截仍然生效。提示启用时,每次请求读取实时上限与持久深度,使恢复会话或调整配置后的 agent 获知当前限制,同时不改变其系统提示词或工具 schema;未变化的上下文不会重复追加。 ### 能力门控保持提供方诚实 diff --git a/packages/preset/persona/README.i18n.yaml b/packages/preset/persona/README.i18n.yaml index 67d0cf506c..5e8b268500 100644 --- a/packages/preset/persona/README.i18n.yaml +++ b/packages/preset/persona/README.i18n.yaml @@ -48,8 +48,8 @@ en: 2b53c1d07ef68f24 zh: 33a3c17ab56adf57 /deepseek-ai-dsh-persona/model-experience/the-persona-section/what-the-model-sees: - en: 164029cbcb3f09e1 - zh: 884aa8e9c7820caa + en: 5c276dd49a9c5053 + zh: be5a329928edde7a /deepseek-ai-dsh-persona/model-experience/the-persona-section/token-effect: en: 346e80f3b7a8c7fd zh: da0e19a955d56c51 diff --git a/packages/preset/persona/README.md b/packages/preset/persona/README.md index 5b58596d85..4ba0d3c120 100644 --- a/packages/preset/persona/README.md +++ b/packages/preset/persona/README.md @@ -96,7 +96,7 @@ Read these pages when the package-level contract is not enough; they move from t #### What the model sees -The `deployment:persona-prefix` section at order `0` carries this row's `prefix`; `deployment:persona-suffix` at order `10200` carries its `suffix`, after first-party guidance. Both replace their deployment defaults and resolve prompt variables. In complete mode, the model sees only the rendered prefix section as its system prompt. Runtime context remains enabled by default. With `includeRuntimeContext: false`, the model still receives required context, such as the current working directory and applicable delegation-depth limits; optional context is omitted. +The `deployment:persona-prefix` section at order `0` carries this row's `prefix`; `deployment:persona-suffix` at order `10200` carries its `suffix`, after first-party guidance. Both replace their deployment defaults and resolve prompt variables. In complete mode, the model sees only the rendered prefix section as its system prompt. Runtime context remains enabled by default. With `includeRuntimeContext: false`, the model still receives required context, such as the current working directory; optional context is omitted. #### Token effect diff --git a/packages/preset/persona/README.zh.md b/packages/preset/persona/README.zh.md index 9e64c2810a..907c2aebae 100644 --- a/packages/preset/persona/README.zh.md +++ b/packages/preset/persona/README.zh.md @@ -96,7 +96,7 @@ kind: "package-reference" #### 模型看到什么 -位于 order `0` 的 `deployment:persona-prefix` 段落携带本行的 `prefix`;位于 order `10200` 的 `deployment:persona-suffix` 在第一方指导之后携带其 `suffix`。两者分别替换对应的部署默认值,并解析提示词变量。在完整模式下,模型只会看到渲染后的前缀段落作为系统提示词。运行时上下文默认保持启用。设置 `includeRuntimeContext: false` 后,模型仍会收到必需上下文,例如当前工作目录和适用的委派深度限制;可选上下文则会省略。 +位于 order `0` 的 `deployment:persona-prefix` 段落携带本行的 `prefix`;位于 order `10200` 的 `deployment:persona-suffix` 在第一方指导之后携带其 `suffix`。两者分别替换对应的部署默认值,并解析提示词变量。在完整模式下,模型只会看到渲染后的前缀段落作为系统提示词。运行时上下文默认保持启用。设置 `includeRuntimeContext: false` 后,模型仍会收到必需上下文,例如当前工作目录;可选上下文则会省略。 #### Token 影响 diff --git a/packages/subagent/tool-subagent/README.i18n.yaml b/packages/subagent/tool-subagent/README.i18n.yaml index 796ea2972b..2caa85634d 100644 --- a/packages/subagent/tool-subagent/README.i18n.yaml +++ b/packages/subagent/tool-subagent/README.i18n.yaml @@ -93,14 +93,14 @@ en: 1e58e87365d88e45 zh: 7648aa3b3865057f /deepseek-ai-dsh-tool-subagent/model-experience/runtime-depth-context/what-the-model-sees: - en: 5d8cacfcc690735e - zh: ccb5657677f4f24b + en: fde048c427103086 + zh: 6614730cc00f02e4 /deepseek-ai-dsh-tool-subagent/model-experience/runtime-depth-context/what-the-model-sees/depth-limit-notice: en: 0ae8dbcee66d0cd6 zh: 5a69283739ae59e0 /deepseek-ai-dsh-tool-subagent/model-experience/runtime-depth-context/token-effect: - en: 85780200aeda7d71 - zh: ded2dc8ff9b5a677 + en: dae9cd59b9649b5a + zh: 8bb35289ebaeaf7b /deepseek-ai-dsh-tool-subagent/model-experience/runtime-depth-context/kv-cache-effect: en: 9e6e8bc993992908 zh: 4de1e9d85ab3bfca diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index a04e8928bd..3dbe464476 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -176,7 +176,7 @@ Prefix-stable while the visible tool names are unchanged; adding or removing a v #### What the model sees -When the calling agent reaches or exceeds a visible tool's effective numeric depth cap, a required runtime-context snapshot tells it to do the work directly. The cap is resolved again at each request, including after settings changes or session resume. Hidden tools and `provider-managed` limits contribute no depth text. For a child at depth one with a cap of one: +When the calling agent reaches or exceeds a visible tool's effective numeric depth cap, an optional runtime-context snapshot tells it to do the work directly. The cap is resolved again at each request, including after settings changes or session resume. Hidden tools and `provider-managed` limits contribute no depth text. For a child at depth one with a cap of one: ##### Depth-limit notice @@ -186,7 +186,7 @@ Delegation depth for `subagent`: current 1, maximum 1, remaining levels 0. Do no #### Token effect -One short contribution per capped tool joins the user-role runtime-context snapshot. Unchanged snapshots are not appended again; agents below the cap receive no additional text. Optional-context suppression retains this operational limit notice. +One short contribution per capped tool joins the user-role runtime-context snapshot. Unchanged snapshots are not appended again; agents below the cap receive no additional text. `includeRuntimeContext: false` suppresses this notice without changing runtime depth enforcement. #### KV Cache effect diff --git a/packages/subagent/tool-subagent/README.zh.md b/packages/subagent/tool-subagent/README.zh.md index 74964076f8..e6c4604777 100644 --- a/packages/subagent/tool-subagent/README.zh.md +++ b/packages/subagent/tool-subagent/README.zh.md @@ -176,7 +176,7 @@ Start independent delegations with `subagent` or `subagent_fork` together in one #### 模型看到什么 -当调用方 agent 达到或超过某个可见工具实际生效的数值深度上限时,必需的运行时上下文快照会提示它直接完成工作。每次请求都会重新解析上限,包括 settings 变更或会话恢复之后。隐藏的工具和 `provider-managed` 上限不贡献深度文本。深度和上限都为一时,子 agent 看到: +当调用方 agent 达到或超过某个可见工具实际生效的数值深度上限时,可选的运行时上下文快照会提示它直接完成工作。每次请求都会重新解析上限,包括 settings 变更或会话恢复之后。隐藏的工具和 `provider-managed` 上限不贡献深度文本。深度和上限都为一时,子 agent 看到: ##### 深度上限提示 @@ -186,7 +186,7 @@ Delegation depth for `subagent`: current 1, maximum 1, remaining levels 0. Do no #### Token 影响 -每个达到上限的工具向用户角色的运行时上下文快照贡献一小段文本。未变化的快照不会重复追加;未达到上限的 agent 不收到额外文本。抑制可选上下文时,仍保留这条操作限制提示。 +每个达到上限的工具向用户角色的运行时上下文快照贡献一小段文本。未变化的快照不会重复追加;未达到上限的 agent 不收到额外文本。`includeRuntimeContext: false` 会抑制这条提示,但不改变运行时对深度上限的强制执行。 #### KV Cache 影响 diff --git a/packages/subagent/tool-subagent/src/index.ts b/packages/subagent/tool-subagent/src/index.ts index faede4a3d8..17a17d23fd 100644 --- a/packages/subagent/tool-subagent/src/index.ts +++ b/packages/subagent/tool-subagent/src/index.ts @@ -387,7 +387,6 @@ export function apply(ctx: Context, config: Config, session?: Session): void { runtimeCtx.systemPrompt.context({ name: `tool:${toolName}:depth`, order: runtimeCtx.systemPrompt.getContextOrder('SUBAGENT_DELEGATION'), - required: true, text: (context) => { if (context.agent === undefined || mounted === undefined || runtimeCtx.tools.get(toolName, context.scope) !== mounted.definition) return '' diff --git a/packages/subagent/tool-subagent/tests/depth-context.spec.ts b/packages/subagent/tool-subagent/tests/depth-context.spec.ts index 6d1e47b775..6bd34e9ed5 100644 --- a/packages/subagent/tool-subagent/tests/depth-context.spec.ts +++ b/packages/subagent/tool-subagent/tests/depth-context.spec.ts @@ -38,8 +38,14 @@ describe('delegation depth context', () => { + ' Do not call `subagent` at this depth; carry out the work directly.', ] : []) const restore = agent.ctx.systemPrompt.suppressRuntimeContext() + try { + expect(await depthContext(ctx, agent)).toEqual([]) + expect(ctx.systemPrompt.refreshContext(assembly, assembleContextFor(agent)) + .contexts.some(part => part.name.endsWith(':depth'))).toBe(false) + } finally { + restore() + } expect(await depthContext(ctx, agent)).toHaveLength(remaining === 0 ? 1 : 0) - restore() }) it('uses the runtime depth when it is deeper than the saved header', async () => { diff --git a/snapshots/session/subagent-depth-context-disabled/runtime.cordis.yml b/snapshots/session/subagent-depth-context-disabled/runtime.cordis.yml new file mode 100644 index 0000000000..c453fb296b --- /dev/null +++ b/snapshots/session/subagent-depth-context-disabled/runtime.cordis.yml @@ -0,0 +1,17 @@ +# Suppress optional hints at the root depth cap while retaining the tool and +# the provider's runtime enforcement. +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + includeRuntimeContext: false + personaPrefix: | + You are a coding assistant powered by the {{model}} model. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. + + Verify your work by running the code or tests. Keep answers brief and factual. + +- id: tool-subagent + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: spawn + toolName: subagent + maxDepth: 0 diff --git a/snapshots/session/subagent-depth-context-disabled/session.v4.jsonl b/snapshots/session/subagent-depth-context-disabled/session.v4.jsonl new file mode 100644 index 0000000000..c4830eb1db --- /dev/null +++ b/snapshots/session/subagent-depth-context-disabled/session.v4.jsonl @@ -0,0 +1,22 @@ +{"type":"session","version":4,"id":"{{session:1}}","createdAt":1783352044766,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} +{"type":"permission/preset","data":{"preset":"danger-full-access"}} +{"type":"sandbox/mode","data":{"mode":"danger-full-access"}} +{"type":"approval/policy","data":{"policy":"never"}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Attempt one subagent call beyond the configured cap, then report the rejection."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} +{"type":"turn/start","data":{"turn":1}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"step/start","data":{"turn":1,"step":1}} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Attempt one subagent call beyond the configured cap, then report the rejection."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} +{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} +{"type":"session/title","data":{"title":"Attempt one subagent call beyond","messageSeqs":[8],"source":{"kind":"fallback"}}} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_depth_one_rejected","name":"subagent","arguments":"{\"description\":\"Exceed depth cap\",\"prompt\":\"This child must never start.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1789625678968,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1789625678968,"index":0,"dt":[],"id":"call_depth_one_rejected","name":"subagent","args":["{\"description\":\"Exceed depth cap\",\"prompt\":\"This child must never start.\"}"]},{"type":"chunk","time":1789625678968,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_depth_one_rejected","name":"subagent","arguments":"{\"description\":\"Exceed depth cap\",\"prompt\":\"This child must never start.\"}"}}},{"type":"chunk","time":1789625678968,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1789625678968,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_depth_one_rejected","name":"subagent","arguments":"{\"description\":\"Exceed depth cap\",\"prompt\":\"This child must never start.\"}"}} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_depth_one_rejected"},"toolCallId":"call_depth_one_rejected","content":[{"type":"text","text":"Error: subagent depth 1 exceeds maxDepth 0"}],"isError":true,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":1}} +{"type":"step/start","data":{"turn":1,"step":2}} +{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DEPTH_REJECTED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":2},"stream":[{"type":"chunk","time":1789625678991,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1789625678991,"index":0,"dt":[],"texts":["DEPTH_REJECTED"]},{"type":"chunk","time":1789625678991,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DEPTH_REJECTED"}}},{"type":"chunk","time":1789625678991,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}},{"type":"chunk","time":1789625678991,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":2}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/snapshots/session/subagent-depth-context-disabled/snapshot.yml b/snapshots/session/subagent-depth-context-disabled/snapshot.yml new file mode 100644 index 0000000000..4eb90503fa --- /dev/null +++ b/snapshots/session/subagent-depth-context-disabled/snapshot.yml @@ -0,0 +1,7 @@ +version: 1 +scenario: subagent-depth-context-disabled +profile: headless +composition: default +recording: authored +header: + class: default