fix(web): keep the subagent registry in the host plane

`dsh web` failed to boot: the aggressive plane split moved `subagents` and
its spawn/fork backends into the preset's entry-local realm, but
`dsh-host-apiproxy` is a host row that injects `subagents` to answer the
browser's cross-session queries, so it waited forever for a service only
sessions now provided. A per-session copy is wrong twice over — a provider
name registers once, so the second session would have collided anyway.

The registry and its backends go back to the host composition; the presets
keep the delegation TOOLS, which resolve that host registry. `workflows`
stays entry-local: nothing outside an agent reads it.

The web real-composition test could not have caught this, because it
disabled `api-gateway` — the very row whose pending injection names the
break. It now boots with the api-proxy enabled and the browse directory
picker substituted, so the boot audit covers the whole host-plane injection
graph. Re-introducing the old split makes it fail with the three pending
entries, which is how this was verified.
This commit is contained in:
Yichen Jiang
2026-08-04 12:46:57 +08:00
parent 3a60d97d21
commit 6595301afc
7 changed files with 51 additions and 56 deletions

View File

@@ -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.
@@ -157,29 +157,21 @@
# ── 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.
- 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'

View File

@@ -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/<name>/`.

View File

@@ -172,29 +172,21 @@
# ── 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.
- 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'