From b5cd511f35c52a8c1c4c2530b446b1a6b0574cab Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:55:26 +0800 Subject: [PATCH] docs: address model experience review --- docs/AGENTS.md | 4 +-- docs/cookbook/adding-a-package.md | 24 +++++++++++--- ...07-12-package-model-experience-contract.md | 4 +-- packages/bash/bash-local/README.md | 12 +++---- packages/bash/bash/README.md | 12 +++---- packages/bash/tool-bash/README.md | 16 +++++----- .../code-runtime-worker/README.md | 12 +++---- packages/code-runtime/code-runtime/README.md | 12 +++---- packages/compact/compact-basic/README.md | 14 ++++---- packages/compact/compact/README.md | 12 +++---- packages/cordis/tool-cordis/README.md | 16 +++++----- packages/core/agent-core/README.md | 14 ++++---- packages/core/agent-loop/README.md | 14 ++++---- packages/core/agent/README.md | 14 ++++---- packages/core/scope/README.md | 12 +++---- packages/core/session/README.md | 14 ++++---- packages/core/system-prompt/README.md | 14 ++++---- packages/core/tools/README.md | 14 ++++---- packages/fs/fs-local/README.md | 12 +++---- packages/fs/fs-policy/README.md | 12 +++---- packages/fs/fs/README.md | 12 +++---- packages/fs/tool-fs/README.md | 16 +++++----- packages/guard/repeat-tool-guard/README.md | 12 +++---- packages/hooks/hook-protocol/README.md | 12 +++---- packages/hooks/hooks-claude/README.md | 14 ++++---- packages/hooks/hooks-codex/README.md | 14 ++++---- packages/llm/llm-deepseek/README.md | 14 ++++---- packages/llm/llm-pi-ai/README.md | 14 ++++---- packages/llm/llm/README.md | 14 ++++---- .../session-persistence-jsonl/README.md | 12 +++---- .../session-persistence-sqlite/README.md | 12 +++---- .../session-persistence/README.md | 12 +++---- packages/skill/skill-local/README.md | 12 +++---- packages/skill/skill/README.md | 12 +++---- packages/skill/tool-skill/README.md | 14 ++++---- packages/subagent/subagent-acp/README.md | 14 ++++---- packages/subagent/subagent-fork/README.md | 14 ++++---- .../subagent/subagent-inprocess/README.md | 14 ++++---- packages/subagent/subagent-spawn/README.md | 14 ++++---- .../subagent/subagent-subprocess/README.md | 12 +++---- packages/subagent/subagent/README.md | 12 +++---- packages/subagent/tool-subagent/README.md | 14 ++++---- packages/support/invariants/README.md | 12 +++---- packages/support/llm-replay/README.md | 12 +++---- packages/support/subagent-mock/README.md | 12 +++---- packages/timeout/timeout-policy/README.md | 12 +++---- packages/todo/tool-todo/README.md | 14 ++++---- packages/ui/acp-agent/README.md | 12 +++---- packages/ui/acp/README.md | 32 +++++++++---------- packages/ui/stdio-agent/README.md | 12 +++---- packages/ui/tool-ask-user/README.md | 14 ++++---- packages/ui/user-interaction/README.md | 12 +++---- packages/util/brand/README.md | 12 +++---- packages/util/timeout/README.md | 12 +++---- packages/web/tool-web/README.md | 16 +++++----- packages/web/web-fetch-local/README.md | 12 +++---- packages/web/web-search-deepseek/README.md | 14 ++++---- packages/web/web-search-exa/README.md | 12 +++---- packages/web/web-search-perplexity/README.md | 14 ++++---- packages/web/web/README.md | 12 +++---- packages/workflow/tool-workflow/README.md | 14 ++++---- .../workflow/workflow-workerthread/README.md | 14 ++++---- packages/workflow/workflow/README.md | 12 +++---- .../verify-package-readme-model-experience.ts | 17 ++++++++++ 64 files changed, 443 insertions(+), 410 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 239af47e48..ad592333d5 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -35,12 +35,12 @@ Placement test: a story about a bug → postmortem. Why we chose X → RFC. How ## Package Model Experience -Every package README carries `## Model Experience` and this table: +Every package README ends with this table followed by `## Known Limitations and Deferred Work`; [allowlisted packages](../scripts/verify-readme-limitations.ts) end after the table: | Context surface | What the model sees | Token effect | |---|---|---| -Rows name the request surface and condition, including agent scope, then classify tokens as fixed per request, conditional per call, retained, replaced, capped, or zero-direct. Distinguish the conversation model from auxiliary calls. A zero-direct package names its indirect path. `verify-package-readme-model-experience` enforces shape; review owns accuracy. +Rows name request surface, condition, and agent scope, then classify tokens as fixed per request, conditional per call, retained, replaced, capped, or zero-direct. Separate conversation and auxiliary calls; zero-direct rows name the indirect path. `verify-package-readme-model-experience` enforces shape and order; review owns accuracy. ## Wordcount Budgets diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 9fd4ed005c..0ccee2b217 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -19,8 +19,6 @@ packages/// # (or a whitelist entry in scripts/verify-readme-limitations.ts) ``` -Fill the canonical [Model Experience table](../AGENTS.md#package-model-experience) from the implementation: name every direct request contribution and token-growth condition, or state zero direct tokens and the exact indirect path. Every package participates, including type-only libraries and backend seams. - Choose an existing group when one matches the package's role (`core`, `llm`, `bash`, `compact`, `subagent`, `todo`, `session-persistence`, `ui`, `util`, or `support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it. package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list is precise: `lib/index.js`, `lib/types/**/*.d.ts`, `lib/types/**/*.d.ts.map`, and `src`; do not publish `lib/types` JS or JS-map intermediates or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`. @@ -42,11 +40,29 @@ Covered automatically by globs or package-manifest discovery — no edits needed For a swappable capability, split interface / implementation / consumer into separate packages (see docs/architecture.md § "Capability seams" — the bash trio is the template). A single-purpose plugin stays one package. -## 4. Verify +## 4. Write the package README + +Keep package-specific service API, config, events, extension points, and design notes first. End a package README with this canonical sequence: + +```markdown +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Request surface and condition | Exact context visible to the model | Fixed, conditional, retained, replaced, capped, or zero-direct token effect | + +## Known Limitations and Deferred Work + +- **Consumer-visible gap** — exact boundary or deliberately deferred work. +``` + +Fill [Model Experience](../AGENTS.md#package-model-experience) from the implementation: name every direct request contribution and token-growth condition, or state zero direct tokens and its indirect path. Every package participates, including type-only libraries and backend seams. A package with genuinely no limitations joins the justified allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. + +## 5. Verify ```sh pnpm install # registers the workspace -pnpm run verify-package-readme-model-experience +pnpm run doc-sync pnpm run constraints && pnpm run typecheck && pnpm run lint pnpm run test:coverage # 100% per-file over src (types.ts exempt) pnpm run build && pnpm run hygiene diff --git a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md index 755bb5a80b..a083e69cb8 100644 --- a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md +++ b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md @@ -8,11 +8,11 @@ A package README can explain APIs and runtime mechanics without answering the qu ## Decision -Every workspace package README carries the canonical [Model Experience table](../../../AGENTS.md#package-model-experience). Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. +Every workspace package README ends with the canonical [Model Experience table](../../../AGENTS.md#package-model-experience), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Every package participates. A service seam, storage backend, test helper, or type-only library that contributes no prompt text, tool schema, message, or auxiliary request records zero direct tokens and names the consumer or control path through which it can still change model-visible material. This explicit negative contract prevents readers from having to infer whether the section was forgotten. -`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README, one exact `## Model Experience` heading, the canonical three-column table header, and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns shape and completeness; implementation review owns the truth of the prose. +`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README, the canonical final-section order, one exact `## Model Experience` heading and three-column table header, and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns shape and completeness; implementation review owns the truth of the prose. ## Alternatives considered diff --git a/packages/bash/bash-local/README.md b/packages/bash/bash-local/README.md index 1bc3f2baab..42a2a0618d 100644 --- a/packages/bash/bash-local/README.md +++ b/packages/bash/bash-local/README.md @@ -2,12 +2,6 @@ Local-subprocess implementation of the `@deepseek-ai/dsh-bash` executor seam: `LocalBashExecutor` spawns `bash -c ` per call in its own process group, collects bounded output with full-stream spill files, and escalates kills SIGTERM→SIGKILL across the whole group. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the retained stdout and stderr tail, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. | - ## Config ```yaml @@ -31,6 +25,12 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi; - **Model-friendly env + credential scrub** — `process.env` minus credential-shaped vars (`*KEY*`/`*SECRET*`/`*TOKEN*`), then `NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` (Codex's hardcoded set) so pagers and ANSI color don't garble results. This scrub is the security control that keeps the harness's *ambient* credentials out of a spawned command. A spec's `env` is merged LAST (after the scrub), so a caller's explicit entry — a value it already holds — wins even on a credential-shaped name. The spec's `stdin`, when supplied, is written to the child and closed; with none supplied, fd 0 is `/dev/null` — the exact pre-seam default, so a command that probes stdin's file type is unaffected. Both `env`/`stdin` are set by in-process plugins (the hooks bridges); the model-facing tool doesn't expose them. See [the bash-stdin-env RFC](../../../docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md). - **Background tasks** — `start()` returns immediately, no timeout applies (Claude Code detaches timeouts when backgrounding), `readOutput()` is incremental with whole-stream byte offsets, and disposal kills everything. The spec's opaque `owner` token is stored on the tracked task and returned by `ownerOf(id)` — the executor never interprets it (the consumer's access policy does), and because it lives with the task here it survives a `tool-bash` HMR reload. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the retained stdout and stderr tail, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. | + ## Known Limitations and Deferred Work - **Unconfined by itself** — this executor always runs commands with the harness process's authority; deployments needing confinement compose [`dsh-bash-sandbox`](../bash-sandbox/README.md), while per-call allow/deny/ask policy belongs on `tools/pre-execute`. diff --git a/packages/bash/bash/README.md b/packages/bash/bash/README.md index 3ce0845e73..36f6686522 100644 --- a/packages/bash/bash/README.md +++ b/packages/bash/bash/README.md @@ -13,12 +13,6 @@ This package is the interface quarter of the bash capability, split so each conc The split mirrors the LLM seam (`LlmService`/`LlmAdapter`) and the agent-tool survey: pi hides execution behind a `BashOperations` interface (local shell / SSH / VM backends), Codex behind an exec-server protocol. `dsh-bash-sandbox` is exactly that swap in action — a sandboxing executor behind the same interface, tool schemas untouched; a containerized or remote executor slots in the same way. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This interface registers no prompt, tool schema, or message. `dsh-tool-bash` turns an implementation's stdout, stderr, task state, and sandbox facts into model-visible tool results and guidance. | Zero direct tokens. Result size and sandbox state affect input tokens only when a consumer renders them. | - ## Service API (`ctx.bash`) | Member | Semantics | @@ -42,6 +36,12 @@ The seam also owns the per-session mode override vocabulary (the sandbox RFC § `stdin` and `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload on stdin and its `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` env. The model-facing `dsh-tool-bash` tool does not expose them as parameters — a model already has equivalent power through shell syntax (`FOO=bar cmd`, a heredoc), so they would be redundant tool params. This is not a security boundary: the implementation's credential scrub (not these fields) is what keeps the harness's ambient secrets out of a spawned command. They are plain optionals on the resolved spec (unlike `owner`'s required-but-nullable): a missing one means "none", the safe default. See [the bash-stdin-env RFC](../../../docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This interface registers no prompt, tool schema, or message. `dsh-tool-bash` turns an implementation's stdout, stderr, task state, and sandbox facts into model-visible tool results and guidance. | Zero direct tokens. Result size and sandbox state affect input tokens only when a consumer renders them. | + ## Known Limitations and Deferred Work - **No interactive-input vocabulary** — `stdin` is written once at spawn and closed; the seam has no channel to feed a running task and no PTY session concept. diff --git a/packages/bash/tool-bash/README.md b/packages/bash/tool-bash/README.md index 66fc7968a6..da1ab41c14 100644 --- a/packages/bash/tool-bash/README.md +++ b/packages/bash/tool-bash/README.md @@ -6,14 +6,6 @@ Requires a loaded executor implementation (e.g. `@deepseek-ai/dsh-bash-local`); The plugin also contributes the `tool:bash` prompt section (order 105) — the cross-call habit the per-tool descriptions cannot carry: check the `[exit code: N]` marker on every result and investigate failures before moving on. Under a sandboxing executor it additionally contributes the per-agent `env:bash-sandbox` section (order 110) stating each session's EFFECTIVE mode, and the pre-step narrator — see [Per-session mode](#per-session-mode-switching-and-visibility). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt | Every request for an agent that can see these tools carries the short `tool:bash` exit-code instruction. With a sandboxing executor it also carries that session's effective sandbox mode, plus a logged context notice after a mode change. | Small fixed input cost per request; a mode-change notice is conditional and then remains in conversation history. | -| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. | -| Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. | - ## Tools ### `bash` @@ -66,6 +58,14 @@ On top of a denial sits the escalation gate ([the sandbox RFC § Escalation](../ Under a sandboxing executor this plugin makes the session's standing mode override ([the sandbox RFC § Per-session mode switching](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md); the `bash/sandbox-mode` fold owned by [`dsh-bash`](../bash/README.md)) real at EXECUTION: every call is stamped `escalation grant > session override > undefined` onto `BashExecRequest.sandboxMode`; without either, the executor's `resolve()` applies its configured default. Nothing is stamped under a non-sandboxing executor (nothing would honor it) or for an agent-less caller (no session to fold). The prompt deliberately does NOT state the mode and a switch is not narrated: a standing declaration teaches the model to refuse preemptively, while the denial marker already names the mode the command ran under exactly when the boundary is hit — behavior, not belief, carries the state. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Every request for an agent that can see these tools carries the short `tool:bash` exit-code instruction. With a sandboxing executor it also carries that session's effective sandbox mode, plus a logged context notice after a mode change. | Small fixed input cost per request; a mode-change notice is conditional and then remains in conversation history. | +| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. | +| Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. | + ## Known Limitations and Deferred Work - **Replay exit pills parse from result text** — output whose final line happens to be exactly `[exit code: N]` / `[killed by signal: …]` shows a wrong pill on session replay; a display-only known residual. diff --git a/packages/code-runtime/code-runtime-worker/README.md b/packages/code-runtime/code-runtime-worker/README.md index fe4b7d6c04..303b9abb90 100644 --- a/packages/code-runtime/code-runtime-worker/README.md +++ b/packages/code-runtime/code-runtime-worker/README.md @@ -2,12 +2,6 @@ Worker-thread implementation of the [`@deepseek-ai/dsh-code-runtime`](../code-runtime/README.md) seam: `WorkerCodeRuntime` runs each program in ONE fresh Node `worker_threads.Worker` — TypeScript in, type-stripped host-side, bindings bridged over the message port, `{ value, logs, error? }` out. **Containment, not a security boundary**: trust posture is bash-equivalent by design (the [Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md) § Trust posture), with containment bash does not have — separate isolate, empty environment, heap cap, hard termination. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| `run_code` result, indirectly | The conversation model sees only what the model-written program prints or returns, or a shaped failure; binding-call traffic and worker internals stay outside its context. This backend contributes no schema or prompt itself. | Zero tokens until Code Mode executes a program. `maxLogBytes` and `maxValueBytes` cap the model-visible result, which then remains in tool history until compaction. | - ## Config ```yaml @@ -37,6 +31,12 @@ Every field is validated (positive numbers) and defaulted; there are no other tu `worker.ts` is deliberately erasable-only TypeScript with type-only cross-package imports: unbuilt (vitest/tsx), the host spawns `src/worker.ts` directly and Node's native type stripping loads it; built, the entry ships as the sibling bundle `lib/worker.js` (its own tsdown entry). The built path is pinned by `tests/built-lib.e2e.ts`, the real-load-path guard from [docs/testing.md](../../../docs/testing.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| `run_code` result, indirectly | The conversation model sees only what the model-written program prints or returns, or a shaped failure; binding-call traffic and worker internals stay outside its context. This backend contributes no schema or prompt itself. | Zero tokens until Code Mode executes a program. `maxLogBytes` and `maxValueBytes` cap the model-visible result, which then remains in tool history until compaction. | + ## Known Limitations and Deferred Work - **OS processes a program spawns survive termination** — `worker.terminate()` ends the thread only, weaker than bash-local's process-group kill; orphan cleanup is a deployment concern until a container backend exists. diff --git a/packages/code-runtime/code-runtime/README.md b/packages/code-runtime/code-runtime/README.md index e6e9a17267..9c256a31dc 100644 --- a/packages/code-runtime/code-runtime/README.md +++ b/packages/code-runtime/code-runtime/README.md @@ -4,12 +4,6 @@ The **code-execution seam**: an abstract `CodeRuntime` service (`ctx.codeRuntime This package is the interface third of the capability (the bash trio is the template — see [capability seams](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)): implementations subclass `CodeRuntime` and register the service; the consumer is the tool registry's Code Mode, which generates the model-facing SDK and bridges tool dispatch — both specified in the [Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md), whose first implementation is a Node worker-thread backend. The runtime knows nothing about tools or sessions: it is handed named async functions and a program string, and everything tool-shaped stays with the consumer. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The seam receives a program and host bindings but registers no prompt, schema, or message. Code Mode in `dsh-tools` exposes the SDK and `run_code`, then converts `CodeRunResult` into the outer tool result. | Zero direct tokens. Program logs, values, and failures affect the conversation only through the Code Mode consumer. | - ## Service API (`ctx.codeRuntime`) | Member | Semantics | @@ -24,6 +18,12 @@ Semantics every implementation must honor (contract details in the class JSDoc): `CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on — defaulting (time budgets, output caps) is the implementation's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions`), each exposed to the program as one global object of async callables. `CodeRunResult` reports the completion `value?`, the ordered `logs` (`CodeLogEntry`: `console`/`stdout`/`stderr` source, console `level`, capped text), and the `error?` (`CodeRunFailure`: `kind` + model-feedable `message`). See `src/types.ts` for the full contracts. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The seam receives a program and host bindings but registers no prompt, schema, or message. Code Mode in `dsh-tools` exposes the SDK and `run_code`, then converts `CodeRunResult` into the outer tool result. | Zero direct tokens. Program logs, values, and failures affect the conversation only through the Code Mode consumer. | + ## Known Limitations and Deferred Work - **`run()` is one-shot** — `logs` arrive only on the resolved `CodeRunResult`; the seam exposes no streaming-log or progress surface for a live program's output. diff --git a/packages/compact/compact-basic/README.md b/packages/compact/compact-basic/README.md index d47db04d99..395b3cccd8 100644 --- a/packages/compact/compact-basic/README.md +++ b/packages/compact/compact-basic/README.md @@ -4,13 +4,6 @@ The **basic compaction backend**: a `BasicCompactService` implementing the `@dee This is the implementation tier of the compaction capability — see the [interface package](../compact/README.md) for the seam and the [capability-seam RFC](../../../docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md) for the design. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Conversation history | Before a step whose estimated system prompt, session prefix, and history exceed the threshold, the conversation model receives one framed summary checkpoint in place of the older balanced surface range, followed by the retained recent units. | The replacement reduces future input history rather than appending a second copy. The summary remains until a later compaction replaces it; one oversized indivisible unit can still exceed the budget. | -| Auxiliary summarizer request | The summarization model sees a fixed checkpoint-writing system instruction and a flattened transcript of the selected range. The conversation model never sees this private request or its reasoning; only returned text is stored. | This is a separate model call with data-dependent input and `maxTokens`-capped output. Convergence retries can pay this cost more than once. | - ## What it owns The abstract contract states only WHAT compaction does; this backend owns every HOW decision: @@ -64,6 +57,13 @@ export function apply(ctx: Context): void { Loading the plugin registers `ctx.compact`. With `auto: true` (the default) it compacts automatically under token pressure; a consumer (a future `/compact` tool) can also call `ctx.compact.compactIfNeeded(...)` or `ctx.compact.compactRegion(...)` directly. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conversation history | Before a step whose estimated system prompt, session prefix, and history exceed the threshold, the conversation model receives one framed summary checkpoint in place of the older balanced surface range, followed by the retained recent units. | The replacement reduces future input history rather than appending a second copy. The summary remains until a later compaction replaces it; one oversized indivisible unit can still exceed the budget. | +| Auxiliary summarizer request | The summarization model sees a fixed checkpoint-writing system instruction and a flattened transcript of the selected range. The conversation model never sees this private request or its reasoning; only returned text is stored. | This is a separate model call with data-dependent input and `maxTokens`-capped output. Convergence retries can pay this cost more than once. | + ## Known Limitations and Deferred Work - **Token estimation is the chars/`charsPerToken` heuristic** — a marked TODO schedules replacing it with an exact count (a real tokenizer, or provider `usage` fed back) so thresholds track the model's actual budget. diff --git a/packages/compact/compact/README.md b/packages/compact/compact/README.md index 098b8dc1fd..73d7c22375 100644 --- a/packages/compact/compact/README.md +++ b/packages/compact/compact/README.md @@ -12,12 +12,6 @@ This package is the interface tier of the compaction capability, split so each c Unlike the bash seam, this interface depends on `@deepseek-ai/dsh-session` and `@deepseek-ai/dsh-llm` — the contract's verbs are defined over a `Session` and its output is the `ContentBlock` vocabulary, so they cannot be expressed without naming those packages. That deviation from the "interface depends only on cordis" guidance is intentional and recorded in the [compaction capability-seam RFC](../../../docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Conversation history, when a backend is invoked | A successful implementation replaces an older surface range with one user-role summary checkpoint; the raw events stay logged but stop appearing in derived model messages. The seam itself performs no rewrite. | Zero direct tokens from this interface. A backend trades many retained history tokens for one summary and leaves the recent tail unchanged. | - ## Service API (`ctx.compact`) Both methods are **abstract** — the backend owns the entire strategy (token estimation, retention policy, event sequencing, summarization). @@ -55,6 +49,12 @@ The `compact/*` events extend `SessionEventMap` (merge-extensible) via declarati Subclass `CompactService`, implement `compactIfNeeded` and `compactRegion`, and load the subclass as a plugin — it registers as `ctx.compact`. A tokenizer-, template-, or model-backed implementation can live as a sibling package without changing callers. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conversation history, when a backend is invoked | A successful implementation replaces an older surface range with one user-role summary checkpoint; the raw events stay logged but stop appearing in derived model messages. The seam itself performs no rewrite. | Zero direct tokens from this interface. A backend trades many retained history tokens for one summary and leaves the recent tail unchanged. | + ## Known Limitations and Deferred Work - **No model-facing consumer tier yet** — `@deepseek-ai/dsh-tool-compact` (the `/compact` tool) is deferred; compaction is reachable only via direct `ctx.compact` calls or a backend's auto listener. diff --git a/packages/cordis/tool-cordis/README.md b/packages/cordis/tool-cordis/README.md index 8dbb70db37..1fb20a1906 100644 --- a/packages/cordis/tool-cordis/README.md +++ b/packages/cordis/tool-cordis/README.md @@ -2,14 +2,6 @@ The self-referential cordis toolset: three model-facing tools over the live runtime the agent runs inside. Design home — sandbox semantics, mount lifecycle, cross-mount composition, the generated API catalog, standing decisions: [the toolset RFC](../../../docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schemas | The conversation model sees `cordis_inspect`, `cordis_mount`, and `cordis_unmount` whenever this plugin is visible. | Fixed schema cost on every request in that tool view. | -| Tool-call history and results | Inspect returns selected live services, plugins, tools, or generated API and event references; mount and unmount return lifecycle facts or structured errors. The submitted mount program remains in the assistant tool-call history. | Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small. | -| Later requests after a mount | A mounted plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; unmount removes those contributions after quiescence. | Indirect token impact equals the mounted plugin's contributions and lasts only for the mount lifetime. | - ## What it does - `cordis_inspect` — read-only report over the runtime: services, the loaded-plugin list, registered tools, the dynamic-mount table, and the catalog-backed `api` / `events` references. @@ -40,6 +32,14 @@ All three tools render `generic` cards (`read` / `execute` / `delete`); `cordis_ Namespace plugin: named exports `name` / `inject` / `Config` / `apply`, no default export ([docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schemas | The conversation model sees `cordis_inspect`, `cordis_mount`, and `cordis_unmount` whenever this plugin is visible. | Fixed schema cost on every request in that tool view. | +| Tool-call history and results | Inspect returns selected live services, plugins, tools, or generated API and event references; mount and unmount return lifecycle facts or structured errors. The submitted mount program remains in the assistant tool-call history. | Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small. | +| Later requests after a mount | A mounted plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; unmount removes those contributions after quiescence. | Indirect token impact equals the mounted plugin's contributions and lasts only for the mount lifetime. | + ## Known Limitations and Deferred Work - **The sandbox is containment for honest code, not a security boundary** — host-realm helpers on the sandbox global are reachable, so mount code can reach Node; load this plugin as deliberately as you would grant a bash tool (see § Trust stance). diff --git a/packages/core/agent-core/README.md b/packages/core/agent-core/README.md index bb75fdbfca..c7cadb616c 100644 --- a/packages/core/agent-core/README.md +++ b/packages/core/agent-core/README.md @@ -4,13 +4,6 @@ The **default executor-less, UI-less agent spine** as ONE Cordis bundle plugin. This is the package to read to see **the whole shared plugin tree at once**: the teaching overview of the spine behind every app package. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Composed system prompt and session prefix | Through its children, the bundle supplies the harness identity, configured persona, and the local skill catalog when skills exist. | The bundle adds no wrapper prose; input cost is exactly the sum of the child contributions, repeated on each request according to their lifecycles. | -| Composed tool surface | The `skill` schema is present, and the three bash schemas appear when a bash executor activates `dsh-tool-bash`; `tools` config can select normal, Code Mode, or both. | Fixed per-request schema or SDK cost for the visible composition. Tool results add data-dependent retained history. | - ## The tree it loads `apply(ctx, config)` mounts each of these as a child of the bundle fiber: @@ -56,6 +49,13 @@ The bundle FORWARDS each field to the child that owns it: `agents` to `agent-loo A YAML include can dedupe the config, but it cannot OWN a `bin`, and it can only *describe* the front-door coupling in a comment and trust each leaf to obey. Moving the spine into a package, and the front-door cluster into the app packages, means the default leaf for an ACP server has no logger entry to copy wrong — "the ACP app never logs to stdout" stops being a prose warning a leaf must remember and becomes the app package's default shape (a leaf can still add a sibling logger, so the rule stays documented — but it has nothing to get wrong by default). Services register in the root store keyed by their isolate symbol, so a child loaded here is visible to the bundle's siblings (the leaf's adapter and executor) exactly as a nested `plugin-include` subtree's services were — cordis gates every read on `inject`, never on load order. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed system prompt and session prefix | Through its children, the bundle supplies the harness identity, configured persona, and the local skill catalog when skills exist. | The bundle adds no wrapper prose; input cost is exactly the sum of the child contributions, repeated on each request according to their lifecycles. | +| Composed tool surface | The `skill` schema is present, and the three bash schemas appear when a bash executor activates `dsh-tool-bash`; `tools` config can select normal, Code Mode, or both. | Fixed per-request schema or SDK cost for the visible composition. Tool results add data-dependent retained history. | + ## Known Limitations and Deferred Work - **The spine set is fixed in code** — `apply()` mounts every child unconditionally (including `tool-bash`); no config excludes or replaces one, so swapping the loop or dropping a spine member means composing a different bundle. diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index cde258a9a1..6a233a03b8 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -4,13 +4,6 @@ THE concrete agent plugin: `ReactLoopAgent` and the loop driver. Implements the This is the only package in the harness that contains concrete loop logic. Everything else is an abstract service or a plugin against extension seams — new behavior goes into plugins, not here. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Complete conversation request | For each step, the loop sends the rendered per-agent system prompt, visible tool schemas, the frozen session prefix, and the session's derived messages. It supplies `model` and `cwd` variable values but no additional fixed prose. | System text, schemas, and prefix are paid again on every step. Per-agent scoping can substitute or remove individual contributions. | -| Retained message history | Accepted user messages, assistant messages, tool calls and results, injected context, and steering are logged and sent on later steps. Raw stream chunks, lifecycle boundaries, and other log-only events are excluded. | Input grows with every surface message until a compaction replacement shadows older nodes; a multi-step tool turn resends the accumulated prefix and history each step. | - ## Service: `AgentLoop` (ctx key: `agentLoop`) ### Public API @@ -110,6 +103,13 @@ Everything that goes beyond "call the model, run the tools, repeat" belongs to p - Persistence: `session/event` + `session/flush` - UI: `session/event` (assistant token stream, boundaries, tool activity) + `agent/*` control events (`agent/status`, `agent/created`/`agent/disposed`) +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Complete conversation request | For each step, the loop sends the rendered per-agent system prompt, visible tool schemas, the frozen session prefix, and the session's derived messages. It supplies `model` and `cwd` variable values but no additional fixed prose. | System text, schemas, and prefix are paid again on every step. Per-agent scoping can substitute or remove individual contributions. | +| Retained message history | Accepted user messages, assistant messages, tool calls and results, injected context, and steering are logged and sent on later steps. Raw stream chunks, lifecycle boundaries, and other log-only events are excluded. | Input grows with every surface message until a compaction replacement shadows older nodes; a multi-step tool turn resends the accumulated prefix and history each step. | + ## Known Limitations and Deferred Work - **Tool calls within a step execute sequentially** — parallel execution waits on concurrency-safety metadata in the tool contract (see `dsh-tools`). diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index 1c7bf63b99..f516618c70 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -2,13 +2,6 @@ Agent interface, registry, and `agent/*` event vocabulary. Every plugin (UI, hooks, orchestrators) programs against the `Agent` handle defined here — it has zero loop dependency, so the loop is swappable. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| User, steering, and injected messages | `send`, `steer`, and `inject` feed the owning session. `agent/prompt-submit`, `agent/session-prefix`, and other declared events let plugins block a prompt or add request material; this interface contributes no fixed prose itself. | Accepted content becomes retained history or a repeated session prefix; blocked content contributes no request tokens. Size is caller- and plugin-dependent. | -| Agent-scoped request composition | Registrations through `agent.ctx` can shadow prompt sections or tools and can install agent-only interceptors during unpublished setup. | The package adds zero tokens itself; scoped contributions affect only that agent and disappear on disposal. | - ## Service: `AgentRegistry` (ctx key: `agents`) Tracks live agents so UI, hook, and orchestrator plugins can find them without importing the concrete loop package. @@ -59,6 +52,13 @@ The handle every plugin programs against: - Event listeners: all `agent/*` events are declared here — no dependency on the loop package needed. - Subagent delegation: implemented by `@deepseek-ai/dsh-subagent`, not by a method on `Agent`; providers create or drive ordinary `Agent` handles through the factory seam, so spawn/fork/ACP transports stay outside the core agent interface. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| User, steering, and injected messages | `send`, `steer`, and `inject` feed the owning session. `agent/prompt-submit`, `agent/session-prefix`, and other declared events let plugins block a prompt or add request material; this interface contributes no fixed prose itself. | Accepted content becomes retained history or a repeated session prefix; blocked content contributes no request tokens. Size is caller- and plugin-dependent. | +| Agent-scoped request composition | Registrations through `agent.ctx` can shadow prompt sections or tools and can install agent-only interceptors during unpublished setup. | The package adds zero tokens itself; scoped contributions affect only that agent and disappear on disposal. | + ## Known Limitations and Deferred Work - **Inter-agent channels beyond delegation** — shared state, streaming child output, and background/poll semantics remain outside the current synchronous `ctx.subagents` seam. diff --git a/packages/core/scope/README.md b/packages/core/scope/README.md index e8ce1b41f1..a939a4c2af 100644 --- a/packages/core/scope/README.md +++ b/packages/core/scope/README.md @@ -2,12 +2,6 @@ Scoped-context registration primitive. `createScope(ctx, key)` mints a Cordis context that TAGS everything registered through it with an opaque `ScopeKey` and OWNS those registrations' lifetime (one backing fiber drives both facts); `scopeOf(ctx)` reads the tag; `scopeTarget(base, key)` builds the dispatch carrier that makes an event scope-filtered — listeners registered through a scoped context fire only for their key's subject, while plain plugin listeners keep firing for every subject. The agent loop is the one scope minter today (one scope per live agent, key = the `Agent` object — the `Agent.ctx` contract in `dsh-agent`), but the mechanism is key-agnostic so packages below the agent layer (`dsh-session`, `dsh-system-prompt`) depend on it without a dependency cycle. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Per-agent visibility control | This package emits no text or schema. It decides whether agent-scoped prompt sections, variables, tools, restrictions, and listeners apply to one agent, can shadow same-named global contributions, and removes them with that agent. | Zero direct tokens. It can add, replace, or remove whole contributions for one agent without changing another agent's request. | - ## Public API - `createScope(ctx: Context, key: ScopeKey): Scope` Mint a scope under `ctx`'s fiber. Usable synchronously (effect collection is uid-gated; service resolution falls through to the minting plugin's dependency surface). Throws on a primitive key, or when `ctx`'s fiber is disposing (`INACTIVE_EFFECT`). @@ -26,6 +20,12 @@ Ownership and visibility derive from ONE fact — which context a registration w Handing out a scoped context hands out the minting plugin's service-resolution capability (resolution walks the minting fiber's dependency chain, not the holder's) — mint scopes from a plugin whose `inject` surface is what scope holders should reach. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Per-agent visibility control | This package emits no text or schema. It decides whether agent-scoped prompt sections, variables, tools, restrictions, and listeners apply to one agent, can shadow same-named global contributions, and removes them with that agent. | Zero direct tokens. It can add, replace, or remove whole contributions for one agent without changing another agent's request. | + ## Known Limitations and Deferred Work - **Only scope-aware surfaces isolate state** — registries must file by `scopeOf()` and events must dispatch through `scopeTarget()`; an arbitrary Cordis service remains context-global merely because it is called through a scoped context. diff --git a/packages/core/session/README.md b/packages/core/session/README.md index f42b31f1cd..13131f0aec 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -2,13 +2,6 @@ Event-sourced session log and in-memory store. A `Session` is the append-only source of truth for an agent's whole interaction history — the LLM message history is *derived* from it. A **surface** layer (a linked list of message-producing events) is maintained on top of the raw log for efficient derivation and compaction. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Derived message history | The model receives projections of `user/message`, `assistant/message`, `tool/result`, `context/message`, and `steering/message` surface nodes. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message. | Appended surface nodes are resent on later steps. A `replace` surface operation removes the shadowed nodes from future inputs without deleting their raw log records. | -| Logged request header | The session reconstructs the system prompt, tool schemas, call config, and session prefix that the loop actually sent. Header events do not add a second copy to message history; the prefix is prepended outside `deriveMessages()`. | Zero duplicate tokens from logging. The reconstructed prefix, system text, and schemas still incur their normal per-request cost. | - ## Service: `SessionStore` (ctx key: `sessions`) Creates and holds event-sourced `Session` instances. Persistence is intentionally not implemented here — plugins subscribe to `session/event` and flush on `session/flush`. @@ -80,6 +73,13 @@ Every `SessionEvent` carries two optional top-level fields (structural metadata) - Replay/fork: `ctx.sessions.create(id, { seed })` seeds a new session with an existing event log. The surface rebuilds deterministically from `surfaceOp` markers in the seeded events. The seed is validated to the SAME always-on invariants `append` enforces — contiguous seqs, JSON-serializable data, and required `surfaceOp` markers on surface-eligible events — so marker-less message events are rejected at construction rather than silently vanishing from `deriveMessages()`. Broader turn-enclosure checks stay in `dsh-invariants` and persistence repair. Ordinary live-session forks use `ctx.sessions.fork(source, boundary?, childSessionId?)`, where `boundary` is the inclusive source event seq to fork through. - Compaction: the `dsh-compact-basic` plugin appends a `user/message` with `surfaceOp: { op: 'replace', start, end }` to shadow old surface nodes behind a summary checkpoint. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Derived message history | The model receives projections of `user/message`, `assistant/message`, `tool/result`, `context/message`, and `steering/message` surface nodes. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message. | Appended surface nodes are resent on later steps. A `replace` surface operation removes the shadowed nodes from future inputs without deleting their raw log records. | +| Logged request header | The session reconstructs the system prompt, tool schemas, call config, and session prefix that the loop actually sent. Header events do not add a second copy to message history; the prefix is prepended outside `deriveMessages()`. | Zero duplicate tokens from logging. The reconstructed prefix, system text, and schemas still incur their normal per-request cost. | + ## Known Limitations and Deferred Work - **Session branching/tree** (pi-style entry tree) — deferred unless needed beyond boundary-based `fork()`. diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index 91002cd1c8..5708083d99 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -2,13 +2,6 @@ System prompt assembly registry. Plugins contribute ordered text sections, tool-schema providers, named prompt variables, and authoritative named protections; the agent loop calls `assemble(context)` once per step, and `renderPrompt(assembly)` is the full system prompt the model sees. The plugin registers the harness-owned openers itself — the static `harness:identity` section and the deployment's `deployment:persona` section — so they exist for every agent regardless of which loop plugin drives it. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt | Every assembly starts with `You are an AI agent powered by the DeepSeek Harness SDK.`, then the configured persona and ordered plugin sections after strict variable interpolation. Empty sections disappear; scoped sections and variables can shadow globals for one agent. | Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content. | -| Tool schemas | The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent; reordering changes cache shape but not semantic content. | - ## Config | Key | Default | Meaning | @@ -50,6 +43,13 @@ Merge-extensible: plugins can declare extra fields on `PromptAssembly` and `Asse Design rationale: [the prompt-variables RFC](../../../docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Every assembly starts with `You are an AI agent powered by the DeepSeek Harness SDK.`, then the configured persona and ordered plugin sections after strict variable interpolation. Empty sections disappear; scoped sections and variables can shadow globals for one agent. | Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content. | +| Tool schemas | The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent; reordering changes cache shape but not semantic content. | + ## Known Limitations and Deferred Work - **Deployment-authored prompt text is config/composition only** — this plugin owns the global persona default, creator plugins may register agent-scoped shadows, and other sections come from the plugin that owns the fact; there is no end-user prompt-editing API. diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index 6f86fe501f..c65948464b 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -2,13 +2,6 @@ Tool registry and execution pipeline. Tool plugins register their schemas and executors; the agent loop executes each call through `tools/pre-execute` (the extensible allow/deny gate) → monotonic registered guards → `tools/execute` (an around-dispatch wrapper for timeout/retry/metrics plugins) → `tools/post-execute` (inspect/replace the result, attach context) → the observe-only `tools/result` notification. The registry also owns HOW its tools are presented to the model — its `mode` config selects native function calling, [Code Mode](#code-mode), or both. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead protects one `run_code` wire schema and adds a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's set. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | -| Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | - ## Service: `ToolRegistry` (ctx key: `tools`) ### Config @@ -147,6 +140,13 @@ Under `mode: code` (or `both`) the registry turns the tool surface into a progra The wire collapse is the registry's own contribution (`systemPrompt.tools()` is mode-aware), so the logged `request/header` records it for free. With no deliberate schema-adding assembly listener, `code` assembles exactly `[run_code]`, pinned by tests and the snapshot goldens; protection guarantees that `run_code` and `tools:sdk` remain present, not that unrelated listener additions are erased. Try it: `pnpm run demo:code-mode` ([the coding-agent example's Code Mode overlay](../../../examples/coding-agent/README.md#code-mode)); `pnpm run demo:code-mode acp` serves the same mode over ACP instead of the REPL. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead protects one `run_code` wire schema and adds a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's set. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | +| Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | + ## Known Limitations and Deferred Work - **Native tool calls execute sequentially** — `ToolDefinition` carries no concurrency-safety metadata; adding it (and parallel execution in the loop) waits on the deferred tool-shapes review (`TODO(review)`). diff --git a/packages/fs/fs-local/README.md b/packages/fs/fs-local/README.md index 135791e9c4..b45cbb13dd 100644 --- a/packages/fs/fs-local/README.md +++ b/packages/fs/fs-local/README.md @@ -10,12 +10,6 @@ await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) // freshness policy gate and @deepseek-ai/dsh-tool-fs to expose read/write/edit. ``` -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Filesystem tool results, indirectly | Through `dsh-tool-fs`, the model sees line-windowed UTF-8 file content, mutation acknowledgements, or structured filesystem errors. Real paths, versions, atomic-write mechanics, and directory metadata remain internal unless a consumer renders them. | Zero direct tokens. Read tokens are bounded by the tool's line, line-length, and byte caps; mutation results are small and remain in history until compaction. | - ## Behavior - **`resolve(path, opts?)`** — a relative `path` resolves against `opts.cwd` when the caller supplies one (the model-facing tools pass the calling agent's session cwd — see [the per-session cwd RFC](../../../docs/rfc/implemented/architecture/2026-07-02-fs-per-session-cwd.md)), else `config.cwd` (default `process.cwd()`); an absolute `path` ignores both. The `targetKey` is the file's `realpath`, so two input paths reaching the same file through symlinks share one identity, and writes/edits land on the link target (preserving the link). A not-yet-existing path uses the realpathed parent directory plus basename when the parent exists; only an unresolvable parent falls back to the absolute path. `displayPath` is the absolute (un-resolved) path. @@ -27,6 +21,12 @@ await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) The raw I/O lives in `src/fsio.ts` (Cordis-free, independently unit-tested); `src/index.ts` is the thin service wiring. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Filesystem tool results, indirectly | Through `dsh-tool-fs`, the model sees line-windowed UTF-8 file content, mutation acknowledgements, or structured filesystem errors. Real paths, versions, atomic-write mechanics, and directory metadata remain internal unless a consumer renders them. | Zero direct tokens. Read tokens are bounded by the tool's line, line-length, and byte caps; mutation results are small and remain in history until compaction. | + ## Known Limitations and Deferred Work - **`config.cwd` is not a sandbox** — it is a resolution default, not containment: absolute paths and `..` escape it. Enforce containment with a stricter `ctx.fs` backend or a permission plugin on the `tools/execute` waterfall ([capability-seam RFC](../../../docs/rfc/implemented/architecture/2026-06-17-filesystem-capability-seam.md#consequences)). diff --git a/packages/fs/fs-policy/README.md b/packages/fs/fs-policy/README.md index a81f6b1f05..df454a74bd 100644 --- a/packages/fs/fs-policy/README.md +++ b/packages/fs/fs-policy/README.md @@ -16,12 +16,6 @@ declare const ctx: Context await ctx.plugin(FsPolicy) ``` -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Filesystem tool outcome | This plugin adds no prompt or schema. It can turn an unobserved or stale write or edit into a structured `FS_NOT_OBSERVED` or `FS_STALE_VERSION` error result instead of a success; observation state itself is never shown. | Zero tokens on allowed operations beyond the ordinary tool result. A denial adds a small retained error result and avoids any success payload. | - ## The four-layer split | Layer | Package | Role | @@ -53,6 +47,12 @@ The `fs/write-intent`/`fs/edit-intent` slots hold exactly one decider — this p Because the plugin influences the world only through events, removing it does not break `@deepseek-ai/dsh-tool-fs` at a service-injection boundary: the tool falls through to the bare `ctx.fs` provider (unconditional write/edit, no observed-state). Loading it back layers the policy on. That graceful add/remove is the whole point of the event gate over a mandatory method service. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Filesystem tool outcome | This plugin adds no prompt or schema. It can turn an unobserved or stale write or edit into a structured `FS_NOT_OBSERVED` or `FS_STALE_VERSION` error result instead of a success; observation state itself is never shown. | Zero tokens on allowed operations beyond the ordinary tool result. A denial adds a small retained error result and avoids any success payload. | + ## Known Limitations and Deferred Work - **Observed state does not survive a session resume** — persistence of the `WeakMap` record is deferred, so a resumed session must re-read files before guarded writes/edits. diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index 0376bf1c0f..851a509cfd 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -13,12 +13,6 @@ This package is the provider-seam layer of the four-layer filesystem stack, spli A future sandboxed, virtual, or remote backend implements this interface and the policy/tool layers don't change. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The provider seam registers no prompt or tool. `dsh-tool-fs` converts provider text and structured `FsError` values into model-visible read, write, and edit results; policy listeners can change which outcome it receives. | Zero direct tokens. File content and errors enter context only through a consumer, whose window and byte caps determine result size. | - ## Service API (`ctx.fs`) A backend subclasses `FileSystem` and implements seven primitives. @@ -49,6 +43,12 @@ This package declares three events (see the generated [events catalog](../../../ `FsTargetKey` / `FsVersion` are branded opaque ids ([the branded-ids RFC](../../../docs/rfc/implemented/architecture/2026-06-20-branded-ids.md)) — consumers must not parse `targetKey` or interpret `version`; only `displayPath` is for model/UI output. `FsWriteIntent` is the explicit GUARDED write intent (`createIfAbsent` creates a missing target and rejects an existing one with `FS_NOT_OBSERVED`; `replaceIfVersion` replaces only at the observed version, else `FS_STALE_VERSION`); omitting it from `writeText` is the third, unconditional state. Failures throw `FsError` (extends `HarnessError`, [the structured error taxonomy RFC](../../../docs/rfc/implemented/architecture/2026-06-11-structured-error-taxonomy.md)) carrying a stable `FsErrorCode` (`FS_NOT_FOUND`, `FS_NOT_DIRECTORY`, `FS_NOT_TEXT`, `FS_NOT_REGULAR_FILE`, `FS_PERMISSION_DENIED`, `FS_IO_ERROR`, `FS_STALE_VERSION`, `FS_NOT_OBSERVED`, `FS_AMBIGUOUS_EDIT`, `FS_EDIT_NOT_FOUND`, `FS_ABORTED`); the tool registry surfaces `{ name, code }` on `isError` results. See `src/types.ts` for the full contracts. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The provider seam registers no prompt or tool. `dsh-tool-fs` converts provider text and structured `FsError` values into model-visible read, write, and edit results; policy listeners can change which outcome it receives. | Zero direct tokens. File content and errors enter context only through a consumer, whose window and byte caps determine result size. | + ## Known Limitations and Deferred Work - **Text-only by contract** — backends reject binary/non-UTF-8 content with `FS_NOT_TEXT`; binary-safe operations are a deliberate deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md). diff --git a/packages/fs/tool-fs/README.md b/packages/fs/tool-fs/README.md index 8bcdba2ffb..c955956f55 100644 --- a/packages/fs/tool-fs/README.md +++ b/packages/fs/tool-fs/README.md @@ -11,14 +11,6 @@ await ctx.plugin(ToolFs) // this package — re `@deepseek-ai/dsh-fs-policy` is **optional**: omit it and the tools run against the bare provider (unconditional write/overwrite/edit, no observed-state). A deployment that loads these tools is expected to also load it, so the behavior is read-before-write/edit. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt | Visible agents receive three short sections explaining line-windowed reads, whole-file writes, literal edits, and the default read-before-mutate habit. | Fixed guidance cost per request while the tools are visible. | -| Tool schemas | The model sees `read`, `write`, and `edit` with their snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. | -| Tool-call history and results | Read returns numbered UTF-8 lines and a pagination or cap footer; write and edit return concise success text or structured errors. The model-emitted write or edit content also remains in the assistant tool-call arguments. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`. Call arguments and results are resent until compaction, so large write payloads can dominate history even though the success result is small. | - ## Config All keys are optional; the defaults are the shipped read caps. @@ -56,6 +48,14 @@ The tool passes `exec` (the tool-execution context) as the opaque `actor` on eve The read rendering (line windowing + output formatting) lives in `src/read-render.ts` (Cordis-free, independently unit-tested); `src/read.ts`/`write.ts`/`edit.ts` are the tool executors and `src/index.ts` composes them. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Visible agents receive three short sections explaining line-windowed reads, whole-file writes, literal edits, and the default read-before-mutate habit. | Fixed guidance cost per request while the tools are visible. | +| Tool schemas | The model sees `read`, `write`, and `edit` with their snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. | +| Tool-call history and results | Read returns numbered UTF-8 lines and a pagination or cap footer; write and edit return concise success text or structured errors. The model-emitted write or edit content also remains in the assistant tool-call arguments. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`. Call arguments and results are resent until compaction, so large write payloads can dominate history even though the success result is small. | + ## Known Limitations and Deferred Work - **No directory-listing, glob, grep, or search tools ship** — a deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md); `ctx.fs.listDir` serves provider code such as skill discovery but still has no model-facing consumer, so models fall back to `bash`. diff --git a/packages/guard/repeat-tool-guard/README.md b/packages/guard/repeat-tool-guard/README.md index 9bb3bc0e02..8600b3d85e 100644 --- a/packages/guard/repeat-tool-guard/README.md +++ b/packages/guard/repeat-tool-guard/README.md @@ -2,12 +2,6 @@ An advisory loop-breaker, not a model-facing tool: it never appears in the tool list, never vetoes or rewrites a call, and adds exactly one behavior — it watches each agent's stream of tool calls, counts runs of consecutive calls to the same tool with identical canonicalized arguments, and at configured run lengths injects an escalating advisory reminder telling the model to stop repeating itself, re-read the last result, and either change approach or conclude. The decision (retry differently, gather more evidence, or finish) stays entirely with the model: a legitimately repeated call is delayed by nothing and blocked by nothing. Decision record: [the repeat-tool-guard RFC](../../../docs/rfc/implemented/feature/2026-07-08-repeat-tool-guard.md). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Conditional context message | At configured consecutive-repeat thresholds, that agent receives a source-attributed synthetic user reminder after the tool results, asking it to inspect the prior result and change approach or finish. No tool schema or normal-call text is added. | Zero tokens before a threshold. Each reminder is retained history; the detailed form caps the quoted canonical arguments at `argumentsPreviewChars`, while agents keep independent counters. | - ## Config ```yaml @@ -42,6 +36,12 @@ Reminders ride the post-execute decision's `additionalContext` (source `{kind: ' Unit suites drive a real agent loop against a mock adapter (no network) and cover the chain semantics above to per-file 100%. The snapshot tier owns the transcript surface: a scripted-replay scenario repeats a call five times and pins both reminder tiers (gentle at 3, detailed at 5) as `context/message`s in the ACP transcript. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conditional context message | At configured consecutive-repeat thresholds, that agent receives a source-attributed synthetic user reminder after the tool results, asking it to inspect the prior result and change approach or finish. No tool schema or normal-call text is added. | Zero tokens before a threshold. Each reminder is retained history; the detailed form caps the quoted canonical arguments at `argumentsPreviewChars`, while agents keep independent counters. | + ## Known Limitations and Deferred Work - **Exact-match detection only** — canonicalization is a deep key-sort, so near-identical variants (a tweaked path, extra whitespace inside a value) evade the chain; fuzzy matching is rejected pending evidence of need. diff --git a/packages/hooks/hook-protocol/README.md b/packages/hooks/hook-protocol/README.md index f39dfe6ccf..754d6ee6f1 100644 --- a/packages/hooks/hook-protocol/README.md +++ b/packages/hooks/hook-protocol/README.md @@ -4,12 +4,6 @@ The **shared core** of the Claude Code / Codex hook wire protocol. NOT a cordis Why a shared lib at all: Codex deliberately reimplements a *subset* of the Claude Code hook protocol — the same `hooks.json` matcher-group shape, the same exit-code/stdout output contract, the same command-hook execution model. The genuinely-shared parts live here; each bridge owns only what differs. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This library registers nothing. Its `hook/invoked` and `hook/result` events are log-only and do not enter derived messages; bridge packages decide whether parsed `additionalContext`, blocks, or continuation feedback reach the model. | Zero direct tokens. Persisted hook audit records add no context tokens. | - ## What's shared (here) vs. per-dialect (the bridges) | Concern | Here (`dsh-hook-protocol`) | The bridge (`dsh-hooks-claude` / `-codex`) | @@ -35,6 +29,12 @@ Declaration-merged into `SessionEventMap` (log-only, like `compact/*` — NOT a Like every event they must sit inside an open turn. The mid-turn points (`PreToolUse`/`PostToolUse`/`UserPromptSubmit`/`Stop`) fire inside the loop's open turn by construction; `SessionStart` gets no `hook/*` record (its injected `context/message` is the durable evidence) — see the hooks RFC. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This library registers nothing. Its `hook/invoked` and `hook/result` events are log-only and do not enter derived messages; bridge packages decide whether parsed `additionalContext`, blocks, or continuation feedback reach the model. | Zero direct tokens. Persisted hook audit records add no context tokens. | + ## Known Limitations and Deferred Work - **`HookOutput.updatedInput` is parsed but not honored** — input rewrite is a deferred consistency-design problem ([the pre-tool-input-rewrite RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md)); a bridge logs + warns when a hook sets it. See `src/types.ts` for the full contracts. diff --git a/packages/hooks/hooks-claude/README.md b/packages/hooks/hooks-claude/README.md index 858d4c70ca..f55790e9e7 100644 --- a/packages/hooks/hooks-claude/README.md +++ b/packages/hooks/hooks-claude/README.md @@ -4,13 +4,6 @@ A cordis plugin that runs a user's existing **Claude Code** hook config (a `hook A native cordis plugin could do everything this bridge does — more powerfully, with typed returns and no serialization boundary. **The bridge exists only to run UNMODIFIED external CC hooks faithfully**; anything bespoke should be a native plugin on the same seams (see [the interception-seams RFC](../../../docs/rfc/implemented/feature/2026-06-30-interception-seams.md)). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Hook-provided context | `SessionStart`, accepted prompt, post-tool, and live in-process subagent-start hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. Remote-child injection has no local target. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent in later conversation requests until compaction. | -| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny or ask before a tool, block a post-tool result with feedback, or force another model step. `systemMessage` and `updatedInput` are logged or warned but are not model-visible in this implementation. | Blocking a prompt removes that prompt's request tokens; denial or feedback adds a retained error or context result; forced continuation pays another full request. | - ## Config ```ts @@ -57,6 +50,13 @@ The matcher subject is the tool name (`PreToolUse`/`PostToolUse`), the session s Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-claude' }` source. `agent.inject()` defaults a missing source to `{ kind: 'user' }`, which would mislabel plugin context as a user prompt — so the bridge always names itself. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Hook-provided context | `SessionStart`, accepted prompt, post-tool, and live in-process subagent-start hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. Remote-child injection has no local target. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent in later conversation requests until compaction. | +| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny or ask before a tool, block a post-tool result with feedback, or force another model step. `systemMessage` and `updatedInput` are logged or warned but are not model-visible in this implementation. | Blocking a prompt removes that prompt's request tokens; denial or feedback adds a retained error or context result; forced continuation pays another full request. | + ## Known Limitations and Deferred Work - **`updatedInput` (tool-input rewrite)** is logged + warned, **not honored** — input rewrite is a deferred consistency-design problem ([the pre-tool-input-rewrite RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md)). diff --git a/packages/hooks/hooks-codex/README.md b/packages/hooks/hooks-codex/README.md index f4ad398aa8..b5ed2ec35a 100644 --- a/packages/hooks/hooks-codex/README.md +++ b/packages/hooks/hooks-codex/README.md @@ -12,13 +12,6 @@ Codex's hook protocol is a deliberate **subset** of Claude Code's (same `hooks.j A native cordis plugin could do everything this bridge does, more powerfully; the bridge exists only to run UNMODIFIED external Codex hooks faithfully (see [the interception-seams RFC](../../../docs/rfc/implemented/feature/2026-06-30-interception-seams.md)). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Hook-provided context | `SessionStart`, accepted prompt, and post-tool hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent until compaction. | -| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny a tool, block a post-tool result with feedback, or force another model step. Codex `systemMessage` is not surfaced. | Blocking a prompt removes its request tokens; denial or feedback adds retained result text; forced continuation pays another full request. | - ## Config ```ts @@ -61,6 +54,13 @@ A tool call's payload carries the real `tool_name` (the same value the matcher t Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-codex' }` source (`agent.inject()` would otherwise default it to `{ kind: 'user' }`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Hook-provided context | `SessionStart`, accepted prompt, and post-tool hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent until compaction. | +| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny a tool, block a post-tool result with feedback, or force another model step. Codex `systemMessage` is not surfaced. | Blocking a prompt removes its request tokens; denial or feedback adds retained result text; forced continuation pays another full request. | + ## Known Limitations and Deferred Work - **Stop loop-guard** (`TODO(stop-loop-guard)`) — as in CC, a Stop hook that unconditionally blocks would force-continue every step (`stop_hook_active` is always `false` here); the loop-guard is deferred, and a hook author must self-limit until it lands. diff --git a/packages/llm/llm-deepseek/README.md b/packages/llm/llm-deepseek/README.md index 11f7396d77..f491605b39 100644 --- a/packages/llm/llm-deepseek/README.md +++ b/packages/llm/llm-deepseek/README.md @@ -4,13 +4,6 @@ DeepSeek chat-completions adapter for the harness LLM seam: hand-rolled `fetch` A second, independent implementation of the same seam exists in `@deepseek-ai/dsh-llm-pi-ai` (library-backed). Same Config shape — pick one per context (registering both for the same model names throws by design). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| DeepSeek request | The selected DeepSeek model receives the harness system prompt, message history, tool schemas, stop sequences, and call config without adapter-authored prompt prose. On a prior assistant turn with tool calls, its reasoning content is passed back as required; reasoning from tool-call-free turns is omitted. | Provider tokenization governs exact input. Conditional reasoning passback increases tool-round-trip context, while dropping other reasoning avoids paying those tokens again; cache-read usage is reported when available. | -| DeepSeek response | Reasoning, text, and raw-string tool arguments are translated into harness chunks for the loop to log and assemble. | Generated tokens follow provider thinking and effort settings plus the request's `maxTokens`; only loop-retained blocks affect later input. | - ## Config ```yaml @@ -49,6 +42,13 @@ Non-2xx responses throw `LlmError` with stable codes: `AUTH` (401/403), `RATE_LI Unit suites run against a local `node:http` mock SSE server (no network). Real-API coverage lives in `tests/adapter.e2e.ts` (`pnpm run test:e2e`, key-gated): V4 Flash + V4 Pro across thinking enabled/disabled and both official effort levels, including the thinking+tools round trip with reasoning passback. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| DeepSeek request | The selected DeepSeek model receives the harness system prompt, message history, tool schemas, stop sequences, and call config without adapter-authored prompt prose. On a prior assistant turn with tool calls, its reasoning content is passed back as required; reasoning from tool-call-free turns is omitted. | Provider tokenization governs exact input. Conditional reasoning passback increases tool-round-trip context, while dropping other reasoning avoids paying those tokens again; cache-read usage is reported when available. | +| DeepSeek response | Reasoning, text, and raw-string tool arguments are translated into harness chunks for the loop to log and assemble. | Generated tokens follow provider thinking and effort settings plus the request's `maxTokens`; only loop-retained blocks affect later input. | + ## Known Limitations and Deferred Work - **`tool_choice` is not mapped** — not part of the core vocabulary (MVP cut, shared with the pi-ai twin). diff --git a/packages/llm/llm-pi-ai/README.md b/packages/llm/llm-pi-ai/README.md index a491f847fe..7a301f8504 100644 --- a/packages/llm/llm-pi-ai/README.md +++ b/packages/llm/llm-pi-ai/README.md @@ -2,13 +2,6 @@ DeepSeek adapter for the harness LLM seam backed by [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai) (the LLM library behind the pi agent). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| DeepSeek request through pi-ai | The selected model receives the same logical system prompt, history, tools, stop sequences, and raw replayed tool arguments as the hand-written adapter. This package adds no prompt prose and removes pi-ai's own per-tool `strict` default to preserve that contract. | Provider tokenization governs exact input. Reasoning level changes generated and passback content; pi-ai reports reasoning inside output usage rather than as a separate count. | -| DeepSeek response | pi-ai events become harness reasoning, text, tool-call, usage, and finish chunks; parsed tool arguments are restored to raw JSON strings at the harness boundary. | Generated content affects later inputs only after the loop records it; adapter conversion adds no model-visible text. | - ## Why a second adapter exists `@deepseek-ai/dsh-llm-deepseek` already talks to the same endpoint. This package is its **design-verification twin**: same models, same wire protocol, completely different internals — a unified LLM library with its own event vocabulary versus hand-rolled fetch/SSE. Anything the harness `StreamChunk` protocol cannot express for BOTH implementations is a core-vocabulary bug. The differences it exercised on purpose: @@ -44,6 +37,13 @@ pi-ai declares the openai/anthropic/google/mistral/AWS SDKs as install-time depe Unit suites run against a local `node:http` mock SSE server (pi-ai's openai SDK happily talks to any base URL). Real-API coverage in `tests/adapter.e2e.ts` (`pnpm run test:e2e`, key-gated): V4 Flash + V4 Pro across all exposed reasoning levels (off/high/xhigh), the thinking+tools round trip, and a cross-adapter structural-equivalence check against llm-deepseek. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| DeepSeek request through pi-ai | The selected model receives the same logical system prompt, history, tools, stop sequences, and raw replayed tool arguments as the hand-written adapter. This package adds no prompt prose and removes pi-ai's own per-tool `strict` default to preserve that contract. | Provider tokenization governs exact input. Reasoning level changes generated and passback content; pi-ai reports reasoning inside output usage rather than as a separate count. | +| DeepSeek response | pi-ai events become harness reasoning, text, tool-call, usage, and finish chunks; parsed tool arguments are restored to raw JSON strings at the harness boundary. | Generated content affects later inputs only after the loop records it; adapter conversion adds no model-visible text. | + ## Known Limitations and Deferred Work - **`tool_choice` is not mapped** — same MVP contract as llm-deepseek. diff --git a/packages/llm/llm/README.md b/packages/llm/llm/README.md index cc1107fe54..8acff4f0da 100644 --- a/packages/llm/llm/README.md +++ b/packages/llm/llm/README.md @@ -2,13 +2,6 @@ Provider-neutral LLM vocabulary and abstract service. This package defines the canonical language spoken by the agent loop, session logs, and every plugin. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Provider request transport | This service adds no system text, schema, or message. It routes the already-assembled frozen `GenerateOptions` to one adapter, while `llm/stream` listeners may cache, retry, or replace the stream without mutating that request. | Zero direct context tokens. The selected adapter and provider tokenizer determine billing, cache accounting, and serialization overhead for the existing content. | -| Streamed model output | Text, reasoning, and tool-call chunks are exposed to the loop, which decides what becomes retained assistant history. | Output usage is provider-reported; later input cost arises only after the loop records assembled content. | - ## Service: `LlmService` (ctx key: `llm`) An adapter registry plus a single streaming call surface, interceptable via a waterfall event. @@ -55,6 +48,13 @@ Every product adapter must identify the application on every provider HTTP reque Two adapters implement `LlmAdapter` against this vocabulary, deliberately built on different internals to keep the contract honest (see [the twin LLM adapters](../../../docs/rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md)): [`@deepseek-ai/dsh-llm-deepseek`](../llm-deepseek) (hand-rolled fetch/SSE) and [`@deepseek-ai/dsh-llm-pi-ai`](../llm-pi-ai) (via `@earendil-works/pi-ai`). The pair pinned down the `StreamChunk` conventions now documented in `types.ts` (usage before finish, raw-string tool arguments, the two sanctioned error paths). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Provider request transport | This service adds no system text, schema, or message. It routes the already-assembled frozen `GenerateOptions` to one adapter, while `llm/stream` listeners may cache, retry, or replace the stream without mutating that request. | Zero direct context tokens. The selected adapter and provider tokenizer determine billing, cache accounting, and serialization overhead for the existing content. | +| Streamed model output | Text, reasoning, and tool-call chunks are exposed to the loop, which decides what becomes retained assistant history. | Output usage is provider-reported; later input cost arises only after the loop records assembled content. | + ## Known Limitations and Deferred Work - **No retry/caching/rate-limit layer ships** — `llm/stream` is the intended wrap seam and has no production listener, so provider 429/5xx failures surface immediately. diff --git a/packages/session-persistence/session-persistence-jsonl/README.md b/packages/session-persistence/session-persistence-jsonl/README.md index 410879728a..4e150f85a0 100644 --- a/packages/session-persistence/session-persistence-jsonl/README.md +++ b/packages/session-persistence/session-persistence-jsonl/README.md @@ -2,12 +2,6 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence` (the `dsh-session-persistence` seam). One append-only `.jsonl` event log per session. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Resumed conversation history | JSONL storage contributes no live prompt or schema. Loading restores stored surface history and preserves prior request headers for reconstruction; the new loop composes its current envelope. An interrupted tail is balanced with error tool results. Raw `assistant/chunk` records do not duplicate messages. | Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus small repair results only after an interrupted tool turn. | - ## On-disk layout ``` @@ -36,6 +30,12 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence The plugin generalizes the example `session-jsonl.ts`: it subscribes to `session/created` (capture the header; persist a fork's seed once), `session/event` (snapshot each event when buffering — the live `session.events` object is mutable), and `session/flush`/dispose (drain the write-behind buffer through `append`). A per-session write cursor means a resumed session never re-appends already-stored events. Existing live sessions are seeded on plugin apply (HMR does not replay `session/created`). All backend operations for one session are serialized, and disposal awaits quiescence (every init + final drain) before returning, so no write lands after teardown. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | JSONL storage contributes no live prompt or schema. Loading restores stored surface history and preserves prior request headers for reconstruction; the new loop composes its current envelope. An interrupted tail is balanced with error tool results. Raw `assistant/chunk` records do not duplicate messages. | Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus small repair results only after an interrupted tool turn. | + ## Known Limitations and Deferred Work - **Only the current `SESSION_FORMAT_VERSION` (v0) loads** — the on-disk format is pre-release/unstable: a breaking format change is absorbed at v0 and non-current logs are rejected; there is no migration. diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index ab5755bb52..558a24af6c 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -4,12 +4,6 @@ A SQLite durable session-persistence backend — a second `SessionPersistence` i > **TODO:** this backend talks to `node:sqlite` directly. If a cordis database service (`cordis/db` / a `@cordisjs` SQL driver plugin) is adopted, route through that instead of holding a raw `DatabaseSync` here — the contract surface (`SessionPersistence`) would not change, only the storage driver. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Resumed conversation history | SQLite storage contributes no live prompt or schema. Loading restores the same surface history as JSONL and preserves prior headers for reconstruction; the new loop composes its current envelope. Interrupted rows are balanced with error tool results. Row metadata and raw chunks are not messages. | Zero live-request tokens. Resume restores retained history and pays the current envelope, with small repair-result tokens only for an interrupted tool turn. | - ## Storage model Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, type, time, data, source_event_seqs, surface_op)` — `data` is the event payload as JSON text, so the row shape is the event verbatim (including `assistant/chunk`, keeping `seq` contiguous). The two `TEXT` columns `source_event_seqs` and `surface_op` are nullable; they store the event's optional surface-metadata fields (see [session surface](../../../docs/rfc/implemented/architecture/2026-06-18-session-surface.md)). Out-of-log metadata (`SessionHeader`) lives in a `sessions` row. A `sessions` row is written only by the first `append` — its existence is the lazy-materialization signal (`list` reports exactly the sessions that have a row), so no separate column is needed. @@ -35,6 +29,12 @@ interface Config { Like the JSONL backend, the plugin also installs the `session/event` → buffer → `session/flush` drain: it snapshots each event when buffered (the live `session.events` object is mutable), persists a fork's seed once on `session/created`, keeps a per-session write cursor so a resumed session never re-appends stored events, and seeds existing live sessions on apply (HMR does not replay `session/created`). Dispose awaits every in-flight init + final drain and then closes the database, so no write lands after teardown. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | SQLite storage contributes no live prompt or schema. Loading restores the same surface history as JSONL and preserves prior headers for reconstruction; the new loop composes its current envelope. Interrupted rows are balanced with error tool results. Row metadata and raw chunks are not messages. | Zero live-request tokens. Resume restores retained history and pays the current envelope, with small repair-result tokens only for an interrupted tool turn. | + ## Known Limitations and Deferred Work - **Raw `node:sqlite`, pending a cordis database service** — the backend holds a `DatabaseSync` directly; if a `cordis/db` / `@cordisjs` SQL driver is adopted, the storage driver routes through it (the `SessionPersistence` contract would not change) — a marked TODO. diff --git a/packages/session-persistence/session-persistence/README.md b/packages/session-persistence/session-persistence/README.md index eabd98cecf..c8e2cbe35b 100644 --- a/packages/session-persistence/session-persistence/README.md +++ b/packages/session-persistence/session-persistence/README.md @@ -4,12 +4,6 @@ The abstract durable session-persistence seam (`ctx.sessionPersistence`). Define The persisted unit IS the existing `SessionEvent` (event-sourced model — the log is the single source of truth), so there is no parallel "persisted message" type. Metadata that is NOT replayable conversation state (format version, cwd, lineage, seed boundary) travels separately as `SessionHeader`, owned by `dsh-session` and re-exported here. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Resumed conversation history | This seam adds no prompt or schema. Resume restores stored surface events as message history; stored request headers reconstruct earlier calls, while the new loop composes the current system prompt, tools, and session prefix for its next request. Crash repair inserts an error result for each unanswered tool call. | Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; crash repair can add small error-result tokens that keep the provider transcript valid. | - ## Service API (`ctx.sessionPersistence`) | Method | Contract | @@ -56,6 +50,12 @@ Three backends run these suites: an in-memory reference (in `tests/`), `dsh-sess Re-exported from `dsh-session`: `SessionHeader` (immutable session metadata: `version`, `id`, `createdAt`, `cwd?`, `parentSession?`, `seedLength?`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | This seam adds no prompt or schema. Resume restores stored surface events as message history; stored request headers reconstruct earlier calls, while the new loop composes the current system prompt, tools, and session prefix for its next request. Crash repair inserts an error result for each unanswered tool call. | Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; crash repair can add small error-result tokens that keep the provider transcript valid. | + ## Known Limitations and Deferred Work - **No deletion or retention surface** — the seam is `create`/`append`/`load`/`list` only; pruning stored sessions is out-of-band backend maintenance. diff --git a/packages/skill/skill-local/README.md b/packages/skill/skill-local/README.md index 44b8f15e33..7833f55e09 100644 --- a/packages/skill/skill-local/README.md +++ b/packages/skill/skill-local/README.md @@ -4,12 +4,6 @@ Local filesystem provider for the `ctx.skills` registry. This package implements one skill source. It scans local project, custom, and user skill roots, parses `SKILL.md` or flat Markdown skill files, and registers the provider on `ctx.skills`. The registry remains in `@deepseek-ai/dsh-skill`; the session-prefix catalog and model-facing loader tool remain in `@deepseek-ai/dsh-tool-skill`. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Skill catalog and loaded body, indirectly | When `dsh-tool-skill` is visible, discovered model-invocable skill names and descriptions enter its session-prefix catalog; a `skill` call returns the selected instructions and resource-base guidance. Paths, provider ranks, and disabled skills stay out of the catalog. | Zero direct tokens from this provider. Catalog cost scales with discovered entries under the consumer's per-description cap; a full body is added only after selection and remains in tool history. | - ## Plugin Requires `ctx.skills` (`inject: ['skills']`). @@ -42,6 +36,12 @@ When `ctx.fs` is available, discovery lists roots through `ctx.fs.listDir`, read Skills can be single-level directory bundles (`/SKILL.md`) or flat Markdown files (`.md`). Nested `**/SKILL.md` discovery is intentionally not part of v1. Frontmatter is parsed as YAML with the `yaml` package; it requires `name` and `description`, while `whenToUse`, `disableModelInvocation`, and `metadata` are optional. Names must be kebab-case. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Skill catalog and loaded body, indirectly | When `dsh-tool-skill` is visible, discovered model-invocable skill names and descriptions enter its session-prefix catalog; a `skill` call returns the selected instructions and resource-base guidance. Paths, provider ranks, and disabled skills stay out of the catalog. | Zero direct tokens from this provider. Catalog cost scales with discovered entries under the consumer's per-description cap; a full body is added only after selection and remains in tool history. | + ## Known Limitations and Deferred Work - **Discovery is one level deep** — only `//SKILL.md` and `/.md` are recognized; nested skill trees and package manifests are ignored. diff --git a/packages/skill/skill/README.md b/packages/skill/skill/README.md index 044cff7eeb..595b0d92f1 100644 --- a/packages/skill/skill/README.md +++ b/packages/skill/skill/README.md @@ -4,12 +4,6 @@ Pure agent skill provider registry. This package owns the `ctx.skills` interface. It does not know whether skills come from local files, embedded plugin data, HTTP, or another backend; providers register those sources with `ctx.skills.registerProvider(...)`. The shipped local implementation is [`@deepseek-ai/dsh-skill-local`](../skill-local). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The registry renders nothing and registers no tool. `dsh-tool-skill` turns `list()` summaries into a session prefix and a selected `get()` body into a tool result; provider failures can remove entries from that request's catalog. | Zero direct tokens. Catalog size, descriptions, and loaded body length affect context only through the consumer. | - ## Service: `SkillService` (ctx key: `skills`) ### Public API @@ -39,6 +33,12 @@ The registry validates candidate names, descriptions, ranks, and provider owners The registry does not render model guidance or register model-facing tools. [`@deepseek-ai/dsh-tool-skill`](../tool-skill) consumes `ctx.skills` to provide the session-prefix catalog and `skill` tool, so providers remain independent of the model surface. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The registry renders nothing and registers no tool. `dsh-tool-skill` turns `list()` summaries into a session prefix and a selected `get()` body into a tool result; provider failures can remove entries from that request's catalog. | Zero direct tokens. Catalog size, descriptions, and loaded body length affect context only through the consumer. | + ## Known Limitations and Deferred Work - **Completed catalogs have no TTL or watcher invalidation** — a provider's underlying files or remote data can change without a registration revision, so a cached cwd stays stale until eviction or provider/runtime reload. diff --git a/packages/skill/tool-skill/README.md b/packages/skill/tool-skill/README.md index 681855e6ef..a9ad9e0053 100644 --- a/packages/skill/tool-skill/README.md +++ b/packages/skill/tool-skill/README.md @@ -4,13 +4,6 @@ The model-facing skill catalog and `skill` tool. Requires `ctx.tools` and `ctx.skills` (`inject: ['tools', 'skills']`). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Session prefix | If model-invocable skills exist and this exact `skill` tool is visible, the agent receives one user-role `` listing sorted names and capped descriptions. The composed catalog is frozen for the loop instance and prepended to every request, outside ordinary history. | Repeated input cost scales with skill count and `catalogDescriptionMaxLength`; no catalog tokens are sent when the list is empty or the tool is hidden or shadowed. | -| Tool schema and result | The model sees the fixed `skill(name)` schema. A successful call returns the selected full instructions plus resource-resolution guidance; no duplicate `agent.inject()` copy is made. | Fixed schema cost per request. Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction. | - ## Session-prefix catalog The plugin contributes one user-role `` catalog through `agent/session-prefix`. It resolves skills for the calling session's cwd, forwards the prefix abort signal to discovery, and lists only sorted `name` and `description` entries; skill bodies, paths, sources, providers, and `whenToUse` hints remain outside the catalog. The catalog is omitted when no model-invocable skills are available, and also when that agent's tool view restricts away the shipped `skill` tool or resolves a same-name scoped shadow instead. This exact-definition check keeps prompt guidance, the model-visible schema, and executable dispatch aligned. @@ -27,6 +20,13 @@ Execution uses the calling agent's `session.header.cwd` so workspace-sensitive p The tool does not call `agent.inject()` in v1. Its result is already recorded as the tool result and becomes available to the next model step without duplicating the content as synthetic context. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Session prefix | If model-invocable skills exist and this exact `skill` tool is visible, the agent receives one user-role `` listing sorted names and capped descriptions. The composed catalog is frozen for the loop instance and prepended to every request, outside ordinary history. | Repeated input cost scales with skill count and `catalogDescriptionMaxLength`; no catalog tokens are sent when the list is empty or the tool is hidden or shadowed. | +| Tool schema and result | The model sees the fixed `skill(name)` schema. A successful call returns the selected full instructions plus resource-resolution guidance; no duplicate `agent.inject()` copy is made. | Fixed schema cost per request. Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction. | + ## Known Limitations and Deferred Work - **The catalog omits `whenToUse`, source, and provider metadata** — routing is based only on name and a capped description; `whenToUse` remains provider metadata and is not rendered by the loaded wrapper either. diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index 6ff15ca96c..e12586c5a7 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -4,13 +4,6 @@ The out-of-process **ACP subagent backend**: runs each child agent in a spawned It is the direction-inverted twin of the server-side bridge in [`@deepseek-ai/dsh-acp`](../../ui/acp): that package is the ACP *agent* (it answers `initialize`/`newSession`/`prompt`); this one is the ACP *client* (it *calls* them and implements the `sessionUpdate`/`requestPermission` callbacks). Point the configured command at the `acp-agent` example to "talk to our own process". -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent request | The remote child receives the standalone task through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation and cannot enforce the parent's scoped persona or tool filter. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | -| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or a stop-reason error, not intermediate messages or tool traffic. | Parent input grows only by the final result, which is data-dependent and retained until compaction. This provider adds no parent schema itself. | - ## What it does `start(request)` spawns the configured command, wraps its stdio in an ACP `ClientSideConnection`, and drives one session: `initialize` → `newSession` → `prompt`. `run.started` resolves after `newSession` publishes the remote session and rejects when initialization fails or cancellation wins first; the service emits no start/end pair for a child that never became live. The child's streamed `agent_message_chunk` text becomes the `SubagentResult.output`; the prompt's terminal `StopReason` maps to the stop reason. `dispose()` kills the subprocess and awaits its exit. @@ -77,6 +70,13 @@ The child env is built by [`buildChildEnv` from `@deepseek-ai/dsh-subagent-subpr Named `name` / `inject` / `Config` / `apply`, with **no default export**: the cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray default would collapse the module to the bare function and drop the `inject` namespace (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The remote child receives the standalone task through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation and cannot enforce the parent's scoped persona or tool filter. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | +| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or a stop-reason error, not intermediate messages or tool traffic. | Parent input grows only by the final result, which is data-dependent and retained until compaction. This provider adds no parent schema itself. | + ## Known Limitations and Deferred Work - **A fresh process per run** — persistent-process pooling is a future optimization ([the seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). diff --git a/packages/subagent/subagent-fork/README.md b/packages/subagent/subagent-fork/README.md index e3882dbb1a..c747988d78 100644 --- a/packages/subagent/subagent-fork/README.md +++ b/packages/subagent/subagent-fork/README.md @@ -2,13 +2,6 @@ The in-process **fork** subagent backend: a [`SubagentProvider`](../subagent/README.md) that runs each child as a child [`Agent`](../../core/agent) **seeded with a prefix of the parent's session log** — so the child inherits the parent's conversation context instead of starting fresh. Shares the run driver (`startInProcessRun`) with [`dsh-subagent-spawn`](../subagent-spawn/README.md); the only difference is the seed. The shared `run.started` boundary resolves only after the seeded child is published, so `subagent/start` observers see a live registry entry. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent history | The child receives the parent's balanced completed-turn surface prefix, then the new task, along with its own scoped persona, tool filter, and optional structured-output contract. The parent's current in-flight turn is excluded. | Forking duplicates the retained completed history into a separate child's requests; the child then accumulates its own tokens independently. A first-turn fork has no inherited history. | -| Parent tool result, indirectly | The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work. | Parent input grows by one data-dependent final result retained until compaction. | - ## The seed boundary (the crux) At the moment a subagent tool's `execute` runs, the parent's CURRENT turn is open and unbalanced: the log holds the `assistant/message` carrying this spawn's tool-call and the dangling `tool/call` with no `tool/result` yet. Seeding that raw prefix would give the child an open turn that the session constructor and the dev-mode [invariants](../../support/invariants) replay **reject**. @@ -29,6 +22,13 @@ The seam this rides on: `CreateAgentOptions.seed` (added on `dsh-agent`, threade See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, model inheritance, and depth tracking — all shared. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent history | The child receives the parent's balanced completed-turn surface prefix, then the new task, along with its own scoped persona, tool filter, and optional structured-output contract. The parent's current in-flight turn is excluded. | Forking duplicates the retained completed history into a separate child's requests; the child then accumulates its own tokens independently. A first-turn fork has no inherited history. | +| Parent tool result, indirectly | The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work. | Parent input grows by one data-dependent final result retained until compaction. | + ## Known Limitations and Deferred Work - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md index 4f8175cf2b..48bc34f22a 100644 --- a/packages/subagent/subagent-inprocess/README.md +++ b/packages/subagent/subagent-inprocess/README.md @@ -2,13 +2,6 @@ The shared **in-process subagent run driver**. A library with no provider or import-time registration that the in-process backends — [spawn](../subagent-spawn/README.md) (a fresh child) and [fork](../subagent-fork/README.md) (a child seeded with a prefix of the parent's log) — both build on. Each accepted run installs one provider-owned cleanup effect. The backends are thin shells that differ ONLY in the session seed they pass; everything downstream lives here, so neither backend depends on the other. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent request | The shared driver sends the task as the child's user message and composes per-child scoped persona and tool restrictions. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. Structured output adds fixed instruction and capability tokens only to that child for that run. | -| Parent result, indirectly | The driver extracts only the child's own last assistant output or captured structured value; seeded parent messages and intermediate child work do not become the result. | The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session. | - ## What it exports ### `startInProcessRun(ctx, request, options): SubagentRun` @@ -45,6 +38,13 @@ Delegation depth rides on a merge-extensible `AgentOptions.subagentDepth` field Thrown by `startInProcessRun` when a spawn would exceed the request's `maxDepth` cap; carries `attemptedDepth` and `maxDepth`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The shared driver sends the task as the child's user message and composes per-child scoped persona and tool restrictions. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. Structured output adds fixed instruction and capability tokens only to that child for that run. | +| Parent result, indirectly | The driver extracts only the child's own last assistant output or captured structured value; seeded parent messages and intermediate child work do not become the result. | The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session. | + ## Known Limitations and Deferred Work - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index db47d036e7..a67c611e47 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -4,13 +4,6 @@ The in-process **spawn** subagent backend: a [`SubagentProvider`](../subagent/RE The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../subagent-inprocess/README.md) driver (`startInProcessRun`); this backend just passes **no seed** (a fresh child). The [fork](../subagent-fork/README.md) backend is an independent peer over the same driver — neither knows about the other. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the globally composed prompt and tools after its scoped persona and tool filter. It receives zero parent conversation messages. | The child pays for a new independent context and history; no parent-history tokens are duplicated. | -| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error. | Parent input grows by one data-dependent result retained until compaction. | - ## What it does `start(request)` delegates to `startInProcessRun(ctx, request, {})` with no seed: a fresh child agent with the parent's `cwd`/`parentSession` lineage and (by default) the parent's model. The driver creates one run-owner fiber under `parent.ctx`; parent teardown, this provider's teardown, and manual disposal all converge there before child publication. Its `run.started` boundary resolves only after the fresh child is published, so `subagent/start` observers see a live registry entry. See the [driver README](../subagent-inprocess/README.md) for the full lifecycle (depth check, one-shot drive, result read, dispose). @@ -25,6 +18,13 @@ The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../ |---|---| | `providerName` | Registry name on `ctx.subagents` (default `spawn`). | +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the globally composed prompt and tools after its scoped persona and tool filter. It receives zero parent conversation messages. | The child pays for a new independent context and history; no parent-history tokens are duplicated. | +| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error. | Parent input grows by one data-dependent result retained until compaction. | + ## Known Limitations and Deferred Work - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. diff --git a/packages/subagent/subagent-subprocess/README.md b/packages/subagent/subagent-subprocess/README.md index 765ea4af92..b09df7b2aa 100644 --- a/packages/subagent/subagent-subprocess/README.md +++ b/packages/subagent/subagent-subprocess/README.md @@ -4,12 +4,6 @@ Shared machinery for **out-of-process subagent backends** — providers that spa Every tunable is a **parameter**: the dispose ladder takes its grace periods per call, the config-dir helper takes an optional pinned path. Defaults live in each consuming plugin's Config (defaulted, validated fields changeable from `cordis.yml`), never in this library. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This process utility registers no provider, prompt, tool, or message. A consuming backend's child application decides the child's model context; environment scrubbing and isolated config directories prevent ambient credentials and user state from silently changing that composition. | Zero direct tokens. It can indirectly stabilize child context, but it adds no text to parent or child requests. | - ## What it exports ### `SENSITIVE_ENV_PATTERN` / `buildChildEnv(extra)` @@ -45,6 +39,12 @@ A per-run isolated config directory for an external CLI child (the target of `CL `tests/subagent-subprocess.spec.ts`: the env scrub and config-dir helpers run against the real process env and real filesystem (the rm-failure path injects its rejection at the fs boundary — a real recursive-rm failure is not portably provokable, and root ignores permission bits); the exit waits and the dispose ladder run against a scriptable fake child, driving each escalation tier deterministically. The [ACP backend suite](../subagent-acp/README.md) exercises the same ladder against real subprocesses (EOF-cooperative, EOF-ignoring, and SIGTERM-trapping children) end to end. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This process utility registers no provider, prompt, tool, or message. A consuming backend's child application decides the child's model context; environment scrubbing and isolated config directories prevent ambient credentials and user state from silently changing that composition. | Zero direct tokens. It can indirectly stabilize child context, but it adds no text to parent or child requests. | + ## Known Limitations and Deferred Work - **The credential scrub is name-based** — only variables matching `KEY` / `SECRET` / `TOKEN` are removed; differently named secrets such as `PASSWORD` pass through unless the backend supplies a stricter environment. diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index 41aaf6afd6..73c4a727c6 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -14,12 +14,6 @@ This package is the interface third of the capability seam, split so each concer Unlike the bash seam (one executor per context, second load throws), **multiple providers coexist** here. Each registers under a unique name and a caller picks one by name — the shape mirrors the LLM adapter registry (`LlmService.registerAdapter`), not the single-service bash executor. This is the requirement that rules out the bash shape: an agent may want an in-process child for a cheap subtask and an out-of-process ACP child for an isolated one, in the same runtime. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The provider registry registers no prompt or tool. Provider lifecycle makes a bound `dsh-tool-subagent` schema appear or disappear, and `inheritsParentContext` selects truthful fresh-versus-fork wording. Run events are observe-only. | Zero direct tokens. Child prompts and final results enter model contexts only through a provider and consumer. | - ## Service API (`ctx.subagents`) | Member | Semantics | @@ -42,6 +36,12 @@ Beside `capabilities` sits one DESCRIPTIVE fact, not validated by the service: ` The service also announces provider lifecycle: `subagent/provider-added` (the frozen registry snapshot) fires after a registration and `subagent/provider-removed` (the accepted name) after an unregistration, so a consumer deriving state from a named provider (the model-facing tool wording) mirrors registry membership instead of assuming load order — the cordis Loader starts sibling plugins concurrently, so "listed earlier" does not mean "registered earlier". Run lifecycle is gated by provider readiness: `subagent/start` (payload `SubagentRunInfo`) fires only after `run.started` fulfills, and `subagent/end` (payload `SubagentRunEndInfo`) fires only for that announced run; readiness rejection emits neither. For spawn/fork, the start listener can resolve the published child via `ctx.agents.get(info.id)`; a remote provider need not have a local registry entry. Both events are **observe-only** plain emits. The service observes `result` immediately even while readiness is pending, clones its output before the caller can mutate it, and buffers that end payload until start has fired; a rejecting result cannot become an unhandled detached promise, start always precedes end, and a listener cannot corrupt the caller's result. `subagent/end` carries the cloned output as `lastAssistantMessage` on the settle path and omits it on infrastructure rejection. Any run-affecting decision is out of scope for this observe-only surface. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The provider registry registers no prompt or tool. Provider lifecycle makes a bound `dsh-tool-subagent` schema appear or disappear, and `inheritsParentContext` selects truthful fresh-versus-fork wording. Run events are observe-only. | Zero direct tokens. Child prompts and final results enter model contexts only through a provider and consumer. | + ## Known Limitations and Deferred Work - **The consumer collects synchronously** — it starts a run and awaits `result`; steering (`sendMessage`) is part of the contract but intentionally unused, and background / poll / spill semantics are deferred to a future redesign unifying long-running-tool handling across subagents and bash ([the seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index 06a8c970c8..c9befb38ae 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -2,13 +2,6 @@ The model-facing `subagent` tool: delegate a self-contained task to a child agent and return its final output. Pure schema + lifecycle shaping over the [`ctx.subagents`](../subagent/README.md) provider registry — an in-process, ACP, or future A2A backend swaps in without changing what the model sees. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | -| Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. The result contains only the child's final text or a stop-reason error, never intermediate child steps. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | - ## Provider selection is config, not model-facing This plugin binds to **exactly one** provider (`Config.provider`). The model sees only `{ description, prompt }` — there is no provider/type parameter in the schema. To expose more than one transport, load the plugin more than once, each bound to a different provider **and a distinct `toolName`** (the tool registry rejects a duplicate name, so a second load that kept the default `subagent` name would throw). Keeping selection in config (not the schema) is the deliberate split: the *service* holds a multi-provider registry; the *tool* picks one. @@ -32,6 +25,13 @@ The tool description and the `prompt` parameter description are DERIVED from the Background / poll collection is deferred (see the [RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)); this cut blocks the parent turn until the child finishes. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | +| Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. The result contains only the child's final text or a stop-reason error, never intermediate child steps. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | + ## Known Limitations and Deferred Work - **Delegation blocks the parent turn** — synchronous collect only; background start + poll collection is deferred to the long-running-runtime redesign. diff --git a/packages/support/invariants/README.md b/packages/support/invariants/README.md index e7d8e37b0f..5d44b42cac 100644 --- a/packages/support/invariants/README.md +++ b/packages/support/invariants/README.md @@ -4,12 +4,6 @@ Dev-mode event-contract invariants and session-log freeze. A pure-listener plugi **Off in production.** Enable it in tests and the demos, where a contract violation should fail loudly. It costs nothing when not registered, and doubles as executable documentation of the event taxonomy — the assertions *are* the contract. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None | The plugin observes and validates session events, agent states, and frozen model requests; it does not rewrite a prompt, schema, message, or stream. An invariant failure aborts the faulty turn instead of adding guidance. | Zero model tokens when checks pass; a failure prevents or ends a request rather than contributing context. | - ## Plugin A functional plugin — register the module namespace (this is what loading by name in `cordis.yml` does): @@ -60,6 +54,12 @@ A `DeepReadonly` is high type-noise across every log consumer, and A seeded/forked session arrives with events already in its log (the `Session` constructor copies the seed without emitting `session/event`). On `session/created` the plugin replays the existing log through the checker and freezes those entries, so seeded history is held to the same contract. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None | The plugin observes and validates session events, agent states, and frozen model requests; it does not rewrite a prompt, schema, message, or stream. An invariant failure aborts the faulty turn instead of adding guidance. | Zero model tokens when checks pass; a failure prevents or ends a request rather than contributing context. | + ## Known Limitations and Deferred Work - **The request-reconstructability assertion covers loop-built requests only** — hand-built one-shots (e.g. compaction's summarize call) carry no live `sessionId` marker and are skipped. diff --git a/packages/support/llm-replay/README.md b/packages/support/llm-replay/README.md index 39794ca8f9..57badc9452 100644 --- a/packages/support/llm-replay/README.md +++ b/packages/support/llm-replay/README.md @@ -4,12 +4,6 @@ A replay LLM plugin for keyless snapshot tests. It installs a single `llm/stream Its consumer is the ACP snapshot harness in `examples/acp-agent`, which loads this plugin (via `cordis.snapshot.yml`) in place of a real LLM adapter. The package exists so its derive/parse/replay logic falls under the per-file 100% coverage gate on `packages/*/src` (the same logic, while it lived under `examples/`, was outside the gate). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Keyless test stream | The real loop still assembles its normal system prompt, tools, prefix, and history, but no provider model receives them. Recorded assistant chunks are replayed as the response and then enter later history exactly like live output. | Zero billed or tokenizer-evaluated model tokens. Fixture output creates deterministic retained test context for later replay steps. | - ## How the fixture works The fixture IS the persisted session log (`/session.jsonl`). Its `assistant/chunk` events carry every `StreamChunk`, so grouping them by `(turn, step)` reconstructs each `stream()` call's chunk sequence (one model call per loop step). Recording is therefore "run the real agent once and harvest the `.jsonl`", done by the snapshot harness — this plugin does not record. A fixture may carry its `request/header` content tokenized to `{{system}}`/`{{tools}}` (the harness pins that content in one scenario and scrubs the rest); replay is indifferent — derivation reads only `assistant/chunk` events and the line-0 session header. @@ -50,6 +44,12 @@ Replay keys every call by its calling session id (`GenerateOptions.sessionId`, s Named `name` / `inject` / `Config` / `apply`, with **no default export**: the cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray default would collapse the module to the bare function and drop the `inject` namespace (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Keyless test stream | The real loop still assembles its normal system prompt, tools, prefix, and history, but no provider model receives them. Recorded assistant chunks are replayed as the response and then enter later history exactly like live output. | Zero billed or tokenizer-evaluated model tokens. Fixture output creates deterministic retained test context for later replay steps. | + ## Known Limitations and Deferred Work - **First-call-order script binding assumes sequential delegation** — a cut that runs sibling subagents concurrently (or a compaction summarize call landing mid-run) would bind live sessions to recorded scripts non-deterministically; a stronger keying is deferred until such a scenario exists (`XXX(concurrent-subagents)`). diff --git a/packages/support/subagent-mock/README.md b/packages/support/subagent-mock/README.md index b99eee9aea..b263b3fe7b 100644 --- a/packages/support/subagent-mock/README.md +++ b/packages/support/subagent-mock/README.md @@ -4,12 +4,6 @@ A scripted `SubagentProvider` for testing the [subagent seam](../../subagent/sub It lets a test drive `ctx.subagents` and the model-facing `dsh-tool-subagent` through the **real cordis Loader / export path**, exercising provider registration, start-time capability validation, the run lifecycle (`result` / `cancel` / `dispose`), and the structured-output branch — all deterministically and keylessly. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Deterministic test result | No child model runs. When exercised through `dsh-tool-subagent`, the parent sees the mock provider's configured reply or stop-reason error, and structured tests receive the configured object. | Zero child-model tokens. Only the scripted final result is added to the parent test history. | - ## Usage Load it as a plugin (functional shape: `name`/`inject`/`Config`/`apply`, no default). Config (all optional): @@ -25,6 +19,12 @@ Load it as a plugin (functional shape: `name`/`inject`/`Config`/`apply`, no defa A `cancel()` issued before `result` settles flips the stop reason to `aborted`, so the cancellation path is observable. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Deterministic test result | No child model runs. When exercised through `dsh-tool-subagent`, the parent sees the mock provider's configured reply or stop-reason error, and structured tests receive the configured object. | Zero child-model tokens. Only the scripted final result is added to the parent test history. | + ## Known Limitations and Deferred Work - **Scripted provider only** — it does not run a model, create a child agent, or exercise real prompt/tool-loop behavior. diff --git a/packages/timeout/timeout-policy/README.md b/packages/timeout/timeout-policy/README.md index 27c7ab94ad..76f153320f 100644 --- a/packages/timeout/timeout-policy/README.md +++ b/packages/timeout/timeout-policy/README.md @@ -2,12 +2,6 @@ Tool-call timeout enforcer: a single `tools/execute` around-dispatch listener that arms a per-call cooperative deadline on `exec.signal` for a tool declaring `timeoutMs` on its `ToolDefinition` and returns a structured `TOOL_TIMEOUT` result when that deadline wins. The budget is read from the tool's own declaration (`ToolDefinition.timeoutMs`, set by the owning tool plugin), so this plugin is **zero-config**. It is the reference `tools/execute` wrapper and the enforcement home for model-facing tool-call budgets (the timeout-library RFC's foreseen middleware). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Conditional tool result | This plugin adds no prompt or schema. If a declared deadline wins, it replaces the provider's outcome with `Error: tool call timed out after ms` plus structured `TOOL_TIMEOUT`; otherwise the original result passes through unchanged. | Zero tokens on non-timeout calls. A timeout adds one small retained error result and can prevent a larger late provider result from entering context. | - ## Plugin (namespace: `timeout-policy`) A function/namespace plugin (`name` / `inject` / `apply`), not a service. It registers no tool and takes no config — it consumes `ctx.tools`'s `tools/execute` waterfall (which the `dsh-tools` registry always provides) and reads each dispatched tool's declared `timeoutMs` from the registry (`ctx.tools.get(exec.name)`). @@ -39,6 +33,12 @@ The derived signal only **notifies**; termination stays with the tool and the ca Multiple `tools/execute` listeners compose by cordis registration order. Combined with a future retry/sandbox/metrics wrapper, registration order chooses the semantics — "timeout covers the whole retry operation" (timeout registered outer) versus "timeout covers each attempt" (timeout registered inner). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conditional tool result | This plugin adds no prompt or schema. If a declared deadline wins, it replaces the provider's outcome with `Error: tool call timed out after ms` plus structured `TOOL_TIMEOUT`; otherwise the original result passes through unchanged. | Zero tokens on non-timeout calls. A timeout adds one small retained error result and can prevent a larger late provider result from entering context. | + ## Known Limitations and Deferred Work - **Cooperative, never a hard kill** — the deadline only notifies via `exec.signal`; a tool that ignores the signal does not stop on timeout (see § Cooperative, not a hard kill). diff --git a/packages/todo/tool-todo/README.md b/packages/todo/tool-todo/README.md index 2ba027120d..32bb39c860 100644 --- a/packages/todo/tool-todo/README.md +++ b/packages/todo/tool-todo/README.md @@ -2,13 +2,6 @@ The model-facing `todo_write` tool: the agent's whole task list, replaced wholesale on each call. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schema | The model sees `todo_write` with the complete-list array and the three status values. | Fixed schema cost on every request where the tool is visible. | -| Tool-call history and result | Each assistant tool call retains the entire replacement list in its arguments. The tool result reports only pending, in-progress, and completed counts; the full `todo/write` session event is UI and replay state, not a second model message. | Token growth scales with every full list the model submits, and those call arguments remain until compaction. The result itself is small and fixed-shape. | - ## What it does Registers one tool, `todo_write(todos: [{ content, status }])`, on `ctx.tools`. The model sends the ENTIRE list every call — there are no partial updates or per-item edits. Each call appends a `todo/write` event (the full list snapshot) to the calling agent's session log via `agent.session.append('todo/write', { todos })`; the current list is the most recent such event (last-write-wins on replay). @@ -31,6 +24,13 @@ The tool writes only the session event; it does not render. UIs subscribe to `se A function/namespace plugin: it exports `name` / `inject` / `apply` and NO default. A stray `export default` would collapse the module via the Loader's `unwrapExports` and drop `inject` (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | The model sees `todo_write` with the complete-list array and the three status values. | Fixed schema cost on every request where the tool is visible. | +| Tool-call history and result | Each assistant tool call retains the entire replacement list in its arguments. The tool result reports only pending, in-progress, and completed counts; the full `todo/write` session event is UI and replay state, not a second model message. | Token growth scales with every full list the model submits, and those call arguments remain until compaction. The result itself is small and fixed-shape. | + ## Known Limitations and Deferred Work - **Single-owner scope only** — the list belongs to the one calling agent session; subagent/shared/swarm scopes are a deliberate cut (see § Single owner), and a non-agent caller is rejected. diff --git a/packages/ui/acp-agent/README.md b/packages/ui/acp-agent/README.md index a1c33de460..00cfc9dccf 100644 --- a/packages/ui/acp-agent/README.md +++ b/packages/ui/acp-agent/README.md @@ -4,12 +4,6 @@ The **ACP server app**: a Cordis app plugin that composes the default agent spin It is the structured counterpart to [`@deepseek-ai/dsh-stdio-agent`](../stdio-agent/README.md): both consume the same spine, but this one bakes in the OPPOSITE front-door cluster. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Composed ACP agent request | Through `dsh-agent-core`, an ACP-created agent receives the harness identity, configured persona, skill catalog, visible tools, and its own ACP prompt history. This app adds no extra prompt prose and omits `ask_user_question` unless a leaf opts in. | Per-request cost is the sum of the composed child packages. ACP framing, JSON-RPC, persistence, and UI rendering add zero model tokens. | - ## What it bakes in — and what it deliberately omits stdout is the ACP JSON-RPC channel, so the cluster is defined as much by what it LEAVES OUT as what it includes: @@ -52,6 +46,12 @@ Run it under `node --expose-internals`: the cordis Loader resolves the config's All diagnostics go to **stderr** — stdout is the protocol. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed ACP agent request | Through `dsh-agent-core`, an ACP-created agent receives the harness identity, configured persona, skill catalog, visible tools, and its own ACP prompt history. This app adds no extra prompt prose and omits `ask_user_question` unless a leaf opts in. | Per-request cost is the sum of the composed child packages. ACP framing, JSON-RPC, persistence, and UI rendering add zero model tokens. | + ## Known Limitations and Deferred Work - **JSONL persistence is baked in** — config chooses its root but cannot select a different backend; that requires a sibling entry or differently composed app package. diff --git a/packages/ui/acp/README.md b/packages/ui/acp/README.md index 80769ef793..60e10d1733 100644 --- a/packages/ui/acp/README.md +++ b/packages/ui/acp/README.md @@ -4,14 +4,6 @@ The **Agent Client Protocol (ACP)** bridge: exposes DeepSeek Harness SDK agents It is a **client-driver / UI plugin**, the structured analogue of the readline `stdio-chat` plugin — NOT a loop change and NOT a [capability seam](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md). It consumes the existing `agent/*` event taxonomy, the `dsh-agent` create/resume factory, and `dsh-session-persistence`. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| User messages | Each ACP `session/prompt` becomes an agent user message: text passes through and a `resource_link` is rendered as text. Unsupported image, audio, and embedded-resource blocks are rejected rather than silently omitted. | Prompt tokens are data-dependent and remain in that session's history until compaction. Concurrent ACP sessions keep separate contexts. | -| Human answers and permissions | When optional consumers are loaded, ACP form answers become `ask_user_question` tool results and permission decisions control whether a tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only. | Answer and denial text enters context only through the owning tool result; presentation metadata adds zero model tokens. | -| Loaded sessions | `session/load` resumes the persisted log, after which the loop sends its reconstructed history and request header. Replaying that log to the editor is not an extra model message. | Restored context has the persistence and session packages' normal retained cost; ACP replay to the client adds none. | - ## Service / plugin `apply(ctx, config)` — wires an `AgentSideConnection` (from `@agentclientprotocol/sdk`) to `process.stdin`/`process.stdout` and implements the ACP `Agent` method surface. @@ -89,14 +81,6 @@ The bridge registers an `approval/request` waterfall listener — the ACP answer Teardown reaches quiescence: for EVERY live session settle any pending prompt as `cancelled`, then run that session's [`AgentHandle`](../../core/agent/README.md) `dispose()` — which stops the loop (sets `disposed` + aborts the in-flight step), `await`s the loop's exit (the final `turn/end` + `session/flush` are captured while the session is still attached), unregisters the agent, and removes its session from the store. A turn cut off mid-flight by teardown ends with reason `disposed` (not `aborted` — `dispose()` uses the disposed path, not `session/cancel`'s queue-aware `cancel()`). The per-session disposes run in parallel. The same teardown runs on a **client disconnect** (`conn.closed` resolves when the editor quits / the transport EOFs), so a vanished client never leaves an orphaned running — or idled-but-still-registered — agent whose `session/update` writes are silently swallowed. The two paths are idempotent and memoized (the first clears the `sessions` map; a second caller awaits the same teardown promise). -## Known Limitations and Deferred Work - -- **`additionalDirectories`** — rejected. A session operates in its single `cwd` (see Per-session cwd); widening the tool/filesystem scope to extra roots is a separate sandbox concern, not yet implemented. -- **Prompt content is `text` + `resource_link` only** — image, audio, and embedded-resource blocks are rejected, as is a non-empty `mcpServers` list at `session/new`. -- **One configured `model` for every created session** — per-session model selection has no config or protocol surface here yet. -- **Terminal cards render completed output** — live incremental streaming and command classification are named follow-ups of [the terminal-rendering RFC](../../../docs/rfc/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md). -- **Permission answers are one-shot only** — the bridge offers `allow_once` / `reject_once`; durable `allow_always` grants and their storage/revocation policy remain deferred to the approval seam. - ## stdout is the protocol The JSON-RPC frames go on stdout, so this plugin MUST run in an example that loads **no stdout logger** (the console logger writes to stdout and would corrupt the frames). The guarantee is config-only — see `examples/acp-agent` (no console logger) and [ACP support risks](../../../docs/rfc/implemented/feature/2026-06-14-acp-agent-client-protocol.md#risks). A stderr exporter is fine for logging. @@ -115,3 +99,19 @@ The JSON-RPC frames go on stdout, so this plugin MUST run in an example that loa } } ``` + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| User messages | Each ACP `session/prompt` becomes an agent user message: text passes through and a `resource_link` is rendered as text. Unsupported image, audio, and embedded-resource blocks are rejected rather than silently omitted. | Prompt tokens are data-dependent and remain in that session's history until compaction. Concurrent ACP sessions keep separate contexts. | +| Human answers and permissions | When optional consumers are loaded, ACP form answers become `ask_user_question` tool results and permission decisions control whether a tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only. | Answer and denial text enters context only through the owning tool result; presentation metadata adds zero model tokens. | +| Loaded sessions | `session/load` resumes the persisted log, after which the loop sends its reconstructed history and request header. Replaying that log to the editor is not an extra model message. | Restored context has the persistence and session packages' normal retained cost; ACP replay to the client adds none. | + +## Known Limitations and Deferred Work + +- **`additionalDirectories`** — rejected. A session operates in its single `cwd` (see Per-session cwd); widening the tool/filesystem scope to extra roots is a separate sandbox concern, not yet implemented. +- **Prompt content is `text` + `resource_link` only** — image, audio, and embedded-resource blocks are rejected, as is a non-empty `mcpServers` list at `session/new`. +- **One configured `model` for every created session** — per-session model selection has no config or protocol surface here yet. +- **Terminal cards render completed output** — live incremental streaming and command classification are named follow-ups of [the terminal-rendering RFC](../../../docs/rfc/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md). +- **Permission answers are one-shot only** — the bridge offers `allow_once` / `reject_once`; durable `allow_always` grants and their storage/revocation policy remain deferred to the approval seam. diff --git a/packages/ui/stdio-agent/README.md b/packages/ui/stdio-agent/README.md index 9f57f5d4cc..38254f5f5d 100644 --- a/packages/ui/stdio-agent/README.md +++ b/packages/ui/stdio-agent/README.md @@ -4,12 +4,6 @@ The **terminal stdio chat app**: a Cordis app plugin that composes the default a It is the readline counterpart to [`@deepseek-ai/dsh-acp-agent`](../acp-agent/README.md): both consume the same spine, but each bakes in the OPPOSITE front-door cluster. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Composed terminal agent request | Through `dsh-agent-core`, the `main` agent receives the harness identity, configured persona, skill catalog, and visible tools; this app also composes the `ask_user_question` schema. Each readline submission becomes a user message. | Child prompt and schema costs repeat per request; user input and tool history grow until compaction. The welcome banner, logger output, and rendered transcript are terminal-only and add zero model tokens. | - ## What it bakes in A terminal chat always wants the same cluster, so the package owns it rather than trusting each leaf to re-wire it: @@ -72,6 +66,12 @@ Fresh stdio sessions use the process launch directory as `session.header.cwd`, s Swap `llm-deepseek` for a `mock-llm` leaf plugin and you have the echo demo — "swap the backend, keep the app". +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed terminal agent request | Through `dsh-agent-core`, the `main` agent receives the harness identity, configured persona, skill catalog, and visible tools; this app also composes the `ask_user_question` schema. Each readline submission becomes a user message. | Child prompt and schema costs repeat per request; user input and tool history grow until compaction. The welcome banner, logger output, and rendered transcript are terminal-only and add zero model tokens. | + ## Known Limitations and Deferred Work - **One pre-created `main` agent drives the readline UI** — there is no multi-session or concurrent-agent surface in this app; a run is one conversation. diff --git a/packages/ui/tool-ask-user/README.md b/packages/ui/tool-ask-user/README.md index 851b9f5864..c41e43d920 100644 --- a/packages/ui/tool-ask-user/README.md +++ b/packages/ui/tool-ask-user/README.md @@ -2,13 +2,6 @@ Model-facing `ask_user_question` tool over `ctx.userInteraction`. It lets the model ask the human a concise question when it needs confirmation, a choice, or missing information before continuing. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schema | The model sees `ask_user_question` with question ids, prompts, headings, options, and multi-select flags. | Fixed schema cost on every request where the tool is visible. | -| Tool-call history and result | The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees JSON containing selected labels and optional custom text. UI interaction while the call is pending is not model context. | Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human. | - ## Tool `ask_user_question` accepts: @@ -26,6 +19,13 @@ The tool calls `ctx.userInteraction.ask()` and returns JSON text shaped as `{ "a This is the consumer package for the user-interaction seam. It does not render UI and does not know how input is collected; it only translates model arguments into `AskUserQuestionRequest` and returns the human answer to the agent loop. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | The model sees `ask_user_question` with question ids, prompts, headings, options, and multi-select flags. | Fixed schema cost on every request where the tool is visible. | +| Tool-call history and result | The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees JSON containing selected labels and optional custom text. UI interaction while the call is pending is not model context. | Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human. | + ## Known Limitations and Deferred Work - **A pending question blocks the tool call until the human answers** — the tool declares no `timeout-policy` budget; cancellation rides the turn's `exec.signal` only. diff --git a/packages/ui/user-interaction/README.md b/packages/ui/user-interaction/README.md index 4261cef246..378e7a626f 100644 --- a/packages/ui/user-interaction/README.md +++ b/packages/ui/user-interaction/README.md @@ -2,12 +2,6 @@ Abstract user-interaction seam. It owns `ctx.userInteraction`, the service a model-facing tool or permission plugin uses when it needs to pause work and ask the human for a decision. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This UI-neutral seam registers no prompt or tool. A consumer such as `dsh-tool-ask-user` turns a model call into an `ask()` request and converts the provider's human answer into a model-visible tool result. | Zero direct tokens. Question and answer size affect context only through the consumer. | - ## Service: `UserInteractionService` (ctx key: `userInteraction`) ### Public API @@ -29,6 +23,12 @@ When an answer includes `custom`, `selected` is empty; custom text is an overrid This is the interface package. Model-facing consumers such as `@deepseek-ai/dsh-tool-ask-user` depend on this seam; UI front doors such as the `stdio-agent` readline module and the `acp` bridge provide the provider. The loop stays unchanged: a tool call simply awaits a promise, and the tool result resumes the normal agent loop. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This UI-neutral seam registers no prompt or tool. A consumer such as `dsh-tool-ask-user` turns a model call into an `ask()` request and converts the provider's human answer into a model-visible tool result. | Zero direct tokens. Question and answer size affect context only through the consumer. | + ## Known Limitations and Deferred Work - **One provider per context** — there is no routing or fan-out to multiple UIs; a second registration throws `DUPLICATE_PROVIDER`, and with none registered `ask()` throws `NO_PROVIDER` rather than degrading. diff --git a/packages/util/brand/README.md b/packages/util/brand/README.md index 22c9c47bb3..5c7fa07272 100644 --- a/packages/util/brand/README.md +++ b/packages/util/brand/README.md @@ -2,12 +2,6 @@ The `Branded` nominal-typing primitive — a tiny, **type-only** package (no runtime code, no harness-package dependency) shared by every package that owns a cross-boundary id. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None | `Branded` is erased at compile time and registers no runtime plugin, prompt, schema, event, or message. Branded ids serialize exactly as their underlying strings when another package logs or renders them. | Zero direct or indirect token overhead beyond the string another package already chose to expose. | - ## What `Branded` is A brand makes structurally-identical strings non-interchangeable at the type level: an `AgentId` cannot be passed where a `CallId` is expected, even though both are plain `string`s at runtime. @@ -30,3 +24,9 @@ Construction goes through the per-id factory in the OWNING package (a plain cast A package brands the ids it OWNS — `CallId` in `dsh-llm` (tool-call correlation), `SessionId` in `dsh-session`, `AgentId` in `dsh-agent`, `BashTaskId`/`OwnerToken` in `dsh-bash`. Branding is for ids that cross package boundaries and could plausibly be confused; **not every string needs a brand.** This package owns ONLY the primitive — no concrete id, no runtime code beyond the (erased) type. Keeping the primitive dependency-free is the point: a capability package can brand its ids without depending on an unrelated package. `dsh-bash`, for example, brands `BashTaskId`/`OwnerToken` by depending on `dsh-brand` alone — it never pulls in `dsh-llm` (or `dsh-session`) just to reach `Branded`. + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None | `Branded` is erased at compile time and registers no runtime plugin, prompt, schema, event, or message. Branded ids serialize exactly as their underlying strings when another package logs or renders them. | Zero direct or indirect token overhead beyond the string another package already chose to expose. | diff --git a/packages/util/timeout/README.md b/packages/util/timeout/README.md index 4ae6aefbe9..86c76ca5b4 100644 --- a/packages/util/timeout/README.md +++ b/packages/util/timeout/README.md @@ -6,12 +6,6 @@ It owns **no termination**. The signal it hands out only *notifies*; actually st It is a **library, not a service or plugin**: no `ctx`, registers nothing, holds no state, emits no events. A "timeout service" would have to understand how to stop every capability's work — exactly the knowledge a microkernel keeps out of shared layers. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This library only creates and classifies abort signals. It registers no prompt, schema, or message; consumers decide whether a timeout becomes a marker, a structured error, or no model-visible change. | Zero direct tokens. It can indirectly cap or replace a consumer's result when that consumer renders a timeout. | - ## Surface ```ts @@ -47,6 +41,12 @@ Pass your own `code` to `timeoutOf` so classification composes under nesting: wh Local file `read`/`write`/`edit` take no `timeoutMs`: a syscall is best-effort-abortable at most, a timeout could not force `fsync`/`rename` to stop, and adding one would be an implicit default that violates explicit-over-implicit. See [`fs/`](../../fs/README.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This library only creates and classifies abort signals. It registers no prompt, schema, or message; consumers decide whether a timeout becomes a marker, a structured error, or no model-visible change. | Zero direct tokens. It can indirectly cap or replace a consumer's result when that consumer renders a timeout. | + ## Known Limitations and Deferred Work - **Notification only** — a deadline cannot stop work that ignores its signal; every capability still needs its own socket/process/task termination path. diff --git a/packages/web/tool-web/README.md b/packages/web/tool-web/README.md index 4d0a53e95a..9b821aaf82 100644 --- a/packages/web/tool-web/README.md +++ b/packages/web/tool-web/README.md @@ -4,14 +4,6 @@ The model-facing web tool suite — `web_search` and `web_fetch` — over the [w Each tool is registered independently; a product that wants only one disables the other via config (`{ search: false }` / `{ fetch: false }`). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt | Each enabled tool adds one short section: search guidance says to discover current sources and follow with fetch; fetch guidance says to retrieve a specific HTTP(S) URL and cite it. | Fixed guidance cost per request for each enabled tool. | -| Tool schemas | According to config, the model sees `web_search(query)`, `web_fetch(url)`, or both. Result-count and timeout budgets are deployment settings, not model arguments. | Fixed schema cost per request; disabling a tool removes its schema and guidance. | -| Tool-call history and results | Search returns an optional answer and bounded source entries; fetch returns status plus decoded text or markdown-shaped HTML, or a structured error. Queries and URLs remain in call history. | Data-dependent results are resent until compaction. Search sources are capped by `searchMaxResults`; fetch providers cap body size, and timeout policy can replace a late result with a short error. | - ## Tools | Tool | Args | Behavior | @@ -42,6 +34,14 @@ Tool registration follows product **enablement**, not backend availability. A to The tool never calls a provider's `status()` and never enumerates providers — its only execution path is `ctx.web.search()` / `ctx.web.fetch()`, and provider unavailability reaches it as the structured `WebError` codes selection throws at execution time. Provider selection stays entirely inside the seam, with one owner. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Each enabled tool adds one short section: search guidance says to discover current sources and follow with fetch; fetch guidance says to retrieve a specific HTTP(S) URL and cite it. | Fixed guidance cost per request for each enabled tool. | +| Tool schemas | According to config, the model sees `web_search(query)`, `web_fetch(url)`, or both. Result-count and timeout budgets are deployment settings, not model arguments. | Fixed schema cost per request; disabling a tool removes its schema and guidance. | +| Tool-call history and results | Search returns an optional answer and bounded source entries; fetch returns status plus decoded text or markdown-shaped HTML, or a structured error. Queries and URLs remain in call history. | Data-dependent results are resent until compaction. Search sources are capped by `searchMaxResults`; fetch providers cap body size, and timeout policy can replace a late result with a short error. | + ## Known Limitations and Deferred Work - **`htmlToMarkdown` is a minimal regex converter, not an HTML parser** — it strips script/style/noscript, keeps headings/bullets/links, and decodes about a dozen named entities; tables, images, and nested formatting are lost. diff --git a/packages/web/web-fetch-local/README.md b/packages/web/web-fetch-local/README.md index ea0141d69a..7602fd6548 100644 --- a/packages/web/web-fetch-local/README.md +++ b/packages/web/web-fetch-local/README.md @@ -4,12 +4,6 @@ An anonymous public HTTP(S) `WebFetchProvider` for the harness [web capability s This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. It is a function/namespace plugin (`inject: ['web']`). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Web fetch result, indirectly | Through `dsh-tool-web`, the conversation model sees the final URL, HTTP status, and decoded text or markdown-shaped HTML, or a structured retrieval error. Redirects, headers, and transport mechanics are not added to context unless reflected in an error. | Zero direct tokens. `maxBodyChars` bounds decoded result length before the tool records it; the retained result is resent until compaction. | - ## Responsibility split The provider owns **safe resource retrieval**: URL validation, HTTP transport, redirect policy, a resource-backstop timeout, abort propagation, byte caps, charset decoding, content-type classification, and binary rejection. `@deepseek-ai/dsh-tool-web` owns **presentation** (HTML→markdown, truncation formatting). A non-2xx HTTP response is a *result* (status code + decoded body), not an error; `WebError` is reserved for failures to safely retrieve or represent the resource. @@ -39,6 +33,12 @@ The provider's `timeoutMs`/`maxTimeoutMs` is a **resource backstop** for direct The numeric limits are validated at plugin construction: every cap except `maxRedirects` must be a positive finite number, and `maxRedirects` must be a non-negative integer. An invalid value throws rather than silently constructing a provider with nonsensical limits. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Web fetch result, indirectly | Through `dsh-tool-web`, the conversation model sees the final URL, HTTP status, and decoded text or markdown-shaped HTML, or a structured retrieval error. Redirects, headers, and transport mechanics are not added to context unless reflected in an error. | Zero direct tokens. `maxBodyChars` bounds decoded result length before the tool records it; the retained result is resent until compaction. | + ## Known Limitations and Deferred Work - **SSRF / private-network protection is deferred** — no blocking of private, loopback, link-local, multicast, or otherwise non-public destinations, no DNS-resolve-then-validate, no per-hop re-validation (see [the web capability seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). Until it lands, this provider is an SSRF primitive and **must not be enabled** in a deployment that can reach sensitive internal network targets. diff --git a/packages/web/web-search-deepseek/README.md b/packages/web/web-search-deepseek/README.md index ae83c4eeb4..cc0c95eb75 100644 --- a/packages/web/web-search-deepseek/README.md +++ b/packages/web/web-search-deepseek/README.md @@ -4,13 +4,6 @@ A [DeepSeek](https://deepseek.com)-backed `WebSearchProvider` for the harness [w This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`). The Anthropic wire shape is a provider-private detail — it does **not** make this provider depend on `ctx.llm`. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Auxiliary DeepSeek search request | A separate DeepSeek model receives the search query and native `web_search` server-tool definition. This request is not part of the conversation model's context. | Separate provider input and output tokens are incurred for each search; `maxTokens` caps generated output and `maxUses` caps native search uses. | -| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees deduplicated URLs, titles, dates, and citation snippets from structured search blocks; provider prose is not trusted as an answer. | Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the seam enforces the requested source bound. | - ## How it differs from a dedicated search endpoint Exa and Perplexity expose dedicated search endpoints; DeepSeek does not. Instead this provider issues a **full Messages model call** carrying the `web_search` server tool, so one search costs a complete model turn in latency and tokens — heavier than a pure retrieval endpoint. DeepSeek runs the search server-side and returns **structured** `web_search_tool_result` blocks; the provider parses those blocks and **never scrapes URLs out of model prose**. @@ -42,6 +35,13 @@ It reuses `$DEEPSEEK_API_KEY` (no new secret) but **not** `$DEEPSEEK_BASE_URL`: DeepSeek returns no provider-generated answer surface this provider trusts as `content`, so `content` is omitted. `sources[]` is built from the `web_search_result` items inside `web_search_tool_result` blocks: `url` ← `url`, `title` ← `title`, `publishedAt` ← `page_age`. The per-source `snippet` lives separately in a `text` block's `citations[]` (a `cited_text` keyed by `url`), so the provider joins the two — a result with no citation excerpt simply has no `snippet`. Results are deduped by `url` (a `maxUses > 1` request can surface the same URL across searches). DeepSeek's `web_search` has no result-count knob (only `maxUses`), so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`). Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Auxiliary DeepSeek search request | A separate DeepSeek model receives the search query and native `web_search` server-tool definition. This request is not part of the conversation model's context. | Separate provider input and output tokens are incurred for each search; `maxTokens` caps generated output and `maxUses` caps native search uses. | +| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees deduplicated URLs, titles, dates, and citation snippets from structured search blocks; provider prose is not trusted as an answer. | Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the seam enforces the requested source bound. | + ## Known Limitations and Deferred Work - **One search costs a full Messages model turn** — latency plus generated tokens, with up to `maxUses` server-side searches; DeepSeek exposes no dedicated retrieval endpoint. diff --git a/packages/web/web-search-exa/README.md b/packages/web/web-search-exa/README.md index e484acf936..388eb7c370 100644 --- a/packages/web/web-search-exa/README.md +++ b/packages/web/web-search-exa/README.md @@ -4,12 +4,6 @@ An [Exa](https://exa.ai)-backed `WebSearchProvider` for the harness [web capabil This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the `ctx.web` key and it does not register a model-facing tool (that is `@deepseek-ai/dsh-tool-web`). Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`) that registers its backend, not a default-export service. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Web search result, indirectly | Through `dsh-tool-web`, the conversation model sees Exa result URLs, titles, first highlight snippets, and publication dates. No generated answer or provider-private response fields enter the tool result. | Zero direct harness-model tokens. Result size scales with the bounded source list and snippets; the seam enforces `maxResults`, and retained results remain until compaction. | - ## Config | Key | Default | Meaning | @@ -31,6 +25,12 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i Exa returns a flat `results[]` and no generated answer, so `content` is omitted. Each result maps to a `WebSearchSource`: `url` ← `url`, `title` ← `title`, `snippet` ← the first non-empty `highlights[]` entry (a result with no highlight has no portable snippet and is dropped), `publishedAt` ← `publishedDate`. A request's `maxResults` wins over the configured `numResults` default and is sent as Exa's `numResults` for a cost/latency optimization; the final bound is enforced by the seam. Provider failures (HTTP errors, network failure, unparseable or wrong-shape bodies) surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Web search result, indirectly | Through `dsh-tool-web`, the conversation model sees Exa result URLs, titles, first highlight snippets, and publication dates. No generated answer or provider-private response fields enter the tool result. | Zero direct harness-model tokens. Result size scales with the bounded source list and snippets; the seam enforces `maxResults`, and retained results remain until compaction. | + ## Known Limitations and Deferred Work - **A result with no non-blank highlight is dropped entirely** — no portable snippet to map, so fewer sources than the requested count can return. diff --git a/packages/web/web-search-perplexity/README.md b/packages/web/web-search-perplexity/README.md index 2c331ce81c..faa33d432f 100644 --- a/packages/web/web-search-perplexity/README.md +++ b/packages/web/web-search-perplexity/README.md @@ -4,13 +4,6 @@ A [Perplexity](https://perplexity.ai)-backed `WebSearchProvider` for the harness This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`). The OpenAI-compatible wire shape is a provider-private detail — it does **not** make this provider depend on `ctx.llm`. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Auxiliary Perplexity request | A separate Perplexity model receives the search query through its chat-completions endpoint. This request is not part of the conversation model's context. | Separate provider tokens are incurred per search; `maxTokens` caps the generated answer. | -| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees the generated answer plus structured result metadata or URL-only citations. | Zero direct conversation tokens from registration. Answer and source tokens are data-dependent, source count is seam-bounded, and the retained result is resent until compaction. | - ## Config | Key | Default | Meaning | @@ -32,6 +25,13 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i `content` ← `choices[0].message.content` (the generated answer). `sources[]` prefers the structured `search_results[]` (`url`, `title`, `snippet`, `publishedAt` ← `date`), falling back to the URL-only `citations[]` array only when `search_results` is absent — those sources carry just a `url`, which is why `title`/`snippet`/`publishedAt` are optional on the seam. Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. Perplexity has no result-count control, so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Auxiliary Perplexity request | A separate Perplexity model receives the search query through its chat-completions endpoint. This request is not part of the conversation model's context. | Separate provider tokens are incurred per search; `maxTokens` caps the generated answer. | +| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees the generated answer plus structured result metadata or URL-only citations. | Zero direct conversation tokens from registration. Answer and source tokens are data-dependent, source count is seam-bounded, and the retained result is resent until compaction. | + ## Known Limitations and Deferred Work - **Citation-fallback sources are URL-only** — when Perplexity omits structured `search_results[]`, sources carry no `title`/`snippet`/`publishedAt`, so the tool renders bare hostname labels. diff --git a/packages/web/web/README.md b/packages/web/web/README.md index f63e24c2a4..3582ca9d1d 100644 --- a/packages/web/web/README.md +++ b/packages/web/web/README.md @@ -14,12 +14,6 @@ This package is the interface third of the web capability. Unlike bash/fs it spa Search and fetch share no request schema and no business logic, but they are deliberately one seam: `ctx.web` is a single web-access middle layer with one provider-selection policy owner, one abort/error vocabulary, and one product-facing "how this harness reaches the web" config surface. The cost is the parallel `Search`/`Fetch` method pairs; that parallelism is intentional, not a missed extraction. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The seam registers providers, not tools or prompt text. `dsh-tool-web` renders normalized search answers, sources, fetched bodies, and structured `WebError` values. Provider selection details stay internal except for an execution error. | Zero direct tokens. The seam indirectly bounds search result tokens by truncating sources to `maxResults`; all rendered size comes through a consumer. | - ## Service API (`ctx.web`) | Member | Semantics | @@ -49,6 +43,12 @@ The failure branches throw `WebError`, whose structured code (plus message detai `WebSearchRequest` (`query`, `maxResults?`) → `WebSearchResult` (`providerId`, `query`, `content?`, `sources[]`, `truncated`); each `WebSearchSource` has a required `url` and optional `title`/`snippet`/`publishedAt` (Perplexity citations may be URL-only). `WebFetchRequest` (`url`, `timeoutMs?`) → `WebFetchResult` (`providerId`, final `url`, `statusCode`, `body`, `truncated`); `WebFetchBody` is a CLOSED discriminated union (`html` | `text`) owned here — consumers `switch` to exhaustiveness so a new kind breaks their compilation until handled. See `src/types.ts` for the full contracts and the `WebError` code taxonomy. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The seam registers providers, not tools or prompt text. `dsh-tool-web` renders normalized search answers, sources, fetched bodies, and structured `WebError` values. Provider selection details stay internal except for an execution error. | Zero direct tokens. The seam indirectly bounds search result tokens by truncating sources to `maxResults`; all rendered size comes through a consumer. | + ## Known Limitations and Deferred Work - **No observation surface** — no provider-change event and no capability-status query; availability is observed only by executing `search()`/`fetch()` and routing the thrown `WebError` codes, and the no-provider failure is the generic `WEB_PROVIDER_UNAVAILABLE` with no per-provider reason enumeration ([RFC](../../../docs/rfc/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md)). diff --git a/packages/workflow/tool-workflow/README.md b/packages/workflow/tool-workflow/README.md index cdeb867a6e..83b46a6852 100644 --- a/packages/workflow/tool-workflow/README.md +++ b/packages/workflow/tool-workflow/README.md @@ -2,13 +2,6 @@ The model-facing **`workflow` tool**: run a JavaScript orchestration script that fans out subagents, and return the script's final value. Pure schema + lifecycle shaping over [`ctx.workflows`](../workflow/README.md) — script parsing, execution, caps, and cancellation live behind the seam, so a hardened engine swaps in without touching what the model sees. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt and tool schema | The parent model receives a short use-only-for-large-orchestration section plus the `workflow` schema. The schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial but fixed per-request guidance and schema cost while visible. | -| Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. The result contains the workflow name, child count, and final JSON value or a shaped error; intermediate child messages are omitted. | Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context. | - ## What the model sees Three parameters: `script` (required plain-JavaScript body with no `export const meta` statement), `meta` (required JSON identity with `name`/`description` and optional `whenToUse`/`phases`), and `args` (optional JSON object exposed to the script as the `args` global; wrap a bare list as a field so the wire schema stays honest). The tool description carries the complete authoring contract: hooks, semantics, and the supported schema subset. The plugin also contributes a `tool:` system-prompt section carrying the usage policy — use the tool only on an explicit user ask for a workflow / large orchestration; prefer plain subagent calls for one or two delegations — per the convention that tool guidance ships with the tool plugin, never in the deployment persona. @@ -28,6 +21,13 @@ Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/arch | `toolName` | `workflow` | The model-facing tool name to register. | | `maxResultChars` | `50000` | Rendered-result ceiling; longer JSON is truncated with a notice. | +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt and tool schema | The parent model receives a short use-only-for-large-orchestration section plus the `workflow` schema. The schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial but fixed per-request guidance and schema cost while visible. | +| Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. The result contains the workflow name, child count, and final JSON value or a shaped error; intermediate child messages are omitted. | Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context. | + ## Known Limitations and Deferred Work - **The parent turn blocks until the whole workflow settles** — there is no background start/poll surface, and cancellation discards partial output as an error. diff --git a/packages/workflow/workflow-workerthread/README.md b/packages/workflow/workflow-workerthread/README.md index 9944012a0b..5cdd267451 100644 --- a/packages/workflow/workflow-workerthread/README.md +++ b/packages/workflow/workflow-workerthread/README.md @@ -2,13 +2,6 @@ The [`WorkflowService`](../workflow/README.md) implementation, on **`node:worker_threads`**: each run gets its OWN worker thread (one run = one worker, no pooling — a run is heavyweight, so the ~tens-of-ms thread spin-up is noise), the script executes in a vm context INSIDE that worker with the workflow hooks injected, and every `agent()` call bridges back over the message port to [`ctx.subagents`](../../subagent/README.md) on the host. Child agents are I/O-bound LLM loops and stay on the host event loop; the thread isolates the SCRIPT, the only part that can spin synchronously. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent requests | Every script `agent()` call sends its prompt and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events. | Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly. | -| Parent tool result, indirectly | The parent sees only the script's materialized final JSON value, child count, or error through `dsh-tool-workflow`. Intermediate child outputs are available to the script but not the parent model. | Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction. | - ## Trust premise: what the thread buys (and what it does not) Workflow scripts are **model-written** — the same trust level as the model's existing bash access — so this engine defends against **buggy** scripts, never hostile ones. A worker thread is NOT a security boundary: the vm context inside it is escapable by construction (`node:vm` shares object machinery with its surrounding realm, so a script can reach the `Function` constructor via `globalThis.constructor.constructor` and from it `process` and every Node builtin), and an escapee holds the same process privileges as the host — Node's permission model is process-wide. The absent globals are API surface that keeps honest scripts portable, not walls. What the thread concretely buys: @@ -59,6 +52,13 @@ A returned promise or thenable resolves per JavaScript semantics BEFORE material | `syncTimeoutMs` | `5000` | vm timeout for the script's initial synchronous slice (in the worker). | | `disposeGraceMs` | `5000` | How long a cancelled run may stay unsettled before force-settle + terminate; also bounds `dispose()`. | +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent requests | Every script `agent()` call sends its prompt and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events. | Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly. | +| Parent tool result, indirectly | The parent sees only the script's materialized final JSON value, child count, or error through `dsh-tool-workflow`. Intermediate child outputs are available to the script but not the parent model. | Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction. | + ## Known Limitations and Deferred Work - **The worker/vm is not a security boundary** — model-written code can escape `node:vm` and reach the worker's process authority; a hostile-code deployment needs a separate-process or container engine. diff --git a/packages/workflow/workflow/README.md b/packages/workflow/workflow/README.md index 7719fa47e4..2c4d3dec89 100644 --- a/packages/workflow/workflow/README.md +++ b/packages/workflow/workflow/README.md @@ -2,12 +2,6 @@ The **workflow seam** (`ctx.workflows`): an abstract service defining WHAT a workflow engine does — execute a model-written orchestration script that fans out subagents — without saying HOW. The bash-shaped third of the [workflow family](../README.md): implementations subclass `WorkflowService` and register as the `workflows` service (one per context); [`dsh-workflow-workerthread`](../workflow-workerthread/README.md) (one worker thread per run) is the implementation, and [`dsh-tool-workflow`](../tool-workflow/README.md) is the model-facing consumer. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The service seam and `workflow/*` observer events register no prompt, schema, or message. `dsh-tool-workflow` renders the parent-facing contract and final value; an engine decides which child prompts run. | Zero direct tokens. Parent result and child contexts affect tokens only through the consumer and implementation. | - ## Service: `WorkflowService` (abstract) `start(request: WorkflowStartRequest): WorkflowRun` — parse and execute a script. Throws synchronously (`SCRIPT_PARSE`/`META_INVALID`) for a script that cannot begin; once a run is returned, its `result` NEVER rejects — every failure resolves with `stopReason: 'error'` (or `'cancelled'`) — and once the run is cancelled, `result` settles within the implementation's bounded grace even if the script itself never settles (a consumer awaiting `result` must never be wedged past a cancellation). `dispose()` must reach quiescence within a bounded grace (cancel → wait for the script to settle and its children to finish disposing → abandon), never hanging its caller. Runs are HOLDER-owned: the engine does not track its live runs, so disposing the engine's fiber mid-run leaves each run to its holder's teardown. @@ -30,6 +24,12 @@ All observe-only emits carrying DATA SNAPSHOTS (`WorkflowRunInfo` = id + meta) - `workflow/phase`(info, title) / `workflow/log`(info, message) — script narration. - `workflow/agent-start`(info, agent) / `workflow/agent-end`(info, agent + outcome) — ready-child lifecycle correlated by `seq`; the [generated event contract](../../../docs/cordis-catalog/events.md#workflowagent-start--emit) defines publication and pairing. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The service seam and `workflow/*` observer events register no prompt, schema, or message. `dsh-tool-workflow` renders the parent-facing contract and final value; an engine decides which child prompts run. | Zero direct tokens. Parent result and child contexts affect tokens only through the consumer and implementation. | + ## Known Limitations and Deferred Work - **Foreground collection only** — the caller owns one live run and awaits it; background start/poll, spill handles, and detached collection are deferred. diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 84ba22ea88..3144c0e560 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -11,6 +11,7 @@ import { relative, resolve } from 'node:path' const root = resolve(import.meta.dirname, '..') const HEADING = '## Model Experience' const HEADING_PATTERN = /^## Model Experience$/gm +const LIMITATIONS_HEADING = '## Known Limitations and Deferred Work' const TABLE_HEADER = '| Context surface | What the model sees | Token effect |' const TABLE_DIVIDER = '|---|---|---|' @@ -46,6 +47,22 @@ for (const packageJson of packageJsons) { continue } const headingIndex = match.index + const h2Headings = [...source.matchAll(/^## .+$/gm)] + const modelH2Index = h2Headings.findIndex(heading => heading.index === headingIndex) + const limitationsH2Index = h2Headings.findIndex(heading => heading[0] === LIMITATIONS_HEADING) + if (limitationsH2Index >= 0) { + if (modelH2Index !== h2Headings.length - 2 || limitationsH2Index !== h2Headings.length - 1) { + failures.push({ + path: readme, + message: `${HEADING} and ${LIMITATIONS_HEADING} must be the final two H2 sections, in that order`, + }) + continue + } + } else if (modelH2Index !== h2Headings.length - 1) { + failures.push({ path: readme, message: `${HEADING} must be the final H2 when ${LIMITATIONS_HEADING} is absent` }) + continue + } + const bodyStart = headingIndex + HEADING.length const nextHeadingOffset = source.slice(bodyStart).search(/^## /m) const section = source.slice(bodyStart, nextHeadingOffset < 0 ? undefined : bodyStart + nextHeadingOffset)