From 51e1f48540127f5a47b372b3a615a0f840f6c7ef Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Fri, 7 Aug 2026 14:45:30 +0800 Subject: [PATCH 1/2] fix(cli): keep the subagent registry host-plane in the cordis preset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cordis preset moved `bash-env`, the `subagents` registry, and its spawn/fork backends into entry-local realms. A host row that injects a service is the criterion for host-plane ownership: `apps/cli/src/web.ts` injects `bash-env`, and `dsh-host-apiproxy` injects `subagents` to answer the browser's cross-session queries, so both waited forever for a service only sessions provided. A provider name also registers once, so the second session would have collided regardless. This layer introduces the preset, so it is where the plane belongs — a later layer already corrected it, which left this layer and the docs one unmountable on their own. The composition test stops disabling `api-gateway` with it: the row whose pending injection names the break cannot be the row the test turns off, so the boot audit now covers the whole host-plane injection graph. --- ...-08-03-per-session-agent-presets.i18n.yaml | 4 +- .../2026-08-03-per-session-agent-presets.md | 2 +- ...2026-08-03-per-session-agent-presets.zh.md | 2 +- .../agent-presets/cordis/agent.cordis.yml | 65 ++++++++----------- .../editing-cordis-compositions/SKILL.md | 6 +- apps/cli/tests/web-agent-presets.e2e.ts | 15 +++-- 6 files changed, 45 insertions(+), 49 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml index 3d91acee93..9280260d00 100644 --- a/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md -2026-08-03-per-session-agent-presets.md: dbf2f7c3de1447382071ebcfb1d6b9abe9640210 -2026-08-03-per-session-agent-presets.zh.md: e34018f713144c6457c86d5ce4b533106a7e5372 +2026-08-03-per-session-agent-presets.md: 63bbc361b7968c3447cffaf8134422b7b78af98f +2026-08-03-per-session-agent-presets.zh.md: bee5ba37dd27db22c18c9d1dd6d58965ee66b80e diff --git a/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md b/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md index dbf2f7c3de..63bbc361b7 100644 --- a/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md +++ b/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md @@ -19,7 +19,7 @@ Composition splits into two planes, decided by what must be shared rather than b | Plane | Instances | Contents | |---|---|---| | Host | one | The registries themselves (`tools`, `systemPrompt`, `agents`, `agent-loop`, `sessions`), cross-session facilities (persistence, query, projections, storage, settings, credentials, telemetry), and the web host | -| Agent | one per session | What a single agent contributes to those registries: tool plugins, persona and prompt sections, delegation backends, compaction policy | +| Agent | one per session | What a single agent contributes to those registries: tool plugins, persona and prompt sections, compaction policy | Model routing stays out of presets. `installAgentLlmTarget` is already the per-agent seam for provider, model, and reasoning effort, and an LLM adapter mounted inside a preset would never be resolved by `agent-loop`, which lives in the host plane. diff --git a/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.zh.md b/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.zh.md index e34018f713..bee5ba37dd 100644 --- a/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.zh.md @@ -19,7 +19,7 @@ Status: implemented | 平面 | 实例数 | 内容 | |---|---|---| | 宿主 | 一份 | 注册表本身(`tools`、`systemPrompt`、`agents`、`agent-loop`、`sessions`)、跨会话设施(持久化、查询、投影、存储、设置、凭据、遥测),以及 web 宿主 | -| agent | 每会话一份 | 单个 agent 对这些注册表的贡献:工具插件、人设与提示词段落、委派后端、压缩策略 | +| agent | 每会话一份 | 单个 agent 对这些注册表的贡献:工具插件、人设与提示词段落、压缩策略 | 模型路由不进 preset。`installAgentLlmTarget` 已经是 provider、model 与 reasoning effort 的按 agent 可替换点;而挂在 preset 内部的 LLM 适配器永远不会被 `agent-loop` 解析到,因为后者位于宿主平面。 diff --git a/apps/cli/config/agent-presets/cordis/agent.cordis.yml b/apps/cli/config/agent-presets/cordis/agent.cordis.yml index d71e813905..ca503e6d0e 100644 --- a/apps/cli/config/agent-presets/cordis/agent.cordis.yml +++ b/apps/cli/config/agent-presets/cordis/agent.cordis.yml @@ -22,7 +22,7 @@ You can read and modify the harness you run on. Its composition is Cordis: every capability is a plugin row in a `cordis.yml`, and an agent preset is one such file mounted for a single session. - Two planes decide where an edit belongs. The HOST composition holds the registries and anything shared across sessions — persistence, the sandbox and approval stack, the model route. An AGENT PRESET holds what one session contributes to those registries: its tools, its persona, its delegation backends. A row that publishes a service belongs in the host composition, or inside an `isolate` realm if the preset genuinely owns that service. + Two planes decide where an edit belongs. The HOST composition holds the registries and anything shared across sessions — persistence, the sandbox and approval stack, the model route, the subagent registry and its backends. An AGENT PRESET holds what one session contributes to those registries: its tools, its persona, its prompt sections. A row that publishes a service belongs in the host composition, or inside an `isolate` realm if the preset genuinely owns that service and nothing outside one agent reads it. Load the `editing-cordis-compositions` skill before writing or changing a composition. @@ -33,22 +33,15 @@ # ── shell ─────────────────────────────────────────────────────────────────── -# `tool-bash` reads as a tool but provides the `bashEnv` service, so it needs a -# realm like any other provider. The executor behind it (`bash-sandbox`) stays -# in the host composition, where the sandbox policy owns it. -- id: shell - name: cordis:group - group: true - isolate: - bashEnv: true - config: - # The registry and its consumer share the realm: a consumer left outside - # would resolve the host's `bashEnv`, which this plane no longer provides. - - id: bash-env - name: '@deepseek-ai/dsh-bash-env' - - - id: tool-bash - name: '@deepseek-ai/dsh-tool-bash' +# `bash-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to +# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is +# the criterion for host-plane ownership — injection resolves before any session +# exists, so there is no agent to key by. Behind a preset realm those variables +# never reached the model's shell at all. `tool-bash` consumes the host registry +# from here; the executor behind it (`bash-sandbox`) is host-plane too, where the +# sandbox policy owns it. +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' # ── filesystem ────────────────────────────────────────────────────────────── @@ -157,29 +150,27 @@ # ── delegation and workflows ──────────────────────────────────────────────── -# Every backend and every tool that reaches `subagents` or `workflows` shares -# one realm: a consumer left outside it would resolve the host's registry -# instead, which this preset does not populate. +# The `subagents` registry and its spawn/fork backends live in the HOST +# composition: the registry is a process singleton whose cross-session queries +# the api-proxy serves to the browser, and a provider name may only be +# registered once. This preset contributes the delegation TOOLS, which resolve +# that host registry. +# +# `workflows` is different — nothing outside an agent reads it — so every row +# that reaches it shares one entry-local realm here, and a consumer left +# outside would resolve a host registry this preset does not populate. +# +# `tool-subagent-report` is host-plane for the same reason as the registry, +# not because a preset may not want it: it registers a CONTINUABLE SETUP on +# that singleton rather than a tool this agent calls, and the setup list is +# not scope-aware — one copy per mounted preset means every child gets +# `report` registered once per live session, which throws on the second. - id: delegation name: cordis:group group: true isolate: - subagents: true workflows: true config: - - id: subagent - name: '@deepseek-ai/dsh-subagent' - - - id: subagent-spawn - name: '@deepseek-ai/dsh-subagent-spawn' - config: - providerName: spawn - - - id: subagent-fork - name: '@deepseek-ai/dsh-subagent-fork' - config: - providerName: fork - - id: tool-subagent-control name: '@deepseek-ai/dsh-tool-subagent-control' @@ -200,9 +191,6 @@ toolName: subagent_fork backgroundMode: continuable - - id: tool-subagent-report - name: '@deepseek-ai/dsh-tool-subagent-report' - - id: workflow-workerthread name: '@deepseek-ai/dsh-workflow-workerthread' config: @@ -219,6 +207,9 @@ # ── remaining model-facing rows ───────────────────────────────────────────── +- id: tool-ask-user + name: '@deepseek-ai/dsh-tool-ask-user' + - id: tool-todo name: '@deepseek-ai/dsh-tool-todo' config: diff --git a/apps/cli/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md b/apps/cli/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md index dc6faea6b1..c7cc2f5f28 100644 --- a/apps/cli/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md +++ b/apps/cli/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md @@ -11,9 +11,11 @@ Every capability in this harness is a plugin row in a `cordis.yml`. There is no Two planes, and the choice is not about how "agent-related" something feels — it is about whether the thing must be shared. -**Host composition.** The registries themselves (`tools`, `systemPrompt`, `agents`, `agent-loop`, `sessions`), anything crossing sessions (persistence, session query, storage, settings, credentials, telemetry), the sandbox and approval stack, and the model route. One instance for the process. +**Host composition.** The registries themselves (`tools`, `systemPrompt`, `agents`, `agent-loop`, `sessions`), anything crossing sessions (persistence, session query, storage, settings, credentials, telemetry), the sandbox and approval stack, the model route, and the subagent registry with its spawn/fork backends. One instance for the process. -**Agent preset.** What one session contributes to those registries: its tool plugins, its persona, its delegation backends, its compaction policy. One instance per session, mounted under that session's scope and unwound with it. +**Agent preset.** What one session contributes to those registries: its tool plugins, its persona and prompt sections, its compaction policy. One instance per session, mounted under that session's scope and unwound with it. + +**A service with a consumer outside the agent plane cannot move into a preset.** `subagents` is the worked example: the registry answers cross-session queries for the host api-proxy, so a per-session copy both starves that host row — it waits forever for a service nothing provides — and collides on the second session, since a provider name registers once. The preset contributes the delegation *tools*; the registry and its backends stay host-side. A preset is a directory holding one `agent.cordis.yml`. The shipped ones live beside the deployment's composition; locally authored ones live under `$DSH_HOME/.agent-presets//`. diff --git a/apps/cli/tests/web-agent-presets.e2e.ts b/apps/cli/tests/web-agent-presets.e2e.ts index d80613e3b1..330c446a8f 100644 --- a/apps/cli/tests/web-agent-presets.e2e.ts +++ b/apps/cli/tests/web-agent-presets.e2e.ts @@ -43,13 +43,16 @@ async function bootWeb(settingsFile: string): Promise { { id: 'telemetry-otel', disabled: true }, { id: 'modules', disabled: true }, { id: 'connection', disabled: true }, - // NOT a side-effect row: the api-proxy cannot mount in THIS layer at all, - // because it injects `subagents` and the subagent registry moved into the - // presets here. That is the breakage a later layer returns to the host - // plane; when it does, this line comes out and the boot audit covers the - // whole host-plane injection graph again. - { id: 'api-gateway', disabled: true }, + // `api-gateway` stays ENABLED on purpose — the api-proxy is the host row + // that injects `subagents`, `workspace`, and the rest of the agent plane, + // so disabling it would hide exactly the breakage this file exists to + // catch: a service moved into the presets that a host row still waits for. + // The boot audit is that assertion. + // The shipped `-auto` chooser resolves its interaction from a running + // host and so waits for the webserver disabled above; the browse variant + // supplies `directoryPicker` without one. { id: 'directory-picker', disabled: true }, + { insert: [{ id: 'directory-picker-browse', name: '@deepseek-ai/dsh-host-directory-picker-browse' }] }, // The roster AppCLIEntry would patch in; only the shipped root, so a // developer's own `~/.dsh/.preset` cannot change this test's outcome. // `default` here is the COMPOSITION default — the base layer the settings From ce468c87f2119c0dd9df0a8a5546ba26ec50a59c Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Fri, 7 Aug 2026 14:47:08 +0800 Subject: [PATCH 2/2] chore(docs): re-record the agent-preset note pairing after the merge --- .../2026-08-03-per-session-agent-presets.i18n.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml index 9280260d00..0c2066885c 100644 --- a/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md -2026-08-03-per-session-agent-presets.md: 63bbc361b7968c3447cffaf8134422b7b78af98f -2026-08-03-per-session-agent-presets.zh.md: bee5ba37dd27db22c18c9d1dd6d58965ee66b80e +2026-08-03-per-session-agent-presets.md: e6d2f8eb999cbf44de4ddf461a372c7020886ab2 +2026-08-03-per-session-agent-presets.zh.md: 8062db60f3664a20f6179b5aa8c084deaaecaf37