mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(preset): cover both root-realm rejections and narrow the packaging claim
Review found two errors in the first pass. The skill quoted `service "<name>" has been registered at <Owner>` as the rejection for a service published into the root realm. That message only appears when the loose name collides with one the host already supplies. A preset publishing its OWN new service name reaches the root realm successfully and is rejected by the mount audit instead, with `row(s) published process-global service(s) [...]` — which is the shape a forgotten realm actually takes. Both messages are now listed, verified by mounting one preset of each kind. The Agent Note claimed every harness package publishes exactly three file patterns. 139 do; 28 also publish `lib/client.js`, 22 publish `lib/types/**/*.js`, and `dsh-message-feedback` publishes `src`. The note now states only the fact it needs: no package's `files` includes its README. Also from review: the copy step no longer repeats the display name it already set, the id rule spells out `[a-z0-9][a-z0-9-]*`, and the sandbox step says reads need no escalation.
This commit is contained in:
@@ -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/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.md
|
||||
2026-08-11-preset-authoring-agent-validates-its-own-composition.md: 77bd30d4c8f6599ccde50b4d814f55d065266763
|
||||
2026-08-11-preset-authoring-agent-validates-its-own-composition.zh.md: cdc0d30bcd769e1a17ddcce364002f0136a26242
|
||||
2026-08-11-preset-authoring-agent-validates-its-own-composition.md: b7d20ef20c611a0ea0c694bac6bd3948bd194b2b
|
||||
2026-08-11-preset-authoring-agent-validates-its-own-composition.zh.md: 512ac5ea0f47f4a796170b070c140f14b0072879
|
||||
|
||||
@@ -14,7 +14,7 @@ Its `isolate` example composed `tasks-local` with `tool-tasks`. `tasks-local` is
|
||||
|
||||
It described a string realm label as pooling one instance across subtrees. Labels join realms; `provide()` still throws on the second registration under the same realm symbol, which `standard`'s header comment already stated.
|
||||
|
||||
It sent the agent to a package's README to learn whether a row publishes a service. Outside `apps/cli` (`files: ["lib/*.js", "config"]`), every harness package publishes only `lib/index.js`, `lib/invariant.js`, and `lib/types/**/*.d.ts` — no README, no `src/`, no `docs/`. In an installed deployment that instruction cannot be followed at all.
|
||||
It sent the agent to a package's README to learn whether a row publishes a service. Every harness package declares `files`, and no declaration includes its README, so an installed deployment carries none. There that instruction cannot be followed at all.
|
||||
|
||||
Underneath all four sat a capability claim: the agent "cannot start one \[a session\] yourself", so verification was hand-reading YAML fields and handing the result to the user through the settings page's red marking. That marking is discovery's shape check, which is far weaker than the sentence implied.
|
||||
|
||||
@@ -41,7 +41,8 @@ Each row was produced by booting the shipped Web composition and calling the too
|
||||
| Composition under test | `list()` `broken` | `standingKeyFor()` |
|
||||
|---|---|---|
|
||||
| row names an absent package | empty | `Cannot find package '@deepseek-ai/dsh-does-not-exist'` |
|
||||
| service row with no realm | empty | `service "tasks" has been registered at <LocalTaskService>` |
|
||||
| service row with no realm, name the host supplies | empty | `service "tasks" has been registered at <LocalTaskService>` |
|
||||
| service row with no realm, name the host does not supply | empty | `row(s) published process-global service(s) [workflows]; …` |
|
||||
| same row inside `isolate` | empty | mounts |
|
||||
| consumer row with no provider | empty | `1 row(s) did not activate: … waiting for workflows` |
|
||||
| row missing a required config field | empty | `invalid config: $.allowParallelInProgress missing required value` |
|
||||
|
||||
@@ -14,7 +14,7 @@ Status: implemented
|
||||
|
||||
它把字符串 realm label 描述为跨子树共享一个实例。label 只是加入同一 realm,`provide()` 在同一 realm symbol 下第二次注册仍然抛错——`standard` 的头部注释早已如此说明。
|
||||
|
||||
它让 agent 去读包的 README 判断某行是否发布服务。除 `apps/cli`(`files: ["lib/*.js", "config"]`)外,所有 harness 包只发布 `lib/index.js`、`lib/invariant.js` 与 `lib/types/**/*.d.ts`,没有 README、没有 `src/`、没有 `docs/`。在装机部署中该指令根本无法执行。
|
||||
它让 agent 去读包的 README 判断某行是否发布服务。每个 harness 包都声明了 `files`,且没有任何一份声明包含自己的 README,因此装机部署中一份也没有。在那里该指令根本无法执行。
|
||||
|
||||
四条之下还压着一个能力断言:agent「自己起不了会话」,于是校验退化成肉眼核对 YAML 字段,再把结果经设置页的红色标记交给用户。那个标记是发现阶段的结构检查,远弱于这句话给人的印象。
|
||||
|
||||
@@ -41,7 +41,8 @@ agent 按 `cordis_mount` 自身文档所述的方式够到 roster 服务:挂
|
||||
| 被测组装 | `list()` 的 `broken` | `standingKeyFor()` |
|
||||
|---|---|---|
|
||||
| 行指向不存在的包 | 空 | `Cannot find package '@deepseek-ai/dsh-does-not-exist'` |
|
||||
| 服务行未套 realm | 空 | `service "tasks" has been registered at <LocalTaskService>` |
|
||||
| 服务行未套 realm,名字宿主已提供 | 空 | `service "tasks" has been registered at <LocalTaskService>` |
|
||||
| 服务行未套 realm,名字宿主未提供 | 空 | `row(s) published process-global service(s) [workflows]; …` |
|
||||
| 同一行置于 `isolate` 内 | 空 | 挂载成功 |
|
||||
| 消费者行无人提供服务 | 空 | `1 row(s) did not activate: … waiting for workflows` |
|
||||
| 行缺少必填配置字段 | 空 | `invalid config: $.allowParallelInProgress missing required value` |
|
||||
|
||||
@@ -63,9 +63,9 @@ Unmount the plugin with `cordis_unmount` when you are done; it is a probe, not a
|
||||
|
||||
## Authoring a preset
|
||||
|
||||
1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id (lowercase letters, digits, and hyphens, because it becomes the directory name), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, and the copy is exactly as loadable as its source. `standard` is the full coding agent and the usual source.
|
||||
2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do.
|
||||
3. **Give the copy its own `name` and `description`** in `preset.yml`.
|
||||
1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id against `[a-z0-9][a-z0-9-]*` (it becomes the directory name, so no leading hyphen), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, and the copy is exactly as loadable as its source. `standard` is the full coding agent and the usual source.
|
||||
2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Only writes are: reading any composition by absolute path needs no escalation. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do.
|
||||
3. **Write the copy's `description`** in `preset.yml`, and its `name` if you passed none to `copy()`.
|
||||
4. **Edit `agent.cordis.yml`** row by row, keeping the plane rule and the realm rule.
|
||||
5. **Mount-validate the result**, then hand off to the user for a real session — both under *Verifying a change*.
|
||||
|
||||
@@ -106,8 +106,8 @@ Realms are for services a preset owns, not for every group. A host capability th
|
||||
|
||||
- a row whose package does not resolve (`Cannot find package …`);
|
||||
- a row whose config is invalid (`invalid config: $.<field> missing required value`);
|
||||
- a service published into the root realm (`service "<name>" has been registered at <Owner>`);
|
||||
- a row that never activated (`N row(s) did not activate: <id>: waiting for <service>`).
|
||||
- a row that never activated (`N row(s) did not activate: <id>: waiting for <service>`);
|
||||
- a service published into the root realm, which arrives as one of two messages. A name the host does not supply lands in the root realm and the mount audit rejects it: `row(s) published process-global service(s) [<name>]; a preset service must sit behind an isolate realm or move to the host composition` — this is the shape a preset's own forgotten realm takes. A name the host already supplies collides before the audit: `service "<name>" has been registered at <Owner>`. Both name the offending service.
|
||||
|
||||
It returns normally when the composition mounts. Run it as the final check on a finished edit rather than after every line: a successful mount installs a standing generation that lives until the process exits, while a failed one disposes its subtree and leaves nothing behind.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user