Merge branch 'stack/agent-profiles-6-cordis-agent' into stack/agent-profiles-7-docs

# Conflicts:
#	.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.i18n.yaml
This commit is contained in:
Yichen Jiang
2026-08-07 14:46:46 +08:00
6 changed files with 45 additions and 49 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # 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: # 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 # 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: cc40af246d018560b7bc7bea92286e0ddaf53e55 2026-08-03-per-session-agent-presets.md: 63bbc361b7968c3447cffaf8134422b7b78af98f
2026-08-03-per-session-agent-presets.zh.md: 5b6113ee9f905478e59db3e982331b8faa190e3c 2026-08-03-per-session-agent-presets.zh.md: bee5ba37dd27db22c18c9d1dd6d58965ee66b80e

View File

@@ -19,7 +19,7 @@ Composition splits into two planes, decided by what must be shared rather than b
| Plane | Instances | Contents | | 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 | | 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. 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.

View File

@@ -19,7 +19,7 @@ Status: implemented
| 平面 | 实例数 | 内容 | | 平面 | 实例数 | 内容 |
|---|---|---| |---|---|---|
| 宿主 | 一份 | 注册表本身(`tools`、`systemPrompt`、`agents`、`agent-loop`、`sessions`)、跨会话设施(持久化、查询、投影、存储、设置、凭据、遥测),以及 web 宿主 | | 宿主 | 一份 | 注册表本身(`tools`、`systemPrompt`、`agents`、`agent-loop`、`sessions`)、跨会话设施(持久化、查询、投影、存储、设置、凭据、遥测),以及 web 宿主 |
| agent | 每会话一份 | 单个 agent 对这些注册表的贡献:工具插件、人设与提示词段落、委派后端、压缩策略 | | agent | 每会话一份 | 单个 agent 对这些注册表的贡献:工具插件、人设与提示词段落、压缩策略 |
模型路由不进 preset。`installAgentLlmTarget` 已经是 provider、model 与 reasoning effort 的按 agent 可替换点;而挂在 preset 内部的 LLM 适配器永远不会被 `agent-loop` 解析到,因为后者位于宿主平面。 模型路由不进 preset。`installAgentLlmTarget` 已经是 provider、model 与 reasoning effort 的按 agent 可替换点;而挂在 preset 内部的 LLM 适配器永远不会被 `agent-loop` 解析到,因为后者位于宿主平面。

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. 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. Load the `editing-cordis-compositions` skill before writing or changing a composition.
@@ -33,22 +33,15 @@
# ── shell ─────────────────────────────────────────────────────────────────── # ── shell ───────────────────────────────────────────────────────────────────
# `tool-bash` reads as a tool but provides the `bashEnv` service, so it needs a # `bash-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
# realm like any other provider. The executor behind it (`bash-sandbox`) stays # publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
# in the host composition, where the sandbox policy owns it. # the criterion for host-plane ownership — injection resolves before any session
- id: shell # exists, so there is no agent to key by. Behind a preset realm those variables
name: cordis:group # never reached the model's shell at all. `tool-bash` consumes the host registry
group: true # from here; the executor behind it (`bash-sandbox`) is host-plane too, where the
isolate: # sandbox policy owns it.
bashEnv: true - id: tool-bash
config: name: '@deepseek-ai/dsh-tool-bash'
# 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'
# ── filesystem ────────────────────────────────────────────────────────────── # ── filesystem ──────────────────────────────────────────────────────────────
@@ -157,29 +150,27 @@
# ── delegation and workflows ──────────────────────────────────────────────── # ── delegation and workflows ────────────────────────────────────────────────
# Every backend and every tool that reaches `subagents` or `workflows` shares # The `subagents` registry and its spawn/fork backends live in the HOST
# one realm: a consumer left outside it would resolve the host's registry # composition: the registry is a process singleton whose cross-session queries
# instead, which this preset does not populate. # 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 - id: delegation
name: cordis:group name: cordis:group
group: true group: true
isolate: isolate:
subagents: true
workflows: true workflows: true
config: 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 - id: tool-subagent-control
name: '@deepseek-ai/dsh-tool-subagent-control' name: '@deepseek-ai/dsh-tool-subagent-control'
@@ -200,9 +191,6 @@
toolName: subagent_fork toolName: subagent_fork
backgroundMode: continuable backgroundMode: continuable
- id: tool-subagent-report
name: '@deepseek-ai/dsh-tool-subagent-report'
- id: workflow-workerthread - id: workflow-workerthread
name: '@deepseek-ai/dsh-workflow-workerthread' name: '@deepseek-ai/dsh-workflow-workerthread'
config: config:
@@ -219,6 +207,9 @@
# ── remaining model-facing rows ───────────────────────────────────────────── # ── remaining model-facing rows ─────────────────────────────────────────────
- id: tool-ask-user
name: '@deepseek-ai/dsh-tool-ask-user'
- id: tool-todo - id: tool-todo
name: '@deepseek-ai/dsh-tool-todo' name: '@deepseek-ai/dsh-tool-todo'
config: config:

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

@@ -43,13 +43,16 @@ async function bootWeb(settingsFile: string): Promise<Context> {
{ id: 'telemetry-otel', disabled: true }, { id: 'telemetry-otel', disabled: true },
{ id: 'modules', disabled: true }, { id: 'modules', disabled: true },
{ id: 'connection', disabled: true }, { id: 'connection', disabled: true },
// NOT a side-effect row: the api-proxy cannot mount in THIS layer at all, // `api-gateway` stays ENABLED on purpose — the api-proxy is the host row
// because it injects `subagents` and the subagent registry moved into the // that injects `subagents`, `workspace`, and the rest of the agent plane,
// presets here. That is the breakage a later layer returns to the host // so disabling it would hide exactly the breakage this file exists to
// plane; when it does, this line comes out and the boot audit covers the // catch: a service moved into the presets that a host row still waits for.
// whole host-plane injection graph again. // The boot audit is that assertion.
{ id: 'api-gateway', disabled: true }, // 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 }, { 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 // The roster AppCLIEntry would patch in; only the shipped root, so a
// developer's own `~/.dsh/.preset` cannot change this test's outcome. // developer's own `~/.dsh/.preset` cannot change this test's outcome.
// `default` here is the COMPOSITION default — the base layer the settings // `default` here is the COMPOSITION default — the base layer the settings