docs: replace model experience tables with sections
This commit is contained in:
@@ -14,7 +14,7 @@ packages/<group>/<pkg>/
|
||||
src/index.ts # service default export or plugin (name/inject/apply/Config)
|
||||
tests/<x>.spec.ts
|
||||
README.md # service API, events, extension points, design notes,
|
||||
# + the gated Model Experience table or short sentence
|
||||
# + gated Model Experience context blocks or short sentence
|
||||
# + the gated "Known Limitations and Deferred Work" section
|
||||
# (or a whitelist entry in scripts/verify-readme-limitations.ts)
|
||||
```
|
||||
@@ -47,15 +47,17 @@ Keep package-specific service API, config, events, extension points, and design
|
||||
````markdown
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Request surface and condition | Short verbatim text, an exact data-dependent shape, or a link to the long literal below | Fixed, conditional, retained, replaced, capped, or zero-direct token effect |
|
||||
### Request surface and condition
|
||||
|
||||
**What the model sees**: Short verbatim text, an exact data-dependent shape, or a link to the long literal below.
|
||||
|
||||
**Token effect**: Fixed, conditional, retained, replaced, capped, or zero-direct token effect.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### Descriptive title matching the table row
|
||||
#### Descriptive title matching the context surface
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Long stable prompt or schema description, copied exactly from source.
|
||||
```
|
||||
|
||||
@@ -64,9 +66,9 @@ Long stable prompt or schema description, copied exactly from source.
|
||||
- **Consumer-visible gap** — exact boundary or deliberately deferred work.
|
||||
````
|
||||
|
||||
Fill Model Experience from the implementation. Direct, multi-surface, conditional, capped, or lifetime effects use the table; name each request contribution and token-growth condition. Quote short stable model-visible source literals verbatim in inline code, using named placeholders such as `<mode>` only for interpolated values. Put a long stable prompt or schema description under the optional exact `### Verbatim model-visible text` heading: give each literal an H4 title and its own `text` fence, then link it from the table instead of putting long prose in a cell. Summarize only data-dependent payloads, provider-owned text, or schemas too large to reproduce, and identify their exact shape and renderer. Do not infer prompt visibility from tool-schema visibility because independently registered guidance can remain after a scoped tool restriction.
|
||||
Fill Model Experience from the implementation. Direct, multi-surface, conditional, capped, or lifetime effects use one H3 block per context surface; each block has the exact bold-led `What the model sees` and `Token effect` fields shown above. Quote short stable model-visible source literals verbatim in inline code, using named placeholders such as `<mode>` only for interpolated values. Put a long stable prompt or schema description under the optional exact `### Verbatim model-visible text` heading: give each literal an H4 title and its own `markdown` fence, then link it from the relevant context surface. Summarize only data-dependent payloads, provider-owned text, or schemas too large to reproduce, and identify their exact shape and renderer. Do not infer prompt visibility from tool-schema visibility because independently registered guidance can remain after a scoped tool restriction.
|
||||
|
||||
An audited package with no context effect or one simple consumer-owned path joins [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and replaces the table with one line beginning `None, as ` or `Indirectly, through `. Every package outside that allowlist must keep the exact table; the verifier also gates the optional verbatim appendix's heading, H4-plus-`text`-fence shape, and placement after the table. A package with genuinely no limitations joins the separate allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. The [Model Experience RFC](../rfc/implemented/process/2026-07-12-package-model-experience-contract.md) records the rationale.
|
||||
An audited package with no context effect or one simple consumer-owned path joins [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and replaces the structured blocks with one line beginning `None, as ` or `Indirectly, through `. Every package outside that allowlist keeps one or more context-surface blocks; the verifier gates their H3 headings, field labels, spacing, and the optional verbatim appendix's H4-plus-`markdown`-fence shape. A package with genuinely no limitations joins the separate allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. The [Model Experience RFC](../rfc/implemented/process/2026-07-12-package-model-experience-contract.md) records the rationale.
|
||||
|
||||
## 5. Verify
|
||||
|
||||
|
||||
@@ -8,21 +8,22 @@ A package README can explain APIs and runtime mechanics without answering the qu
|
||||
|
||||
## Decision
|
||||
|
||||
Every workspace package README ends with the canonical [Model Experience section](../../../cookbook/adding-a-package.md#4-write-the-package-readme), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Packages with direct, multi-surface, conditional, capped, or lifetime effects use the three-column table. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. Short stable source literals are quoted verbatim in the table, with named placeholders only for interpolated values. Long stable prompts or schema descriptions live in a `### Verbatim model-visible text` appendix immediately after the table, one titled `text` fence per literal, and the table links to them; long prompts do not become table-cell prose. Summaries are reserved for data-dependent payloads, provider-owned text, or schemas too large to reproduce. 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. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other.
|
||||
Every workspace package README ends with the canonical [Model Experience section](../../../cookbook/adding-a-package.md#4-write-the-package-readme), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Packages with direct, multi-surface, conditional, capped, or lifetime effects use one H3 block per context surface. Each block says what the relevant model literally receives and when under `**What the model sees**:`, then classifies the token effect under `**Token effect**:`. Short stable source literals are quoted verbatim in the first field, with named placeholders only for interpolated values. Long stable prompts or schema descriptions live in a `### Verbatim model-visible text` appendix after the context surfaces, one titled `markdown` fence per literal, and the relevant surface links to them. Summaries are reserved for data-dependent payloads, provider-owned text, or schemas too large to reproduce. 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. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other.
|
||||
|
||||
Every package participates. A package with no model-context effect, or one simple effect rendered entirely by another package, can join the verifier's audited sentence allowlist. It then uses exactly one sentence beginning `None, as ` or `Indirectly, through ` instead of stretching a negative fact across a three-column table. Implementations that shape results, caps, history, lifetimes, or more than one request surface keep the table even when they add zero direct prompt tokens.
|
||||
Every package participates. A package with no model-context effect, or one simple effect rendered entirely by another package, can join the verifier's audited sentence allowlist. It then uses exactly one sentence beginning `None, as ` or `Indirectly, through ` instead of expanding a negative fact into a structured block. Implementations that shape results, caps, history, lifetimes, or more than one request surface keep the context-surface blocks even when they add zero direct prompt tokens.
|
||||
|
||||
`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README and the canonical final-section order, and validates one of two package-classified bodies. An allowlisted package must carry exactly one sentence with its assigned prefix; every other package must start with the exact three-column header and at least one complete row. Optional long literals must follow that table under the exact appendix heading, with each H4 title paired to one non-empty `text` fence. It runs in `doc-sync` and the parallel gate runner. The check owns package classification, structural presence, table and appendix shape, and order; implementation review owns coverage and the truth of the prose.
|
||||
`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README and the canonical final-section order, and validates one of two package-classified bodies. An allowlisted package carries exactly one sentence with its assigned prefix; every other package carries at least one H3 context surface with the two exact, non-empty fields and one blank line between each element. Optional long literals follow the structured blocks under the exact appendix heading, with each H4 title paired to one non-empty `markdown` fence and linked from a model-view field. It runs in `doc-sync` and the parallel gate runner. The check owns package classification, structural presence, block and appendix shape, and order; implementation review owns coverage and the truth of the prose.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Document only packages that register prompts or tools** — rejected because backends, policy plugins, adapters, persistence, scoping, and compaction change the content or lifetime of tokens without owning a model-facing schema.
|
||||
- **Generate one central context-cost catalog from source** — rejected because an AST can find registrations but cannot infer semantic conditions such as history retention, output truncation, parent-versus-child visibility, or an auxiliary model boundary. The package README is the implementation-local contract; a central copy would add another drift surface.
|
||||
- **Require numeric token counts** — rejected because exact counts depend on the selected model tokenizer, adapter serialization, configuration, and runtime data. The stable contract is the growth shape: fixed per request, conditional per call, retained, replaced, capped, or zero-direct.
|
||||
- **Use a three-column table** — rejected because exact source text and conditional result shapes make cells dense and difficult to scan. Repeated subsections give each context surface readable vertical space while preserving the same fields.
|
||||
- **Allow zero-impact packages to omit the section** — rejected because absence is ambiguous between an audited zero and forgotten documentation. One explicit sentence is cheap and mechanically distinguishable.
|
||||
- **Require the full table for audited zero or simple indirect packages** — rejected because it spreads one fact across three cells and encourages repetitive prose. A gated sentence preserves explicit coverage without the ceremony.
|
||||
- **Require the full structured form for audited zero or simple indirect packages** — rejected because it repeats labels around one fact. A gated sentence preserves explicit coverage without the ceremony.
|
||||
- **Convention without a gate** — rejected because a repo-wide contract must also cover every future package; review memory cannot reliably detect an omitted README section.
|
||||
|
||||
## Consequences
|
||||
|
||||
A reviewer can start at any package and see its contribution to the conversation model, child models, and auxiliary calls without reconstructing the full plugin graph. Token-budget work can distinguish repeated request overhead from data-dependent history, and agent-scoped changes have an explicit documentation checkpoint. Package authors pay for one small table or one classified sentence and must update it whenever model-visible behavior changes. Tables deliberately do not promise provider-exact token counts; measurements remain model- and workload-specific, while the documented growth and visibility contract stays stable.
|
||||
A reviewer can start at any package and see its contribution to the conversation model, child models, and auxiliary calls without reconstructing the full plugin graph. Token-budget work can distinguish repeated request overhead from data-dependent history, and agent-scoped changes have an explicit documentation checkpoint. Package authors maintain one or more compact context-surface blocks or one classified sentence whenever model-visible behavior changes. The structured fields do not promise provider-exact token counts; measurements remain model- and workload-specific, while the documented growth and visibility contract stays stable.
|
||||
@@ -27,9 +27,11 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi;
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the data-dependent stdout and stderr tail inside that consumer's exact result wrappers, 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. An unknown task becomes exactly `Error: unknown bash task "<taskId>"`; a pre-spawn cancellation becomes `Error: aborted before spawn: <reason>`. | 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. |
|
||||
### Bash tool results, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-bash`, the conversation model sees the data-dependent stdout and stderr tail inside that consumer's exact result wrappers, 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. An unknown task becomes exactly `Error: unknown bash task "<taskId>"`; a pre-spawn cancellation becomes `Error: aborted before spawn: <reason>`.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -34,11 +34,23 @@ The keyless consumer-integration proofs are `tests/bwrap.e2e.ts`, `tests/landloc
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Bash tool schema, indirectly | By advertising a confining `sandboxMode`, this backend makes `dsh-tool-bash` expose `sandbox_permissions` with enum `workspace-write` \| `danger-full-access` and `justification`. The backend adds no prompt prose, and the session's effective mode remains unstated. | Small fixed schema increment on requests where `bash` is visible; mode switches add no context tokens. |
|
||||
| Bash tool result, indirectly | After ordinary bounded output, a denied call appends exactly `[sandbox: file access denied under <mode> mode]`. When escalation is available it next appends `[sandbox: escalation available — retry this exact command once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]`. A settled background runner failure instead appends `[sandbox: the sandbox runner itself failed under <mode> mode — the command did not run; this is a sandbox problem, not a command failure]`. | Zero additional tokens on an unremarkable allowed run beyond ordinary output. Denial or failure adds the quoted conditional marker, retained until compaction. |
|
||||
| Bash tool error, indirectly | If no runner can enforce a confined mode, the foreground call fails with code `SANDBOX_UNAVAILABLE` and the exact message `sandbox mode "<mode>" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS) — Windows has no confinement backend yet — or switch the consumer to danger-full-access.` An execution-time runner failure appends ` Runner failure: <first stderr line>`. | Conditional error text is visible for that call and retained in history until compaction. |
|
||||
### Bash tool schema, indirectly
|
||||
|
||||
**What the model sees**: By advertising a confining `sandboxMode`, this backend makes `dsh-tool-bash` expose `sandbox_permissions` with enum `workspace-write` | `danger-full-access` and `justification`. The backend adds no prompt prose, and the session's effective mode remains unstated.
|
||||
|
||||
**Token effect**: Small fixed schema increment on requests where `bash` is visible; mode switches add no context tokens.
|
||||
|
||||
### Bash tool result, indirectly
|
||||
|
||||
**What the model sees**: After ordinary bounded output, a denied call appends exactly `[sandbox: file access denied under <mode> mode]`. When escalation is available it next appends `[sandbox: escalation available — retry this exact command once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]`. A settled background runner failure instead appends `[sandbox: the sandbox runner itself failed under <mode> mode — the command did not run; this is a sandbox problem, not a command failure]`.
|
||||
|
||||
**Token effect**: Zero additional tokens on an unremarkable allowed run beyond ordinary output. Denial or failure adds the quoted conditional marker, retained until compaction.
|
||||
|
||||
### Bash tool error, indirectly
|
||||
|
||||
**What the model sees**: If no runner can enforce a confined mode, the foreground call fails with code `SANDBOX_UNAVAILABLE` and the exact message `sandbox mode "<mode>" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS) — Windows has no confinement backend yet — or switch the consumer to danger-full-access.` An execution-time runner failure appends ` Runner failure: <first stderr line>`.
|
||||
|
||||
**Token effect**: Conditional error text is visible for that call and retained in history until compaction.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -60,13 +60,35 @@ Under a sandboxing executor this plugin makes the session's standing mode overri
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| System prompt | Every request in this plugin's registration scope contains exactly `Check the [exit code: N] marker on every bash result; investigate failures before moving on.` A sandboxing executor adds no mode statement or switch notice. Scoped tool restrictions can hide the schemas without removing this independently registered section. | Small fixed input cost per request while the plugin is active, unchanged by sandbox mode or mode switches. |
|
||||
| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`; their full descriptions and JSON schemas are the exact output of the definitions in `src/index.ts`. `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 and its conditional description paragraph. |
|
||||
| Foreground result | The renderer emits the data-dependent stdout tail, then optional `[stderr]` and the stderr tail. With no output it emits exactly `(no output)`. Conditional lines are exactly `[output truncated; full output: <path-or-(unavailable)>]`, `[sandbox: file access denied under <mode> mode]`, `[timed out after <timeoutMs>ms]`, `[killed by signal: <signal>]`, and `[exit code: <exitCode>]`; the sandbox escalation and runner-failure lines are quoted in [`dsh-bash-sandbox`](../bash-sandbox/README.md). | Zero result tokens before a call. Output is bounded per stream, while each emitted line remains in history until compaction. |
|
||||
| Background task context and results | Start returns exactly `started background task <taskId>`. Completion injects exactly `background bash task <taskId> finished <status>. Read its output with bash_output.` Reads return only the data-dependent delta or `(no new output)`, optionally `[some output was dropped from memory; full output: <paths-or-(unavailable)>]`, then exactly one of `[status: running]`, `[status: killed]`, `[status: killed by <signal>]`, or `[status: completed, exit code: <exitCode>]`. Kill returns `killed background task <taskId>` or `task <taskId> had already finished`. | Start and status text is small; deltas are data-dependent. The completion notice and every tool result are retained until compaction, but polling does not repeat already-delivered output. |
|
||||
| Tool errors | Validation and policy failures are normalized as `Error: <message>`. This package's stable messages are `invalid command: expected a non-empty string`, `invalid description: expected a non-empty string`, `invalid timeoutMs: expected a positive number, got <value>`, `invalid escalation: sandbox_permissions requires a justification`, `invalid escalation: justification is only valid together with sandbox_permissions`, `invalid justification: expected a non-empty sentence`, `invalid task_id: expected a string, got <value>`, `task <taskId> belongs to another session`, `sandbox_permissions is not available in this composition (no sandboxing executor to escalate)`, `sandbox escalation to "<mode>" is not strictly wider than this call's current "<mode>" mode`, the approval-availability/rejection/cancellation variants, and `command aborted`. | Only the failing call adds these retained tokens; a rejected escalation does not add command output because the command does not run. |
|
||||
### System prompt
|
||||
|
||||
**What the model sees**: Every request in this plugin's registration scope contains exactly `Check the [exit code: N] marker on every bash result; investigate failures before moving on.` A sandboxing executor adds no mode statement or switch notice. Scoped tool restrictions can hide the schemas without removing this independently registered section.
|
||||
|
||||
**Token effect**: Small fixed input cost per request while the plugin is active, unchanged by sandbox mode or mode switches.
|
||||
|
||||
### Tool schemas
|
||||
|
||||
**What the model sees**: The model sees `bash`, `bash_output`, and `bash_kill`; their full descriptions and JSON schemas are the exact output of the definitions in `src/index.ts`. `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.
|
||||
|
||||
**Token effect**: Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields and its conditional description paragraph.
|
||||
|
||||
### Foreground result
|
||||
|
||||
**What the model sees**: The renderer emits the data-dependent stdout tail, then optional `[stderr]` and the stderr tail. With no output it emits exactly `(no output)`. Conditional lines are exactly `[output truncated; full output: <path-or-(unavailable)>]`, `[sandbox: file access denied under <mode> mode]`, `[timed out after <timeoutMs>ms]`, `[killed by signal: <signal>]`, and `[exit code: <exitCode>]`; the sandbox escalation and runner-failure lines are quoted in [`dsh-bash-sandbox`](../bash-sandbox/README.md).
|
||||
|
||||
**Token effect**: Zero result tokens before a call. Output is bounded per stream, while each emitted line remains in history until compaction.
|
||||
|
||||
### Background task context and results
|
||||
|
||||
**What the model sees**: Start returns exactly `started background task <taskId>`. Completion injects exactly `background bash task <taskId> finished <status>. Read its output with bash_output.` Reads return only the data-dependent delta or `(no new output)`, optionally `[some output was dropped from memory; full output: <paths-or-(unavailable)>]`, then exactly one of `[status: running]`, `[status: killed]`, `[status: killed by <signal>]`, or `[status: completed, exit code: <exitCode>]`. Kill returns `killed background task <taskId>` or `task <taskId> had already finished`.
|
||||
|
||||
**Token effect**: Start and status text is small; deltas are data-dependent. The completion notice and every tool result are retained until compaction, but polling does not repeat already-delivered output.
|
||||
|
||||
### Tool errors
|
||||
|
||||
**What the model sees**: Validation and policy failures are normalized as `Error: <message>`. This package's stable messages are `invalid command: expected a non-empty string`, `invalid description: expected a non-empty string`, `invalid timeoutMs: expected a positive number, got <value>`, `invalid escalation: sandbox_permissions requires a justification`, `invalid escalation: justification is only valid together with sandbox_permissions`, `invalid justification: expected a non-empty sentence`, `invalid task_id: expected a string, got <value>`, `task <taskId> belongs to another session`, `sandbox_permissions is not available in this composition (no sandboxing executor to escalate)`, `sandbox escalation to "<mode>" is not strictly wider than this call's current "<mode>" mode`, the approval-availability/rejection/cancellation variants, and `command aborted`.
|
||||
|
||||
**Token effect**: Only the failing call adds these retained tokens; a rejected escalation does not add command output because the command does not run.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -33,9 +33,11 @@ Every field is validated (positive numbers) and defaulted; there are no other tu
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| `run_code` result, indirectly | Through Code Mode in `dsh-tools`, the conversation model sees only what the program prints or returns. A log cap emits exactly `[dsh-code-runtime-worker] log capture truncated at <maxLogBytes> bytes`; an oversized or non-transferable return rendered as text ends exactly `… [truncated]`. Worker failures become `Error: code run failed (<kind>): <message>`; this backend's stable messages include `compute budget exhausted (<computeMs>ms busy)`, `wall-clock ceiling reached (<maxWallMs>ms)`, `worker error: <message>`, and `worker exited with code <code> before completing`. Binding-call traffic and worker internals stay outside context. | Zero tokens until Code Mode executes a program. `maxLogBytes` and `maxValueBytes` cap the model-visible result, which then remains in tool history until compaction. |
|
||||
### `run_code` result, indirectly
|
||||
|
||||
**What the model sees**: Through Code Mode in `dsh-tools`, the conversation model sees only what the program prints or returns. A log cap emits exactly `[dsh-code-runtime-worker] log capture truncated at <maxLogBytes> bytes`; an oversized or non-transferable return rendered as text ends exactly `… [truncated]`. Worker failures become `Error: code run failed (<kind>): <message>`; this backend's stable messages include `compute budget exhausted (<computeMs>ms busy)`, `wall-clock ceiling reached (<maxWallMs>ms)`, `worker error: <message>`, and `worker exited with code <code> before completing`. Binding-call traffic and worker internals stay outside context.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -59,23 +59,35 @@ Loading the plugin registers `ctx.compact`. With `auto: true` (the default) it c
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Conversation history | Before a step whose estimated envelope and history exceed the threshold, the conversation model receives the exact [checkpoint preamble](#conversation-checkpoint-preamble), a blank line, `<compacted-summary>`, the data-dependent summary, and `</compacted-summary>`. This one checkpoint replaces the selected older range and is 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 user message | The summarization model receives exactly `Summarize this conversation history:` followed by a blank line, the data-dependent [`renderTranscript()`](../compact/README.md) output, another blank line, and `Summary:`. 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. |
|
||||
| Auxiliary summarizer system prompt | The summarization model receives the exact [checkpoint-writing instruction](#auxiliary-summarizer-system-prompt). | Fixed auxiliary input cost plus the data-dependent transcript on every summarization attempt. |
|
||||
### Conversation history
|
||||
|
||||
**What the model sees**: Before a step whose estimated envelope and history exceed the threshold, the conversation model receives the exact [checkpoint preamble](#conversation-checkpoint-preamble), a blank line, `<compacted-summary>`, the data-dependent summary, and `</compacted-summary>`. This one checkpoint replaces the selected older range and is followed by the retained recent units.
|
||||
|
||||
**Token effect**: 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 user message
|
||||
|
||||
**What the model sees**: The summarization model receives exactly `Summarize this conversation history:` followed by a blank line, the data-dependent [`renderTranscript()`](../compact/README.md) output, another blank line, and `Summary:`. The conversation model never sees this private request or its reasoning; only returned text is stored.
|
||||
|
||||
**Token effect**: This is a separate model call with data-dependent input and `maxTokens`-capped output. Convergence retries can pay this cost more than once.
|
||||
|
||||
### Auxiliary summarizer system prompt
|
||||
|
||||
**What the model sees**: The summarization model receives the exact [checkpoint-writing instruction](#auxiliary-summarizer-system-prompt).
|
||||
|
||||
**Token effect**: Fixed auxiliary input cost plus the data-dependent transcript on every summarization attempt.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### Conversation checkpoint preamble
|
||||
|
||||
```text
|
||||
```markdown
|
||||
This is an automatically generated checkpoint condensing an earlier span of the conversation to free up context. Treat the captured context as established background and build on it without restating it. Continue the task directly from the messages that follow, without acknowledging this checkpoint.
|
||||
```
|
||||
|
||||
#### Auxiliary summarizer system prompt
|
||||
|
||||
```text
|
||||
```markdown
|
||||
You are a compaction engine for an AI coding assistant. Condense the conversation transcript into a structured checkpoint that lets another model resume the work with no loss of essential context.
|
||||
|
||||
Output EXACTLY the Markdown structure below: keep every section, in order. Use terse bullets, not prose paragraphs. Write "(none)" for an empty section — never drop a section.
|
||||
|
||||
@@ -51,10 +51,17 @@ Subclass `CompactService`, implement `compactIfNeeded` and `compactRegion`, and
|
||||
|
||||
## 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. |
|
||||
| Transcript supplied to a compaction consumer | `renderTranscript()` joins entries with one blank line and renders them exactly as `User: <content>`, `Assistant: <content>`, `Tool result (call <callId>): <content>`, `Tool error (call <callId>): <content>`, `[Context: <content>]`, or `[Steering: <content>]`. Non-text blocks render exactly as `[reasoning: <text>]`, `[tool-call: <name>(<arguments>)]`, `[tool-result: <content>]`, `[tool-result]`, or `[<block-type>]`. | Data-dependent input tokens are paid only by the auxiliary model or consumer that requests this transcript; the conversation model does not receive a duplicate transcript. |
|
||||
### Conversation history, when a backend is invoked
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: Zero direct tokens from this interface. A backend trades many retained history tokens for one summary and leaves the recent tail unchanged.
|
||||
|
||||
### Transcript supplied to a compaction consumer
|
||||
|
||||
**What the model sees**: `renderTranscript()` joins entries with one blank line and renders them exactly as `User: <content>`, `Assistant: <content>`, `Tool result (call <callId>): <content>`, `Tool error (call <callId>): <content>`, `[Context: <content>]`, or `[Steering: <content>]`. Non-text blocks render exactly as `[reasoning: <text>]`, `[tool-call: <name>(<arguments>)]`, `[tool-result: <content>]`, `[tool-result]`, or `[<block-type>]`.
|
||||
|
||||
**Token effect**: Data-dependent input tokens are paid only by the auxiliary model or consumer that requests this transcript; the conversation model does not receive a duplicate transcript.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -34,11 +34,23 @@ Namespace plugin: named exports `name` / `inject` / `Config` / `apply`, no defau
|
||||
|
||||
## 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 joins selected sections exactly as `## <section>` then a newline and the data-dependent body, with one blank line between sections. Mount returns `mounted <id> (plugin "<name>", state: <state>)`, optionally inserting ` — waiting for service(s): <names> (activates when provided)` before the closing parenthesis. Unmount returns `unmounted <id> (plugin "<name>")`; an unknown id becomes `Error: no dynamic plugin with id "<id>" (list mounts with cordis_inspect what:"dynamic")`. 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. |
|
||||
### Tool schemas
|
||||
|
||||
**What the model sees**: The conversation model sees `cordis_inspect`, `cordis_mount`, and `cordis_unmount` whenever this plugin is visible.
|
||||
|
||||
**Token effect**: Fixed schema cost on every request in that tool view.
|
||||
|
||||
### Tool-call history and results
|
||||
|
||||
**What the model sees**: Inspect joins selected sections exactly as `## <section>` then a newline and the data-dependent body, with one blank line between sections. Mount returns `mounted <id> (plugin "<name>", state: <state>)`, optionally inserting ` — waiting for service(s): <names> (activates when provided)` before the closing parenthesis. Unmount returns `unmounted <id> (plugin "<name>")`; an unknown id becomes `Error: no dynamic plugin with id "<id>" (list mounts with cordis_inspect what:"dynamic")`. The submitted mount program remains in the assistant tool-call history.
|
||||
|
||||
**Token effect**: Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small.
|
||||
|
||||
### Later requests after a mount
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: Indirect token impact equals the mounted plugin's contributions and lasts only for the mount lifetime.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -51,10 +51,17 @@ A YAML include can dedupe the config, but it cannot OWN a `bin`, and it can only
|
||||
|
||||
## 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. |
|
||||
### Composed system prompt and session prefix
|
||||
|
||||
**What the model sees**: Through its children, the bundle supplies the harness identity, configured persona, and the local skill catalog when skills exist.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: Fixed per-request schema or SDK cost for the visible composition. Tool results add data-dependent retained history.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -110,10 +110,17 @@ Everything that goes beyond "call the model, run the tools, repeat" belongs to p
|
||||
|
||||
## 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 chooses the initial contributions, while the authoritative assembly waterfall can alter the final request and makes its listener responsible for protocol coherence. |
|
||||
| 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. |
|
||||
### Complete conversation request
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: System text, schemas, and prefix are paid again on every step. Per-agent scoping chooses the initial contributions, while the authoritative assembly waterfall can alter the final request and makes its listener responsible for protocol coherence.
|
||||
|
||||
### Retained message history
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -54,10 +54,17 @@ The handle every plugin programs against:
|
||||
|
||||
## 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. |
|
||||
### User, steering, and injected messages
|
||||
|
||||
**What the model sees**: `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.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: Registrations through `agent.ctx` can shadow prompt sections or tools and can install agent-only interceptors during unpublished setup.
|
||||
|
||||
**Token effect**: The package adds zero tokens itself; scoped contributions affect only that agent and disappear on disposal.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ Handing out a scoped context hands out the minting plugin's service-resolution s
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Per-agent visibility control | This package emits no text or schema. It routes scoped prompt sections, variables, tools, restrictions, and listeners to one agent: scoped registrations can shadow same-named globals, while restrictions filter global tool schemas and Code Mode bindings before scope-local tools are merged. Restrictions do not filter independently registered prompt sections. All scoped state disappears with that agent. This is request composition, not authority confinement. | Zero direct tokens. Scoped registrations can add or replace whole contributions for one agent; restrictions remove schema entries or SDK bindings and reduce that agent's repeated envelope cost without changing another agent's request. |
|
||||
### Per-agent visibility control
|
||||
|
||||
**What the model sees**: This package emits no text or schema. It routes scoped prompt sections, variables, tools, restrictions, and listeners to one agent: scoped registrations can shadow same-named globals, while restrictions filter global tool schemas and Code Mode bindings before scope-local tools are merged. Restrictions do not filter independently registered prompt sections. All scoped state disappears with that agent. This is request composition, not authority confinement.
|
||||
|
||||
**Token effect**: Zero direct tokens. Scoped registrations can add or replace whole contributions for one agent; restrictions remove schema entries or SDK bindings and reduce that agent's repeated envelope cost without changing another agent's request.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -80,11 +80,23 @@ Every `SessionEvent` carries two optional top-level fields (structural metadata)
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Derived message history | The model receives projections of `user/message`, `assistant/message`, and `tool/result` surface nodes verbatim. A `context/message` is a user-role message containing exactly `<context source="<source-kind>">`, its content blocks, and `</context>`; `steering/message` uses the identical `<steering source="<source-kind>">` / `</steering>` wrapper. 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. |
|
||||
| Crash-repair result | If a persisted turn ended with unanswered tool calls, each synthetic error result contains exactly `Tool call interrupted by a crash; no result was recorded.` | Zero tokens in an intact session. Each repaired call adds this retained error text on resume. |
|
||||
| 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. |
|
||||
### Derived message history
|
||||
|
||||
**What the model sees**: The model receives projections of `user/message`, `assistant/message`, and `tool/result` surface nodes verbatim. A `context/message` is a user-role message containing exactly `<context source="<source-kind>">`, its content blocks, and `</context>`; `steering/message` uses the identical `<steering source="<source-kind>">` / `</steering>` wrapper. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message.
|
||||
|
||||
**Token effect**: 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.
|
||||
|
||||
### Crash-repair result
|
||||
|
||||
**What the model sees**: If a persisted turn ended with unanswered tool calls, each synthetic error result contains exactly `Tool call interrupted by a crash; no result was recorded.`
|
||||
|
||||
**Token effect**: Zero tokens in an intact session. Each repaired call adds this retained error text on resume.
|
||||
|
||||
### Logged request header
|
||||
|
||||
**What the model sees**: 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()`.
|
||||
|
||||
**Token effect**: Zero duplicate tokens from logging. The reconstructed prefix, system text, and schemas still incur their normal per-request cost.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -42,10 +42,17 @@ Design rationale: [the prompt-variables RFC](../../../docs/rfc/implemented/archi
|
||||
|
||||
## 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. The final `system-prompt/assemble` waterfall result is authoritative, so an expert listener's changes determine the delivered prompt and tool schemas. | 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. Sections and schema providers are separate assembly inputs, so a tool restriction does not remove independently registered guidance. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent but not a separate prompt section; reordering changes cache shape but not semantic content. |
|
||||
### System prompt
|
||||
|
||||
**What the model sees**: 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. The final `system-prompt/assemble` waterfall result is authoritative, so an expert listener's changes determine the delivered prompt and tool schemas.
|
||||
|
||||
**Token effect**: Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content.
|
||||
|
||||
### Tool schemas
|
||||
|
||||
**What the model sees**: The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. Sections and schema providers are separate assembly inputs, so a tool restriction does not remove independently registered guidance.
|
||||
|
||||
**Token effect**: Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent but not a separate prompt section; reordering changes cache shape but not semantic content.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -140,23 +140,35 @@ The wire collapse is the registry's own contribution (`systemPrompt.tools()` is
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Normal tool schemas | In normal mode the model sees each visible definition's exact name, description, and JSON schema. Agent-scoped restrictions and shadows change that agent's end-tool set. | Fixed per-request cost proportional to the visible definitions. Restrictions that hide tools remove their entire schema cost for that agent. |
|
||||
| Code Mode schema and SDK | Code Mode exposes `run_code` with the exact [tool description](#run_code-tool-description), parameter description `The program: the body of an async TypeScript function.`, and [SDK instructions](#code-mode-sdk-instructions) followed by the generated exact `declare const tools` block. `both` exposes normal schemas and this Code Mode surface. | 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 content. Any thrown or denied call becomes exactly `Error: <message>`. Code Mode returns only the outer program's printed lines and rendered return value, `(run_code completed with no output)` when both are empty, or `Error: code run failed (<kind>): <message>` followed conditionally by `Captured output:` and the captured lines. Inner dispatch events stay log-only; post-execute listeners may append source-attributed context after the result. | 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. |
|
||||
### Normal tool schemas
|
||||
|
||||
**What the model sees**: In normal mode the model sees each visible definition's exact name, description, and JSON schema. Agent-scoped restrictions and shadows change that agent's end-tool set.
|
||||
|
||||
**Token effect**: Fixed per-request cost proportional to the visible definitions. Restrictions that hide tools remove their entire schema cost for that agent.
|
||||
|
||||
### Code Mode schema and SDK
|
||||
|
||||
**What the model sees**: Code Mode exposes `run_code` with the exact [tool description](#run_code-tool-description), parameter description `The program: the body of an async TypeScript function.`, and [SDK instructions](#code-mode-sdk-instructions) followed by the generated exact `declare const tools` block. `both` exposes normal schemas and this Code Mode surface.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: The loop retains model-emitted arguments and the registry's final content. Any thrown or denied call becomes exactly `Error: <message>`. Code Mode returns only the outer program's printed lines and rendered return value, `(run_code completed with no output)` when both are empty, or `Error: code run failed (<kind>): <message>` followed conditionally by `Captured output:` and the captured lines. Inner dispatch events stay log-only; post-execute listeners may append source-attributed context after the result.
|
||||
|
||||
**Token effect**: 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.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### `run_code` tool description
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.
|
||||
```
|
||||
|
||||
#### Code Mode SDK instructions
|
||||
|
||||
```text
|
||||
```markdown
|
||||
## Writing code for run_code
|
||||
|
||||
Pass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program:
|
||||
|
||||
@@ -23,9 +23,11 @@ The raw I/O lives in `src/fsio.ts` (Cordis-free, independently unit-tested); `sr
|
||||
|
||||
## 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 or mutation acknowledgements. This backend's stable failures are normalized as `Error: <message>`; messages include `cannot read "<path>": binary file`, `cannot <read-or-edit> "<path>": invalid UTF-8 text`, `cannot write "<path>": not a regular file`, `cannot write "<path>": file no longer exists`, `cannot <write-or-edit> "<path>": file changed since it was read`, `cannot overwrite existing "<path>" without reading it first`, `cannot edit "<path>": binary file`, `old_string must be a non-empty string`, `old_string was not found in "<path>"`, and `old_string matched <count> times in "<path>"; provide a more specific old_string or set replace_all to true`. 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 or errors remain in history until compaction. |
|
||||
### Filesystem tool results, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-fs`, the model sees line-windowed UTF-8 file content or mutation acknowledgements. This backend's stable failures are normalized as `Error: <message>`; messages include `cannot read "<path>": binary file`, `cannot <read-or-edit> "<path>": invalid UTF-8 text`, `cannot write "<path>": not a regular file`, `cannot write "<path>": file no longer exists`, `cannot <write-or-edit> "<path>": file changed since it was read`, `cannot overwrite existing "<path>" without reading it first`, `cannot edit "<path>": binary file`, `old_string must be a non-empty string`, `old_string was not found in "<path>"`, and `old_string matched <count> times in "<path>"; provide a more specific old_string or set replace_all to true`. Real paths, versions, atomic-write mechanics, and directory metadata remain internal unless a consumer renders them.
|
||||
|
||||
**Token effect**: Zero direct tokens. Read tokens are bounded by the tool's line, line-length, and byte caps; mutation results or errors remain in history until compaction.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -49,9 +49,11 @@ Because the plugin influences the world only through events, removing it does no
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Filesystem tool outcome | This plugin adds no prompt or schema. Through `dsh-tool-fs`, an edit without a prior read becomes exactly `Error: edit requires reading "<path>" first` with code `FS_NOT_OBSERVED`; guarded mutations whose observed version is stale receive the backend's exact `Error: cannot <write-or-edit> "<path>": file changed since it was read` with code `FS_STALE_VERSION`. Observation state itself is never shown. | Zero tokens on allowed operations beyond the ordinary tool result. A denial adds the small retained error result and avoids any success payload. |
|
||||
### Filesystem tool outcome
|
||||
|
||||
**What the model sees**: This plugin adds no prompt or schema. Through `dsh-tool-fs`, an edit without a prior read becomes exactly `Error: edit requires reading "<path>" first` with code `FS_NOT_OBSERVED`; guarded mutations whose observed version is stale receive the backend's exact `Error: cannot <write-or-edit> "<path>": file changed since it was read` with code `FS_STALE_VERSION`. Observation state itself is never shown.
|
||||
|
||||
**Token effect**: Zero tokens on allowed operations beyond the ordinary tool result. A denial adds the small retained error result and avoids any success payload.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -50,31 +50,53 @@ The read rendering (line windowing + output formatting) lives in `src/read-rende
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| System prompt | Every request in this plugin's registration scope receives the exact independently registered [read](#read-guidance), [write](#write-guidance), and [edit](#edit-guidance) sections. Scoped tool restrictions can hide schemas without removing these sections. | Fixed guidance cost per request while the plugin is active, even when a restriction hides one or more tools. |
|
||||
| Tool schemas | The model sees the exact `read`, `write`, and `edit` descriptions and JSON schemas from their definitions, with snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. |
|
||||
| Read result | A successful read is exactly `<path><displayPath></path>`, newline, `<type>file</type>`, newline, `<content>`, numbered lines as `<lineNumber>: <text>`, a blank line, one footer, and `</content>`. The footer is exactly `(Output capped. Showing lines <start>-<end>. Use offset=<next> to continue.)`, `(Showing lines <start>-<end> of <total>. Use offset=<next> to continue.)`, or `(End of file - total <total> lines)`. A long line ends exactly `... (line truncated to <max> chars)`. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`; the retained call and result are resent until compaction. |
|
||||
| Write and edit results | Write returns the exact five-line envelope `<path><displayPath></path>`, `<type>file</type>`, `<content>`, `Created file` or `Updated file`, then `</content>`. Edit returns exactly `The file <displayPath> has been updated successfully.` or, for `replace_all`, `The file <displayPath> has been updated. All occurrences were successfully replaced.` The full write or replacement text remains in the assistant tool-call arguments. | Success text is small, but large mutation arguments and any result are resent until compaction. |
|
||||
| Tool errors | Failures are normalized as `Error: <message>`. This package's stable validation and read messages are `file_path must be a non-empty string`, `limit must be less than or equal to <max>`, `old_string must be a non-empty string`, `old_string and new_string must differ`, `cannot read "<path>": not found`, `cannot read "<path>": not a regular file`, and `offset <offset> is out of range for "<path>" (<total> lines)`; provider and policy templates are quoted in their package READMEs. | Only a failing call adds these retained tokens. |
|
||||
### System prompt
|
||||
|
||||
**What the model sees**: Every request in this plugin's registration scope receives the exact independently registered [read](#read-guidance), [write](#write-guidance), and [edit](#edit-guidance) sections. Scoped tool restrictions can hide schemas without removing these sections.
|
||||
|
||||
**Token effect**: Fixed guidance cost per request while the plugin is active, even when a restriction hides one or more tools.
|
||||
|
||||
### Tool schemas
|
||||
|
||||
**What the model sees**: The model sees the exact `read`, `write`, and `edit` descriptions and JSON schemas from their definitions, with snake_case arguments. Scoped tool restrictions can remove any definition for one agent.
|
||||
|
||||
**Token effect**: Fixed schema cost on every request in that tool view.
|
||||
|
||||
### Read result
|
||||
|
||||
**What the model sees**: A successful read is exactly `<path><displayPath></path>`, newline, `<type>file</type>`, newline, `<content>`, numbered lines as `<lineNumber>: <text>`, a blank line, one footer, and `</content>`. The footer is exactly `(Output capped. Showing lines <start>-<end>. Use offset=<next> to continue.)`, `(Showing lines <start>-<end> of <total>. Use offset=<next> to continue.)`, or `(End of file - total <total> lines)`. A long line ends exactly `... (line truncated to <max> chars)`.
|
||||
|
||||
**Token effect**: Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`; the retained call and result are resent until compaction.
|
||||
|
||||
### Write and edit results
|
||||
|
||||
**What the model sees**: Write returns the exact five-line envelope `<path><displayPath></path>`, `<type>file</type>`, `<content>`, `Created file` or `Updated file`, then `</content>`. Edit returns exactly `The file <displayPath> has been updated successfully.` or, for `replace_all`, `The file <displayPath> has been updated. All occurrences were successfully replaced.` The full write or replacement text remains in the assistant tool-call arguments.
|
||||
|
||||
**Token effect**: Success text is small, but large mutation arguments and any result are resent until compaction.
|
||||
|
||||
### Tool errors
|
||||
|
||||
**What the model sees**: Failures are normalized as `Error: <message>`. This package's stable validation and read messages are `file_path must be a non-empty string`, `limit must be less than or equal to <max>`, `old_string must be a non-empty string`, `old_string and new_string must differ`, `cannot read "<path>": not found`, `cannot read "<path>": not a regular file`, and `offset <offset> is out of range for "<path>" (<total> lines)`; provider and policy templates are quoted in their package READMEs.
|
||||
|
||||
**Token effect**: Only a failing call adds these retained tokens.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### Read guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
|
||||
```
|
||||
|
||||
#### Write guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
|
||||
```
|
||||
|
||||
#### Edit guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
|
||||
```
|
||||
|
||||
|
||||
@@ -38,22 +38,29 @@ Unit suites drive a real agent loop against a mock adapter (no network) and cove
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| First-threshold context message | At the first configured consecutive-repeat threshold, that agent receives the exact [gentle reminder](#first-threshold-reminder). No tool schema or normal-call text is added. | Zero tokens before the threshold. The reminder is retained history for that agent. |
|
||||
| Later-threshold context message | A later threshold receives the exact [detailed reminder template](#later-threshold-reminder). A capped argument preview ends exactly `… (+<omitted> more chars)`. | Each reminder is retained history; `argumentsPreviewChars` bounds its data-dependent argument text, while agents keep independent counters. |
|
||||
### First-threshold context message
|
||||
|
||||
**What the model sees**: At the first configured consecutive-repeat threshold, that agent receives the exact [gentle reminder](#first-threshold-reminder). No tool schema or normal-call text is added.
|
||||
|
||||
**Token effect**: Zero tokens before the threshold. The reminder is retained history for that agent.
|
||||
|
||||
### Later-threshold context message
|
||||
|
||||
**What the model sees**: A later threshold receives the exact [detailed reminder template](#later-threshold-reminder). A capped argument preview ends exactly `… (+<omitted> more chars)`.
|
||||
|
||||
**Token effect**: Each reminder is retained history; `argumentsPreviewChars` bounds its data-dependent argument text, while agents keep independent counters.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### First-threshold reminder
|
||||
|
||||
```text
|
||||
```markdown
|
||||
You are repeating the exact same tool call with identical arguments. Carefully analyze the previous result before calling again: if the task is not complete, try a different approach or different arguments instead of repeating the call.
|
||||
```
|
||||
|
||||
#### Later-threshold reminder
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Repeated tool call detected:
|
||||
- tool: <toolName>
|
||||
- consecutive_calls: <count>
|
||||
|
||||
@@ -52,10 +52,17 @@ Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-claude' }
|
||||
|
||||
## 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 | Provider-supplied reasons pass through verbatim. When absent, a blocked prompt uses exactly `blocked by UserPromptSubmit hook`, a denied tool becomes `Error: blocked by PreToolUse hook`, blocked post-tool feedback is exactly `blocked by PostToolUse hook`, and a blocking stop adds steering exactly `continue: blocked by Stop hook`. `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 the retained fallback or provider text; forced continuation pays another full request. |
|
||||
### Hook-provided context
|
||||
|
||||
**What the model sees**: `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.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: Provider-supplied reasons pass through verbatim. When absent, a blocked prompt uses exactly `blocked by UserPromptSubmit hook`, a denied tool becomes `Error: blocked by PreToolUse hook`, blocked post-tool feedback is exactly `blocked by PostToolUse hook`, and a blocking stop adds steering exactly `continue: blocked by Stop hook`. `systemMessage` and `updatedInput` are logged or warned but are not model-visible in this implementation.
|
||||
|
||||
**Token effect**: Blocking a prompt removes that prompt's request tokens; denial or feedback adds the retained fallback or provider text; forced continuation pays another full request.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -56,10 +56,17 @@ Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-codex' }`
|
||||
|
||||
## 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 | Provider-supplied reasons pass through verbatim. When absent, a blocked prompt uses exactly `blocked by UserPromptSubmit hook`, a denied tool becomes `Error: blocked by PreToolUse hook`, blocked post-tool feedback is exactly `blocked by PostToolUse hook`, and a blocking stop adds steering exactly `continue: blocked by Stop hook`. Codex `systemMessage` is not surfaced. | Blocking a prompt removes its request tokens; denial or feedback adds the retained fallback or provider text; forced continuation pays another full request. |
|
||||
### Hook-provided context
|
||||
|
||||
**What the model sees**: `SessionStart`, accepted prompt, and post-tool hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering.
|
||||
|
||||
**Token effect**: No cost when hooks return no context. Hook text is data-dependent, logged, and resent until compaction.
|
||||
|
||||
### Blocked prompt or tool outcome
|
||||
|
||||
**What the model sees**: Provider-supplied reasons pass through verbatim. When absent, a blocked prompt uses exactly `blocked by UserPromptSubmit hook`, a denied tool becomes `Error: blocked by PreToolUse hook`, blocked post-tool feedback is exactly `blocked by PostToolUse hook`, and a blocking stop adds steering exactly `continue: blocked by Stop hook`. Codex `systemMessage` is not surfaced.
|
||||
|
||||
**Token effect**: Blocking a prompt removes its request tokens; denial or feedback adds the retained fallback or provider text; forced continuation pays another full request.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -44,10 +44,17 @@ Unit suites run against a local `node:http` mock SSE server (no network). Real-A
|
||||
|
||||
## 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. |
|
||||
### DeepSeek request
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: Reasoning, text, and raw-string tool arguments are translated into harness chunks for the loop to log and assemble.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -39,10 +39,17 @@ Unit suites run against a local `node:http` mock SSE server (pi-ai's openai SDK
|
||||
|
||||
## 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. |
|
||||
### DeepSeek request through pi-ai
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: Generated content affects later inputs only after the loop records it; adapter conversion adds no model-visible text.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -50,10 +50,17 @@ Two adapters implement `LlmAdapter` against this vocabulary, deliberately built
|
||||
|
||||
## 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. |
|
||||
### Provider request transport
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: Zero direct context tokens. The selected adapter and provider tokenizer determine billing, cache accounting, and serialization overhead for the existing content.
|
||||
|
||||
### Streamed model output
|
||||
|
||||
**What the model sees**: Text, reasoning, and tool-call chunks are exposed to the loop, which decides what becomes retained assistant history.
|
||||
|
||||
**Token effect**: Output usage is provider-reported; later input cost arises only after the loop records assembled content.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -19,9 +19,11 @@ Consumers: [`@deepseek-ai/dsh-bash-sandbox`](../../bash/bash-sandbox/); see [`ex
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Sandbox result facts, indirectly | This provider adds no prompt or tool. It supplies the selected enforcement and denial dialect to `dsh-bash-sandbox`, which can become that consumer's exact `[sandbox: file access denied under <mode> mode]` marker. If no local runner can confine the command, the model instead receives the exact `SANDBOX_UNAVAILABLE` text quoted in [`dsh-sandbox`](../sandbox/README.md). Runner selection and profiles are not shown. | Zero direct tokens; only the conditional marker or error reaches context through the bash consumer. |
|
||||
### Sandbox result facts, indirectly
|
||||
|
||||
**What the model sees**: This provider adds no prompt or tool. It supplies the selected enforcement and denial dialect to `dsh-bash-sandbox`, which can become that consumer's exact `[sandbox: file access denied under <mode> mode]` marker. If no local runner can confine the command, the model instead receives the exact `SANDBOX_UNAVAILABLE` text quoted in [`dsh-sandbox`](../sandbox/README.md). Runner selection and profiles are not shown.
|
||||
|
||||
**Token effect**: Zero direct tokens; only the conditional marker or error reaches context through the bash consumer.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ Implementations: [`@deepseek-ai/dsh-sandbox-local`](../sandbox-local/) (Linux: `
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Sandbox result, indirectly | Through `dsh-bash-sandbox` and `dsh-tool-bash`, enforcement facts may become that consumer's exact denial marker. `SandboxUnavailableError` becomes exactly `Error: sandbox mode "<mode>" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS) — Windows has no confinement backend yet — or switch the consumer to danger-full-access.`, optionally followed by ` Runner failure: <detail>`. | This package adds no prompt or schema. Only a denial or failed confinement adds retained result tokens. |
|
||||
### Sandbox result, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-bash-sandbox` and `dsh-tool-bash`, enforcement facts may become that consumer's exact denial marker. `SandboxUnavailableError` becomes exactly `Error: sandbox mode "<mode>" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS) — Windows has no confinement backend yet — or switch the consumer to danger-full-access.`, optionally followed by ` Runner failure: <detail>`.
|
||||
|
||||
**Token effect**: This package adds no prompt or schema. Only a denial or failed confinement adds retained result tokens.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -32,9 +32,11 @@ The plugin generalizes the example `session-jsonl.ts`: it subscribes to `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. Each unanswered call in an interrupted tail is balanced with the exact error text `Tool call interrupted by a crash; no result was recorded.` Raw `assistant/chunk` records do not duplicate messages. | Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus the quoted repair result for each interrupted call. |
|
||||
### Resumed conversation history
|
||||
|
||||
**What the model sees**: 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. Each unanswered call in an interrupted tail is balanced with the exact error text `Tool call interrupted by a crash; no result was recorded.` Raw `assistant/chunk` records do not duplicate messages.
|
||||
|
||||
**Token effect**: Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus the quoted repair result for each interrupted call.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -31,9 +31,11 @@ Like the JSONL backend, the plugin also installs the `session/event` → buffer
|
||||
|
||||
## 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. Each unanswered call in interrupted rows is balanced with the exact error text `Tool call interrupted by a crash; no result was recorded.` Row metadata and raw chunks are not messages. | Zero live-request tokens. Resume restores retained history and pays the current envelope, plus the quoted repair result for each interrupted call. |
|
||||
### Resumed conversation history
|
||||
|
||||
**What the model sees**: 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. Each unanswered call in interrupted rows is balanced with the exact error text `Tool call interrupted by a crash; no result was recorded.` Row metadata and raw chunks are not messages.
|
||||
|
||||
**Token effect**: Zero live-request tokens. Resume restores retained history and pays the current envelope, plus the quoted repair result for each interrupted call.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -52,9 +52,11 @@ Re-exported from `dsh-session`: `SessionHeader` (immutable session metadata: `ve
|
||||
|
||||
## 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 exactly `Tool call interrupted by a crash; no result was recorded.` as the error result for each unanswered tool call. | Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; each repaired call adds the quoted retained error text. |
|
||||
### Resumed conversation history
|
||||
|
||||
**What the model sees**: 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 exactly `Tool call interrupted by a crash; no result was recorded.` as the error result for each unanswered tool call.
|
||||
|
||||
**Token effect**: Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; each repaired call adds the quoted retained error text.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -38,9 +38,11 @@ Skills can be single-level directory bundles (`<name>/SKILL.md`) or flat Markdow
|
||||
|
||||
## 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. |
|
||||
### Skill catalog and loaded body, indirectly
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -22,18 +22,35 @@ The tool does not call `agent.inject()` in v1. Its result is already recorded as
|
||||
|
||||
## 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 the exact [catalog template](#skill-catalog-template), with one data-dependent entry per sorted skill. The catalog is a frozen user-role session prefix. | 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 | The model sees `skill(name)` with exact description `Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.` and parameter description `The exact skill name from the available skills list.` | Fixed schema cost per request where the tool is visible. |
|
||||
| Tool result | A successful call uses the exact [result template](#skill-result-template) with the exact [provider-managed](#provider-managed-resource-guidance), [directory](#directory-resource-guidance), [URL](#url-resource-guidance), or [opaque](#opaque-resource-guidance) resource guidance. | Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction; no duplicate `agent.inject()` copy is made. |
|
||||
| Tool errors | Invalid or stale selections return exactly `Error: invalid skill name "<name>"`, `Error: skill "<name>" is unknown or no longer available`, or `Error: skill "<name>" is not available for model invocation`. Provider-thrown lookup text is data-dependent and receives the same `Error: <message>` wrapper. | Only a failing call adds these retained tokens. |
|
||||
### Session prefix
|
||||
|
||||
**What the model sees**: If model-invocable skills exist and this exact `skill` tool is visible, the agent receives the exact [catalog template](#skill-catalog-template), with one data-dependent entry per sorted skill. The catalog is a frozen user-role session prefix.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: The model sees `skill(name)` with exact description `Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.` and parameter description `The exact skill name from the available skills list.`
|
||||
|
||||
**Token effect**: Fixed schema cost per request where the tool is visible.
|
||||
|
||||
### Tool result
|
||||
|
||||
**What the model sees**: A successful call uses the exact [result template](#skill-result-template) with the exact [provider-managed](#provider-managed-resource-guidance), [directory](#directory-resource-guidance), [URL](#url-resource-guidance), or [opaque](#opaque-resource-guidance) resource guidance.
|
||||
|
||||
**Token effect**: Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction; no duplicate `agent.inject()` copy is made.
|
||||
|
||||
### Tool errors
|
||||
|
||||
**What the model sees**: Invalid or stale selections return exactly `Error: invalid skill name "<name>"`, `Error: skill "<name>" is unknown or no longer available`, or `Error: skill "<name>" is not available for model invocation`. Provider-thrown lookup text is data-dependent and receives the same `Error: <message>` wrapper.
|
||||
|
||||
**Token effect**: Only a failing call adds these retained tokens.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### Skill catalog template
|
||||
|
||||
```text
|
||||
```markdown
|
||||
<system-reminder>
|
||||
A skill is a reusable set of task-specific instructions. The following skills are available in this session:
|
||||
|
||||
@@ -47,7 +64,7 @@ If the user names a skill, or the task clearly matches a skill's description, ca
|
||||
|
||||
#### Skill result template
|
||||
|
||||
```text
|
||||
```markdown
|
||||
<skill_content name="<escaped-name>">
|
||||
<skill_resources>
|
||||
<resource-guidance>
|
||||
@@ -61,28 +78,28 @@ If the user names a skill, or the task clearly matches a skill's description, ca
|
||||
|
||||
#### Provider-managed resource guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Resources for this skill are managed by provider "<provider>".
|
||||
Load referenced resources only as needed.
|
||||
```
|
||||
|
||||
#### Directory resource guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Base directory for this skill: <path>
|
||||
Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed.
|
||||
```
|
||||
|
||||
#### URL resource guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Base URL for this skill: <url>
|
||||
Resolve relative URLs mentioned by this skill against the base URL before using them. Load referenced resources only as needed.
|
||||
```
|
||||
|
||||
#### Opaque resource guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Resources for this skill: <description>
|
||||
Load referenced resources only as needed.
|
||||
```
|
||||
|
||||
@@ -59,10 +59,17 @@ Keyless tests drive a scripted ACP subprocess over real stdio. The with-key e2e
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Child-agent request | The remote child receives the standalone task content through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation. This provider advertises no optional start-time capabilities, so the local service rejects requests for persona, tool filtering, depth enforcement, or structured output instead of silently omitting them. | 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 that consumer's exact stop-reason error, not intermediate messages or tool traffic. A request already cancelled before publication becomes exactly `Error: subagent request was aborted before the ACP child started`; other start failures pass through as `Error: <message>`. | Parent input grows only by the final result or error, which is data-dependent and retained until compaction. This provider adds no parent schema itself. |
|
||||
### Child-agent request
|
||||
|
||||
**What the model sees**: The remote child receives the standalone task content through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation. This provider advertises no optional start-time capabilities, so the local service rejects requests for persona, tool filtering, depth enforcement, or structured output instead of silently omitting them.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or that consumer's exact stop-reason error, not intermediate messages or tool traffic. A request already cancelled before publication becomes exactly `Error: subagent request was aborted before the ACP child started`; other start failures pass through as `Error: <message>`.
|
||||
|
||||
**Token effect**: Parent input grows only by the final result or error, which is data-dependent and retained until compaction. This provider adds no parent schema itself.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -25,10 +25,17 @@ See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, m
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Child-agent history and envelope | The child receives the parent's balanced completed-turn surface prefix, then the new task content verbatim. A configured persona shadows prompt text in the child's fresh scope; a tool restriction filters its global wire schemas, executable lookup, and Code Mode SDK bindings but not standalone guidance. The parent's tool view and authority are not inherited. An optional structured-output request adds its child-only contract. The parent's current in-flight turn is excluded. | Forking duplicates retained completed history into separate child requests; the child then accumulates its own tokens independently. Persona changes repeated prompt cost, filtering changes schema or generated SDK cost, and 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. |
|
||||
### Child-agent history and envelope
|
||||
|
||||
**What the model sees**: The child receives the parent's balanced completed-turn surface prefix, then the new task content verbatim. A configured persona shadows prompt text in the child's fresh scope; a tool restriction filters its global wire schemas, executable lookup, and Code Mode SDK bindings but not standalone guidance. The parent's tool view and authority are not inherited. An optional structured-output request adds its child-only contract. The parent's current in-flight turn is excluded.
|
||||
|
||||
**Token effect**: Forking duplicates retained completed history into separate child requests; the child then accumulates its own tokens independently. Persona changes repeated prompt cost, filtering changes schema or generated SDK cost, and a first-turn fork has no inherited history.
|
||||
|
||||
### Parent tool result, indirectly
|
||||
|
||||
**What the model sees**: The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work.
|
||||
|
||||
**Token effect**: Parent input grows by one data-dependent final result retained until compaction.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -42,18 +42,35 @@ A clean turn that never commits the required structured value reports `error`; t
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Child-agent request | The shared driver sends the task verbatim as the child's user message and, when requested, shadows the persona and restricts global tool schemas, lookup, execution, and Code Mode SDK bindings in the unpublished child's fresh scope; parent restrictions are not inherited, and standalone tool-guidance sections remain. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. A persona changes repeated prompt text; filtering changes schema or generated SDK cost but not independently registered guidance. |
|
||||
| Structured child request and results | A structured run adds the exact [structured-output instruction](#structured-output-instruction). The tool description is exactly `Report your final structured result. Call this exactly once, when your answer is complete; the arguments must match this tool's parameter schema exactly.` with the requested schema. Success returns `Structured output recorded.`; a later call becomes ``Error: structured output already recorded: the run is complete, so `<tool>` is not executed``. | Fixed instruction and capability tokens are paid only by that child. Result text enters the child history, while the captured value alone becomes the parent result. |
|
||||
| Parent start error, indirectly | Through `dsh-tool-subagent`, invalid depth state becomes exactly `Error: agent subagentDepth must be a non-negative safe integer`, `Error: subagent child depth exceeds the safe-integer range`, or `Error: subagent depth <attempted> exceeds maxDepth <max>`. A pre-publication cancellation passes its abort reason through the registry's `Error: <message>` wrapper. | Zero tokens on a successful start; only the failed parent tool call retains this text. |
|
||||
| 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. |
|
||||
### Child-agent request
|
||||
|
||||
**What the model sees**: The shared driver sends the task verbatim as the child's user message and, when requested, shadows the persona and restricts global tool schemas, lookup, execution, and Code Mode SDK bindings in the unpublished child's fresh scope; parent restrictions are not inherited, and standalone tool-guidance sections remain. Spawn supplies no history; fork supplies its balanced seed.
|
||||
|
||||
**Token effect**: Child input is isolated from the parent and grows through the child's own steps. A persona changes repeated prompt text; filtering changes schema or generated SDK cost but not independently registered guidance.
|
||||
|
||||
### Structured child request and results
|
||||
|
||||
**What the model sees**: A structured run adds the exact [structured-output instruction](#structured-output-instruction). The tool description is exactly `Report your final structured result. Call this exactly once, when your answer is complete; the arguments must match this tool's parameter schema exactly.` with the requested schema. Success returns `Structured output recorded.`; a later call becomes ``Error: structured output already recorded: the run is complete, so `<tool>` is not executed``.
|
||||
|
||||
**Token effect**: Fixed instruction and capability tokens are paid only by that child. Result text enters the child history, while the captured value alone becomes the parent result.
|
||||
|
||||
### Parent start error, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-subagent`, invalid depth state becomes exactly `Error: agent subagentDepth must be a non-negative safe integer`, `Error: subagent child depth exceeds the safe-integer range`, or `Error: subagent depth <attempted> exceeds maxDepth <max>`. A pre-publication cancellation passes its abort reason through the registry's `Error: <message>` wrapper.
|
||||
|
||||
**Token effect**: Zero tokens on a successful start; only the failed parent tool call retains this text.
|
||||
|
||||
### Parent result, indirectly
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### Structured-output instruction
|
||||
|
||||
```text
|
||||
```markdown
|
||||
When you have your final answer, you MUST report it by calling the `structured_output` tool with arguments matching its parameter schema exactly. Do not finish with a plain text answer: only the tool call counts as your result.
|
||||
```
|
||||
|
||||
|
||||
@@ -20,10 +20,17 @@ Spawn advertises `{ outputSchema: true, depthLimit: true, toolFilter: true, pers
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Child-agent request | The fresh child receives the standalone task content verbatim, inherits the parent model and workspace by default, and sees the global prompt with any configured child-scoped persona shadow. A tool filter removes global wire schemas, executable lookup, and Code Mode SDK bindings for that child but leaves independently registered guidance. It receives zero parent conversation messages; the filter is visibility/composition, not an authority grant inherited from the parent. | The child pays for a new independent context and history; no parent-history tokens are duplicated. Persona changes this child's repeated prompt cost, while filtering changes its schema or generated SDK cost. |
|
||||
| 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. |
|
||||
### Child-agent request
|
||||
|
||||
**What the model sees**: The fresh child receives the standalone task content verbatim, inherits the parent model and workspace by default, and sees the global prompt with any configured child-scoped persona shadow. A tool filter removes global wire schemas, executable lookup, and Code Mode SDK bindings for that child but leaves independently registered guidance. It receives zero parent conversation messages; the filter is visibility/composition, not an authority grant inherited from the parent.
|
||||
|
||||
**Token effect**: The child pays for a new independent context and history; no parent-history tokens are duplicated. Persona changes this child's repeated prompt cost, while filtering changes its schema or generated SDK cost.
|
||||
|
||||
### Parent tool result, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error.
|
||||
|
||||
**Token effect**: Parent input grows by one data-dependent result retained until compaction.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -62,9 +62,11 @@ The current model-facing tool collects synchronously: it awaits the child result
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Delegation result, indirectly | Through `dsh-tool-subagent`, registered providers create child contexts and return data-dependent final output. A missing provider or unsupported requested capability becomes exactly `Error: no subagent provider registered for "<name>"` or `Error: subagent provider "<name>" does not support the "<capability>" capability`. Provider-specific start errors receive the same `Error: <message>` wrapper. | This seam adds no parent schema itself. The parent retains only the final output or start error; child working tokens remain in the child. |
|
||||
### Delegation result, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-subagent`, registered providers create child contexts and return data-dependent final output. A missing provider or unsupported requested capability becomes exactly `Error: no subagent provider registered for "<name>"` or `Error: subagent provider "<name>" does not support the "<capability>" capability`. Provider-specific start errors receive the same `Error: <message>` wrapper.
|
||||
|
||||
**Token effect**: This seam adds no parent schema itself. The parent retains only the final output or start error; child working tokens remain in the child.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -29,35 +29,47 @@ A non-`completed` stop reason becomes an `isError` tool result; partial child ou
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Standalone-provider schema | While a fresh-context provider exists, the configured tool uses the exact [standalone tool](#standalone-provider-tool-description) and [`prompt` parameter](#standalone-provider-prompt-description) descriptions. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema. |
|
||||
| Inherited-context-provider schema | A provider that seeds completed turns uses the exact [inherited-context tool](#inherited-context-provider-tool-description) and [`prompt` parameter](#inherited-context-provider-prompt-description) descriptions. Both variants describe `description` exactly as `A short (3-5 word) description of the delegated task, for display.` | Fixed schema cost per parent request while mounted. 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. Success contains only the child's data-dependent final text. Other stop reasons become exactly `Error: subagent run was cancelled`, `Error: subagent run failed`, `Error: subagent run hit its token limit before finishing`, `Error: subagent declined the task`, or `Error: subagent run ended abnormally (<reason>)`; a call without an owning agent becomes `Error: subagent tool requires a calling agent (exec.agent was undefined)`. Intermediate child steps never enter the parent. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. |
|
||||
### Standalone-provider schema
|
||||
|
||||
**What the model sees**: While a fresh-context provider exists, the configured tool uses the exact [standalone tool](#standalone-provider-tool-description) and [`prompt` parameter](#standalone-provider-prompt-description) descriptions.
|
||||
|
||||
**Token effect**: Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema.
|
||||
|
||||
### Inherited-context-provider schema
|
||||
|
||||
**What the model sees**: A provider that seeds completed turns uses the exact [inherited-context tool](#inherited-context-provider-tool-description) and [`prompt` parameter](#inherited-context-provider-prompt-description) descriptions. Both variants describe `description` exactly as `A short (3-5 word) description of the delegated task, for display.`
|
||||
|
||||
**Token effect**: Fixed schema cost per parent request while mounted. Exposing multiple providers adds one independently named schema per load.
|
||||
|
||||
### Tool-call history and result
|
||||
|
||||
**What the model sees**: The task description and full prompt remain in the parent assistant tool call. Success contains only the child's data-dependent final text. Other stop reasons become exactly `Error: subagent run was cancelled`, `Error: subagent run failed`, `Error: subagent run hit its token limit before finishing`, `Error: subagent declined the task`, or `Error: subagent run ended abnormally (<reason>)`; a call without an owning agent becomes `Error: subagent tool requires a calling agent (exec.agent was undefined)`. Intermediate child steps never enter the parent.
|
||||
|
||||
**Token effect**: Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### Standalone-provider tool description
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation.
|
||||
```
|
||||
|
||||
#### Standalone-provider prompt description
|
||||
|
||||
```text
|
||||
```markdown
|
||||
The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs.
|
||||
```
|
||||
|
||||
#### Inherited-context-provider tool description
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Delegate a task to a subagent that INHERITS this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn), returning only its final result. Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive only its final answer, not its intermediate steps.
|
||||
```
|
||||
|
||||
#### Inherited-context-provider prompt description
|
||||
|
||||
```text
|
||||
```markdown
|
||||
The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new.
|
||||
```
|
||||
|
||||
|
||||
@@ -46,9 +46,11 @@ Named `name` / `inject` / `Config` / `apply`, with **no default export**: the co
|
||||
|
||||
## 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. |
|
||||
### Keyless test stream
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: Zero billed or tokenizer-evaluated model tokens. Fixture output creates deterministic retained test context for later replay steps.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ Aborting the required request signal or disposing before `result` settles flips
|
||||
|
||||
## 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. |
|
||||
### Deterministic test result
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: Zero child-model tokens. Only the scripted final result is added to the parent test history.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -35,9 +35,11 @@ Multiple `tools/execute` listeners compose by cordis registration order. Combine
|
||||
|
||||
## 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>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. |
|
||||
### Conditional tool result
|
||||
|
||||
**What the model sees**: 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>ms` plus structured `TOOL_TIMEOUT`; otherwise the original result passes through unchanged.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -26,16 +26,23 @@ A function/namespace plugin: it exports `name` / `inject` / `apply` and NO defau
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Tool schema | The model sees `todo_write` with the exact [tool description](#todo_write-tool-description). The `todos` parameter says `The COMPLETE task list, replacing any previous list.`; each item uses `What the task is — a short imperative line.` and `pending (not started) \| in_progress (now) \| completed (done).` | 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. Success returns exactly `Updated todo list: <pending> pending, <inProgress> in progress, <completed> completed.` Stable failures are ``Error: invalid todo: `content` must be a non-empty string``, `Error: invalid todos: duplicate content "<content>"`, `Error: invalid todos: at most one task may be in_progress, got <count>`, and `Error: todo_write requires an owning agent session`. 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. |
|
||||
### Tool schema
|
||||
|
||||
**What the model sees**: The model sees `todo_write` with the exact [tool description](#todo_write-tool-description). The `todos` parameter says `The COMPLETE task list, replacing any previous list.`; each item uses `What the task is — a short imperative line.` and `pending (not started) | in_progress (now) | completed (done).`
|
||||
|
||||
**Token effect**: Fixed schema cost on every request where the tool is visible.
|
||||
|
||||
### Tool-call history and result
|
||||
|
||||
**What the model sees**: Each assistant tool call retains the entire replacement list in its arguments. Success returns exactly `Updated todo list: <pending> pending, <inProgress> in progress, <completed> completed.` Stable failures are ``Error: invalid todo: `content` must be a non-empty string``, `Error: invalid todos: duplicate content "<content>"`, `Error: invalid todos: at most one task may be in_progress, got <count>`, and `Error: todo_write requires an owning agent session`. The full `todo/write` session event is UI and replay state, not a second model message.
|
||||
|
||||
**Token effect**: 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.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### `todo_write` tool description
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Keep AT MOST ONE todo `in_progress` at a time; while work remains, exactly one active task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).
|
||||
```
|
||||
|
||||
|
||||
@@ -48,9 +48,11 @@ 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. |
|
||||
### Composed ACP agent request
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -102,11 +102,23 @@ 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 verbatim and each `resource_link` becomes exactly a leading newline, `[resource_link name=<JSON-string> uri=<JSON-string>]`, and a trailing newline. 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 the exact JSON shape documented by `dsh-tool-ask-user`. Failures become `Error: ACP user questions must come from an agent-owned request`, `Error: ACP user question has no matching session`, `Error: ACP elicitation request failed`, `Error: ask_user_question was cancelled by the user`, `Error: ask_user_question returned no answer`, or `Error: ask_user_question was aborted before the user answered`. Permission decisions control whether another tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only. | Answer, error, 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. |
|
||||
### User messages
|
||||
|
||||
**What the model sees**: Each ACP `session/prompt` becomes an agent user message: text passes through verbatim and each `resource_link` becomes exactly a leading newline, `[resource_link name=<JSON-string> uri=<JSON-string>]`, and a trailing newline. Unsupported image, audio, and embedded-resource blocks are rejected rather than silently omitted.
|
||||
|
||||
**Token effect**: Prompt tokens are data-dependent and remain in that session's history until compaction. Concurrent ACP sessions keep separate contexts.
|
||||
|
||||
### Human answers and permissions
|
||||
|
||||
**What the model sees**: When optional consumers are loaded, ACP form answers become the exact JSON shape documented by `dsh-tool-ask-user`. Failures become `Error: ACP user questions must come from an agent-owned request`, `Error: ACP user question has no matching session`, `Error: ACP elicitation request failed`, `Error: ask_user_question was cancelled by the user`, `Error: ask_user_question returned no answer`, or `Error: ask_user_question was aborted before the user answered`. Permission decisions control whether another tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only.
|
||||
|
||||
**Token effect**: Answer, error, and denial text enters context only through the owning tool result; presentation metadata adds zero model tokens.
|
||||
|
||||
### Loaded sessions
|
||||
|
||||
**What the model sees**: `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.
|
||||
|
||||
**Token effect**: Restored context has the persistence and session packages' normal retained cost; ACP replay to the client adds none.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -68,10 +68,17 @@ Swap `llm-deepseek` for a `mock-llm` leaf plugin and you have the echo demo —
|
||||
|
||||
## 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. |
|
||||
| Human-answer result | Through `dsh-tool-ask-user`, successful terminal answers use that package's exact compact JSON shape. Interruption becomes exactly `Error: ask_user_question was interrupted before the user answered`; a closed stdin becomes `Error: ask_user_question cannot be answered because stdin is closed`. | Only a completed or failed tool call adds retained result tokens; prompts printed while waiting are terminal-only. |
|
||||
### Composed terminal agent request
|
||||
|
||||
**What the model sees**: 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.
|
||||
|
||||
**Token effect**: 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.
|
||||
|
||||
### Human-answer result
|
||||
|
||||
**What the model sees**: Through `dsh-tool-ask-user`, successful terminal answers use that package's exact compact JSON shape. Interruption becomes exactly `Error: ask_user_question was interrupted before the user answered`; a closed stdin becomes `Error: ask_user_question cannot be answered because stdin is closed`.
|
||||
|
||||
**Token effect**: Only a completed or failed tool call adds retained result tokens; prompts printed while waiting are terminal-only.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -21,10 +21,17 @@ This is the consumer package for the user-interaction seam. It does not render U
|
||||
|
||||
## 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 compact JSON in the exact shape `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}`; `custom` is omitted when unused and `selected` can contain zero, one, or several labels. 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 schema
|
||||
|
||||
**What the model sees**: The model sees `ask_user_question` with question ids, prompts, headings, options, and multi-select flags.
|
||||
|
||||
**Token effect**: Fixed schema cost on every request where the tool is visible.
|
||||
|
||||
### Tool-call history and result
|
||||
|
||||
**What the model sees**: The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees compact JSON in the exact shape `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}`; `custom` is omitted when unused and `selected` can contain zero, one, or several labels. UI interaction while the call is pending is not model context.
|
||||
|
||||
**Token effect**: Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -14,10 +14,17 @@ Answerers today: the ACP bridge ([`@deepseek-ai/dsh-acp`](../../ui/acp/)) forwar
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| System prompt and policy notice | Under `ask`, every agent request carries exactly `<!-- dsh-user-approval-policy:ask -->`. Under `never`, it carries exactly ``Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).`` followed by a newline and `<!-- dsh-user-approval-policy:never -->`. A policy switch injects exactly `The approval policy changed from "<old>" to "<new>" (changed by the user).` or `The approval policy changed from "<old>" to "<new>" (changed by the operator/config).` before the next step. | Small fixed per-request cost, larger under `never`; a change notice is conditional and retained in history. |
|
||||
| Tool outcome | `approval/asked` and `approval/decided` are log-only. The model sees only the asking consumer's eventual allowed, rejected, cancelled, or unavailable tool outcome; the human permission UI is not context. | Zero duplicate audit tokens. A rejection may replace a normal tool result with a small retained error, while an allowance leaves the consumer's ordinary result. |
|
||||
### System prompt and policy notice
|
||||
|
||||
**What the model sees**: Under `ask`, every agent request carries exactly `<!-- dsh-user-approval-policy:ask -->`. Under `never`, it carries exactly ``Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).`` followed by a newline and `<!-- dsh-user-approval-policy:never -->`. A policy switch injects exactly `The approval policy changed from "<old>" to "<new>" (changed by the user).` or `The approval policy changed from "<old>" to "<new>" (changed by the operator/config).` before the next step.
|
||||
|
||||
**Token effect**: Small fixed per-request cost, larger under `never`; a change notice is conditional and retained in history.
|
||||
|
||||
### Tool outcome
|
||||
|
||||
**What the model sees**: `approval/asked` and `approval/decided` are log-only. The model sees only the asking consumer's eventual allowed, rejected, cancelled, or unavailable tool outcome; the human permission UI is not context.
|
||||
|
||||
**Token effect**: Zero duplicate audit tokens. A rejection may replace a normal tool result with a small retained error, while an allowance leaves the consumer's ordinary result.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -25,9 +25,11 @@ This is the interface package. Model-facing consumers such as `@deepseek-ai/dsh-
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Human-answer result, indirectly | Through `dsh-tool-ask-user`, a successful provider answer becomes that consumer's exact compact JSON result. Seam-level failures become exactly `Error: ask_user_question was aborted before the user answered`, `Error: ask_user_question requires at least one question`, or `Error: no user-interaction provider is registered`; provider-owned failures receive the same `Error: <message>` wrapper. | This seam adds no prompt or schema. Only the consumer's completed or failed tool call adds retained tokens; waiting for the human adds none. |
|
||||
### Human-answer result, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-ask-user`, a successful provider answer becomes that consumer's exact compact JSON result. Seam-level failures become exactly `Error: ask_user_question was aborted before the user answered`, `Error: ask_user_question requires at least one question`, or `Error: no user-interaction provider is registered`; provider-owned failures receive the same `Error: <message>` wrapper.
|
||||
|
||||
**Token effect**: This seam adds no prompt or schema. Only the consumer's completed or failed tool call adds retained tokens; waiting for the human adds none.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -36,25 +36,47 @@ The tool never calls a provider's `status()` and never enumerates providers —
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| System prompt | Search and fetch contribute the exact [web-search](#web-search-guidance) and [web-fetch](#web-fetch-guidance) guidance. A scoped tool restriction does not remove these independently registered sections. | Fixed guidance cost per request for each config-enabled tool, even when a restriction hides its schema. |
|
||||
| Tool schemas | `web_search` has exact description `Search the web for current information. Returns an optional summary answer and a list of source URLs.` and query description `The search query.` `web_fetch` has exact description `Fetch the content of a specific HTTP(S) URL and return it decoded to text.` and URL description `The HTTP(S) URL to fetch.` Result-count and timeout budgets are deployment settings, not model arguments. | Fixed schema cost per request; config disablement removes both schema and guidance, while a scoped restriction removes only the schema. |
|
||||
| Search result | The optional provider-owned answer is followed by `Sources:` and data-dependent lines shaped exactly `- [<title-or-url>](<url>)`, optionally suffixed ` — <snippet> (<publishedAt>)`. With neither answer nor sources the result says `No results found.` A capped list adds `(Showing the first <count> sources. Refine the query for more.)`; every result ends `Cite the relevant URLs above as markdown links in your answer.` | Data-dependent results are resent until compaction and sources are capped by `searchMaxResults`. |
|
||||
| Fetch result | A successful fetch is exactly `Fetched <finalUrl> (HTTP <statusCode>)`, a blank line, and the provider-owned decoded body. Truncation adds a blank line and `(Content truncated. Fetch a more specific URL or section for the full text.)`; failures become `Error: <message>`. Queries and URLs remain in call history. | Provider caps bound body size; retained call arguments and results are resent until compaction, and timeout policy can replace a late result with a short error. |
|
||||
| Argument errors | Blank inputs become exactly `Error: query must be a non-empty string` or `Error: url must be a non-empty string`. | Only the failing call adds these retained tokens. |
|
||||
### System prompt
|
||||
|
||||
**What the model sees**: Search and fetch contribute the exact [web-search](#web-search-guidance) and [web-fetch](#web-fetch-guidance) guidance. A scoped tool restriction does not remove these independently registered sections.
|
||||
|
||||
**Token effect**: Fixed guidance cost per request for each config-enabled tool, even when a restriction hides its schema.
|
||||
|
||||
### Tool schemas
|
||||
|
||||
**What the model sees**: `web_search` has exact description `Search the web for current information. Returns an optional summary answer and a list of source URLs.` and query description `The search query.` `web_fetch` has exact description `Fetch the content of a specific HTTP(S) URL and return it decoded to text.` and URL description `The HTTP(S) URL to fetch.` Result-count and timeout budgets are deployment settings, not model arguments.
|
||||
|
||||
**Token effect**: Fixed schema cost per request; config disablement removes both schema and guidance, while a scoped restriction removes only the schema.
|
||||
|
||||
### Search result
|
||||
|
||||
**What the model sees**: The optional provider-owned answer is followed by `Sources:` and data-dependent lines shaped exactly `- [<title-or-url>](<url>)`, optionally suffixed ` — <snippet> (<publishedAt>)`. With neither answer nor sources the result says `No results found.` A capped list adds `(Showing the first <count> sources. Refine the query for more.)`; every result ends `Cite the relevant URLs above as markdown links in your answer.`
|
||||
|
||||
**Token effect**: Data-dependent results are resent until compaction and sources are capped by `searchMaxResults`.
|
||||
|
||||
### Fetch result
|
||||
|
||||
**What the model sees**: A successful fetch is exactly `Fetched <finalUrl> (HTTP <statusCode>)`, a blank line, and the provider-owned decoded body. Truncation adds a blank line and `(Content truncated. Fetch a more specific URL or section for the full text.)`; failures become `Error: <message>`. Queries and URLs remain in call history.
|
||||
|
||||
**Token effect**: Provider caps bound body size; retained call arguments and results are resent until compaction, and timeout policy can replace a late result with a short error.
|
||||
|
||||
### Argument errors
|
||||
|
||||
**What the model sees**: Blank inputs become exactly `Error: query must be a non-empty string` or `Error: url must be a non-empty string`.
|
||||
|
||||
**Token effect**: Only the failing call adds these retained tokens.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### Web search guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Use the web_search tool to discover current information on the web. It returns an optional answer plus a list of source URLs. Follow up with web_fetch when you need the full content of a specific result, and cite the relevant URLs as markdown links.
|
||||
```
|
||||
|
||||
#### Web fetch guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Use the web_fetch tool to retrieve the content of a specific HTTP(S) URL (for example a result from web_search). It returns the page content decoded to text. Cite the URL as a markdown link when you use its content.
|
||||
```
|
||||
|
||||
|
||||
@@ -35,9 +35,11 @@ The numeric limits are validated at plugin construction: every cap except `maxRe
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Web fetch result, indirectly | Through `dsh-tool-web`, the conversation model sees that consumer's exact fetch header plus the data-dependent decoded text or markdown-shaped HTML. This provider's stable failures become `Error: <message>`; messages include `web fetch aborted`, `web fetch timed out`, `web fetch failed: <error>`, `exceeded the maximum of <count> redirects`, `redirect response (HTTP <status>) without a Location header`, `invalid redirect Location "<location>"`, `cross-origin redirect to <origin> is not followed automatically; retry against that URL directly`, `unsupported content type "<type>"`, `response exceeds the maximum of <bytes> bytes`, `URL exceeds the maximum length of <chars>`, `invalid URL: <url>`, `unsupported URL scheme "<scheme>" (only http and https are allowed)`, `credentials in URLs are not allowed`, and `unsupported charset "<charset>"`. Redirects, headers, and transport mechanics are otherwise absent. | Zero direct tokens. `maxBodyChars` bounds decoded result length before the tool records it; the retained result or error is resent until compaction. |
|
||||
### Web fetch result, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-web`, the conversation model sees that consumer's exact fetch header plus the data-dependent decoded text or markdown-shaped HTML. This provider's stable failures become `Error: <message>`; messages include `web fetch aborted`, `web fetch timed out`, `web fetch failed: <error>`, `exceeded the maximum of <count> redirects`, `redirect response (HTTP <status>) without a Location header`, `invalid redirect Location "<location>"`, `cross-origin redirect to <origin> is not followed automatically; retry against that URL directly`, `unsupported content type "<type>"`, `response exceeds the maximum of <bytes> bytes`, `URL exceeds the maximum length of <chars>`, `invalid URL: <url>`, `unsupported URL scheme "<scheme>" (only http and https are allowed)`, `credentials in URLs are not allowed`, and `unsupported charset "<charset>"`. Redirects, headers, and transport mechanics are otherwise absent.
|
||||
|
||||
**Token effect**: Zero direct tokens. `maxBodyChars` bounds decoded result length before the tool records it; the retained result or error is resent until compaction.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -37,10 +37,17 @@ DeepSeek returns no provider-generated answer surface this provider trusts as `c
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Auxiliary DeepSeek search request | A separate DeepSeek model receives exactly `Perform a web search for the query: <query>` as its user text and one 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. Provider failures become `Error: DeepSeek search aborted`, `Error: DeepSeek search request failed: <error>`, `Error: DeepSeek returned no web_search_tool_result blocks; the request may not have triggered native web search`, or `Error: DeepSeek returned an unprocessable response body: <error>`; HTTP failures pass through their provider message after `Error:`. | Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the seam enforces the requested source bound. |
|
||||
### Auxiliary DeepSeek search request
|
||||
|
||||
**What the model sees**: A separate DeepSeek model receives exactly `Perform a web search for the query: <query>` as its user text and one native `web_search` server-tool definition. This request is not part of the conversation model's context.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
**What the model sees**: 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. Provider failures become `Error: DeepSeek search aborted`, `Error: DeepSeek search request failed: <error>`, `Error: DeepSeek returned no web_search_tool_result blocks; the request may not have triggered native web search`, or `Error: DeepSeek returned an unprocessable response body: <error>`; HTTP failures pass through their provider message after `Error:`.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -27,9 +27,11 @@ Exa returns a flat `results[]` and no generated answer, so `content` is omitted.
|
||||
|
||||
## 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. Failures become `Error: Exa search aborted`, `Error: Exa search request failed: <error>`, or `Error: Exa returned an unprocessable response body: <error>`; HTTP failures pass through their provider message after `Error:`. | Zero direct harness-model tokens. Result size scales with the bounded source list and snippets; the seam enforces `maxResults`, and retained results or errors remain until compaction. |
|
||||
### Web search result, indirectly
|
||||
|
||||
**What the model sees**: 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. Failures become `Error: Exa search aborted`, `Error: Exa search request failed: <error>`, or `Error: Exa returned an unprocessable response body: <error>`; HTTP failures pass through their provider message after `Error:`.
|
||||
|
||||
**Token effect**: Zero direct harness-model tokens. Result size scales with the bounded source list and snippets; the seam enforces `maxResults`, and retained results or errors remain until compaction.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -27,10 +27,17 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Auxiliary Perplexity request | A separate Perplexity model receives `<query>` verbatim as its sole user message through the 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. Failures become `Error: Perplexity search aborted`, `Error: Perplexity search request failed: <error>`, or `Error: Perplexity returned an unprocessable response body: <error>`; HTTP failures pass through their provider message after `Error:`. | Zero direct conversation tokens from registration. Answer and source tokens are data-dependent, source count is seam-bounded, and the retained result or error is resent until compaction. |
|
||||
### Auxiliary Perplexity request
|
||||
|
||||
**What the model sees**: A separate Perplexity model receives `<query>` verbatim as its sole user message through the chat-completions endpoint. This request is not part of the conversation model's context.
|
||||
|
||||
**Token effect**: Separate provider tokens are incurred per search; `maxTokens` caps the generated answer.
|
||||
|
||||
### Conversation tool result, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-web`, the conversation model sees the generated answer plus structured result metadata or URL-only citations. Failures become `Error: Perplexity search aborted`, `Error: Perplexity search request failed: <error>`, or `Error: Perplexity returned an unprocessable response body: <error>`; HTTP failures pass through their provider message after `Error:`.
|
||||
|
||||
**Token effect**: Zero direct conversation tokens from registration. Answer and source tokens are data-dependent, source count is seam-bounded, and the retained result or error is resent until compaction.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -45,9 +45,11 @@ The failure branches throw `WebError`, whose structured code (plus message detai
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Search or fetch result, indirectly | Through `dsh-tool-web`, the model sees bounded normalized provider data. Selection failures become exactly `Error: configured web provider "<id>" is not registered`, `Error: configured web provider "<id>" is registered but unavailable`, `Error: no usable web provider is registered`, or `Error: multiple usable web providers are registered (<ids>); configure one explicitly`; provider failures use the same `Error: <message>` wrapper. | This seam adds no prompt or schema. Only a call result or error adds retained tokens; source caps bound search output. |
|
||||
### Search or fetch result, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-web`, the model sees bounded normalized provider data. Selection failures become exactly `Error: configured web provider "<id>" is not registered`, `Error: configured web provider "<id>" is registered but unavailable`, `Error: no usable web provider is registered`, or `Error: multiple usable web providers are registered (<ids>); configure one explicitly`; provider failures use the same `Error: <message>` wrapper.
|
||||
|
||||
**Token effect**: This seam adds no prompt or schema. Only a call result or error adds retained tokens; source caps bound search output.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -23,17 +23,29 @@ Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/arch
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| System prompt | Every parent request in this plugin's registration scope receives the exact [workflow guidance](#workflow-guidance). A scoped tool restriction can hide the schema without removing this independently registered guidance. | Small fixed guidance cost per request while the plugin is active. |
|
||||
| Tool schema | When visible, the `workflow` schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial fixed schema cost on each request where the tool is visible. |
|
||||
| Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. Success is exactly `workflow "<name>" completed (<count> agent<optional-s>).`, newline, `Return value:`, newline, and pretty-printed data-dependent JSON; a cap adds `… [truncated: <omitted> more characters]` on a new line. Failures are exactly `Error: workflow run was cancelled`, optionally suffixed ` (<error>)`, `Error: workflow run failed: <error-or-unknown error>`, or defensively `Error: workflow run ended abnormally (<reason>)`; a call without an owning agent becomes `Error: workflow tool requires a calling agent (exec.agent was undefined)`. 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. |
|
||||
### System prompt
|
||||
|
||||
**What the model sees**: Every parent request in this plugin's registration scope receives the exact [workflow guidance](#workflow-guidance). A scoped tool restriction can hide the schema without removing this independently registered guidance.
|
||||
|
||||
**Token effect**: Small fixed guidance cost per request while the plugin is active.
|
||||
|
||||
### Tool schema
|
||||
|
||||
**What the model sees**: When visible, the `workflow` schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args.
|
||||
|
||||
**Token effect**: Substantial fixed schema cost on each request where the tool is visible.
|
||||
|
||||
### Tool-call history and result
|
||||
|
||||
**What the model sees**: The full model-written script, metadata, and args remain in the assistant tool call. Success is exactly `workflow "<name>" completed (<count> agent<optional-s>).`, newline, `Return value:`, newline, and pretty-printed data-dependent JSON; a cap adds `… [truncated: <omitted> more characters]` on a new line. Failures are exactly `Error: workflow run was cancelled`, optionally suffixed ` (<error>)`, `Error: workflow run failed: <error-or-unknown error>`, or defensively `Error: workflow run ended abnormally (<reason>)`; a call without an owning agent becomes `Error: workflow tool requires a calling agent (exec.agent was undefined)`. Intermediate child messages are omitted.
|
||||
|
||||
**Token effect**: 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.
|
||||
|
||||
### Verbatim model-visible text
|
||||
|
||||
#### Workflow guidance
|
||||
|
||||
```text
|
||||
```markdown
|
||||
Use the <toolName> tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.
|
||||
```
|
||||
|
||||
|
||||
@@ -81,10 +81,17 @@ The host keeps a ledger of forwarded child starts. A graceful worker supplies th
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Child-agent requests | Every script `agent()` call sends its prompt verbatim 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 | Through `dsh-tool-workflow`, success exposes only the materialized final JSON value and child count in that consumer's exact wrapper. An engine failure becomes exactly `Error: workflow run failed: <engine-error>`; stable engine-error shapes include `workflow script does not parse: <error>`, `invalid meta: <violations>`, `agent() requires a non-empty prompt string`, `agent() could not start a child: <error>`, `child agent run failed: <error>`, and the exact `parallel()`, `pipeline()`, `phase()`, option, schema, and JSON-boundary validation messages from this package. 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. |
|
||||
### Child-agent requests
|
||||
|
||||
**What the model sees**: Every script `agent()` call sends its prompt verbatim 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.
|
||||
|
||||
**Token effect**: Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly.
|
||||
|
||||
### Parent tool result, indirectly
|
||||
|
||||
**What the model sees**: Through `dsh-tool-workflow`, success exposes only the materialized final JSON value and child count in that consumer's exact wrapper. An engine failure becomes exactly `Error: workflow run failed: <engine-error>`; stable engine-error shapes include `workflow script does not parse: <error>`, `invalid meta: <violations>`, `agent() requires a non-empty prompt string`, `agent() could not start a child: <error>`, `child agent run failed: <error>`, and the exact `parallel()`, `pipeline()`, `phase()`, option, schema, and JSON-boundary validation messages from this package. Intermediate child outputs are available to the script but not the parent model.
|
||||
|
||||
**Token effect**: 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
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Doc-sync gate: require every workspace package README to explain its exact
|
||||
* model-visible context surface and token behavior. Most packages require the
|
||||
* canonical table plus an optional linked long-literal appendix; an audited
|
||||
* allowlist requires one concise zero-effect or indirect-only sentence instead.
|
||||
* canonical context-surface blocks plus an optional linked long-literal
|
||||
* appendix; an audited allowlist requires one concise zero-effect or
|
||||
* indirect-only sentence instead.
|
||||
*
|
||||
* Run: `tsx scripts/verify-package-readme-model-experience.ts`.
|
||||
*/
|
||||
@@ -13,9 +14,9 @@ import { relative, resolve } from 'node:path'
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
const HEADING = '## Model Experience'
|
||||
const LIMITATIONS_HEADING = '## Known Limitations and Deferred Work'
|
||||
const TABLE_HEADER = '| Context surface | What the model sees | Token effect |'
|
||||
const TABLE_DIVIDER = '|---|---|---|'
|
||||
const VERBATIM_HEADING = '### Verbatim model-visible text'
|
||||
const MODEL_VIEW_LABEL = '**What the model sees**'
|
||||
const TOKEN_EFFECT_LABEL = '**Token effect**'
|
||||
const H2_HEADING = /^## .+$/
|
||||
|
||||
type SentenceKind = 'none' | 'indirect'
|
||||
@@ -27,8 +28,8 @@ interface SentenceContract {
|
||||
|
||||
/**
|
||||
* Packages whose Model Experience is simple enough for one gated sentence.
|
||||
* Every other package must carry the canonical table. A package moves on or
|
||||
* off this list in the same change that changes its context behavior.
|
||||
* Every other package must carry canonical context-surface blocks. A package
|
||||
* moves on or off this list with the change to its context behavior.
|
||||
*/
|
||||
const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/bash/bash': { kind: 'indirect', reason: 'The service interface delegates all model rendering to dsh-tool-bash.' },
|
||||
@@ -75,36 +76,13 @@ function proseLines(text: string): Line[] {
|
||||
return kept
|
||||
}
|
||||
|
||||
/** Split a canonical table row without treating escaped pipes as delimiters. */
|
||||
function tableCells(raw: string): string[] | undefined {
|
||||
const last = raw.length - 1
|
||||
if (!raw.startsWith('|') || !raw.endsWith('|') || isEscaped(raw, last)) return undefined
|
||||
|
||||
const cells: string[] = []
|
||||
let start = 1
|
||||
for (let index = 1; index < last; index += 1) {
|
||||
if (raw[index] !== '|' || isEscaped(raw, index)) continue
|
||||
cells.push(raw.slice(start, index).trim())
|
||||
start = index + 1
|
||||
}
|
||||
cells.push(raw.slice(start, last).trim())
|
||||
return cells
|
||||
}
|
||||
|
||||
/** Whether the character at `index` follows an odd-length backslash run. */
|
||||
function isEscaped(text: string, index: number): boolean {
|
||||
let backslashes = 0
|
||||
for (let cursor = index - 1; cursor >= 0 && text[cursor] === '\\'; cursor -= 1) backslashes += 1
|
||||
return backslashes % 2 === 1
|
||||
}
|
||||
|
||||
/** Validate the optional long-form literal appendix after a Model Experience table. */
|
||||
/** Validate the optional long-form literal appendix after the context blocks. */
|
||||
function validateVerbatimTail(raw: readonly string[]): { blocks: number; titles: string[]; error?: string } {
|
||||
let cursor = 0
|
||||
while (raw[cursor]?.trim().length === 0) cursor += 1
|
||||
if (cursor === raw.length) return { blocks: 0, titles: [] }
|
||||
if (raw[cursor] !== VERBATIM_HEADING) {
|
||||
return { blocks: 0, titles: [], error: `non-table content must begin with ${VERBATIM_HEADING}` }
|
||||
return { blocks: 0, titles: [], error: `content after the context surfaces must begin with ${VERBATIM_HEADING}` }
|
||||
}
|
||||
cursor += 1
|
||||
|
||||
@@ -125,14 +103,14 @@ function validateVerbatimTail(raw: readonly string[]): { blocks: number; titles:
|
||||
titles.push(title)
|
||||
cursor += 1
|
||||
while (raw[cursor]?.trim().length === 0) cursor += 1
|
||||
if (raw[cursor] !== '```text') {
|
||||
return { blocks, titles, error: 'each verbatim entry requires an exact ```text fence' }
|
||||
if (raw[cursor] !== '```markdown') {
|
||||
return { blocks, titles, error: 'each verbatim entry requires an exact ```markdown fence' }
|
||||
}
|
||||
cursor += 1
|
||||
const contentStart = cursor
|
||||
while (cursor < raw.length && raw[cursor] !== '```') cursor += 1
|
||||
if (cursor === raw.length) return { blocks, titles, error: 'unterminated verbatim ```text fence' }
|
||||
if (cursor === contentStart) return { blocks, titles, error: 'verbatim ```text fence must not be empty' }
|
||||
if (cursor === raw.length) return { blocks, titles, error: 'unterminated verbatim ```markdown fence' }
|
||||
if (cursor === contentStart) return { blocks, titles, error: 'verbatim ```markdown fence must not be empty' }
|
||||
cursor += 1
|
||||
blocks += 1
|
||||
}
|
||||
@@ -147,7 +125,8 @@ function headingFragment(title: string): string {
|
||||
const failures: Failure[] = []
|
||||
const packageJsons = globSync('packages/*/*/package.json', { cwd: root }).sort()
|
||||
const scannedPackages = new Set(packageJsons.map(path => path.slice(0, -'/package.json'.length)))
|
||||
let tableCount = 0
|
||||
let structuredCount = 0
|
||||
let contextSurfaceCount = 0
|
||||
let noneCount = 0
|
||||
let indirectCount = 0
|
||||
let verbatimBlockCount = 0
|
||||
@@ -157,7 +136,7 @@ for (const [pkg, contract] of Object.entries(SENTENCE_MODEL_EXPERIENCE)) {
|
||||
failures.push({ path: `${pkg}/README.md`, message: 'sentence allowlist entry does not name a scanned package' })
|
||||
}
|
||||
if (contract.reason.trim().length === 0) {
|
||||
failures.push({ path: `${pkg}/README.md`, message: 'sentence allowlist entry must justify why a table is unnecessary' })
|
||||
failures.push({ path: `${pkg}/README.md`, message: 'sentence allowlist entry must justify why structured context surfaces are unnecessary' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,59 +204,81 @@ for (const packageJson of packageJsons) {
|
||||
continue
|
||||
}
|
||||
|
||||
const headers = section.filter(line => line.raw === TABLE_HEADER)
|
||||
const header = headers[0]
|
||||
const headerIndex = header === undefined ? -1 : section.indexOf(header)
|
||||
if (header === undefined || headers.length !== 1 || headerIndex < 0 || section[headerIndex + 1]?.raw !== TABLE_DIVIDER) {
|
||||
failures.push({ path: readme, message: `must contain the exact table header ${TABLE_HEADER}` })
|
||||
const appendixIndex = content.findIndex(line => line.raw === VERBATIM_HEADING)
|
||||
const surfaceContent = appendixIndex < 0 ? content : content.slice(0, appendixIndex)
|
||||
if (surfaceContent.length === 0 || surfaceContent.length % 3 !== 0) {
|
||||
failures.push({ path: readme, message: 'must contain one or more complete context-surface blocks' })
|
||||
continue
|
||||
}
|
||||
|
||||
const rows: Line[] = []
|
||||
for (const line of section.slice(headerIndex + 2)) {
|
||||
if (!line.raw.startsWith('|')) break
|
||||
rows.push(line)
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
failures.push({ path: readme, message: 'Model Experience table must contain at least one data row' })
|
||||
continue
|
||||
}
|
||||
for (const row of rows) {
|
||||
const cells = tableCells(row.raw)
|
||||
if (cells === undefined || cells.length !== 3 || cells.some(cell => cell.length === 0)) {
|
||||
failures.push({ path: readme, message: `line ${row.index}: invalid three-column Model Experience row: ${row.raw}` })
|
||||
const surfaces: Array<{ heading: Line; modelView: Line; tokenEffect: Line }> = []
|
||||
const surfaceFragments = new Set<string>()
|
||||
let previousTokenEffect: Line | undefined
|
||||
let surfaceError = false
|
||||
for (let index = 0; index < surfaceContent.length; index += 3) {
|
||||
const heading = surfaceContent[index] as Line
|
||||
const modelView = surfaceContent[index + 1] as Line
|
||||
const tokenEffect = surfaceContent[index + 2] as Line
|
||||
const fragment = /^### \S/.test(heading.raw) && heading.raw !== VERBATIM_HEADING
|
||||
? headingFragment(heading.raw.slice('### '.length))
|
||||
: ''
|
||||
if (fragment.length === 0) {
|
||||
failures.push({ path: readme, message: `line ${heading.index}: each context surface requires a non-empty H3 heading` })
|
||||
surfaceError = true
|
||||
break
|
||||
}
|
||||
if (surfaceFragments.has(fragment)) {
|
||||
failures.push({ path: readme, message: `line ${heading.index}: duplicate context-surface link fragment ${JSON.stringify(fragment)}` })
|
||||
surfaceError = true
|
||||
break
|
||||
}
|
||||
if (!modelView.raw.startsWith(`${MODEL_VIEW_LABEL}: `) || modelView.raw.slice(`${MODEL_VIEW_LABEL}: `.length).trim().length === 0) {
|
||||
failures.push({ path: readme, message: `line ${modelView.index}: context surface requires non-empty ${MODEL_VIEW_LABEL}: text` })
|
||||
surfaceError = true
|
||||
break
|
||||
}
|
||||
if (!tokenEffect.raw.startsWith(`${TOKEN_EFFECT_LABEL}: `) || tokenEffect.raw.slice(`${TOKEN_EFFECT_LABEL}: `.length).trim().length === 0) {
|
||||
failures.push({ path: readme, message: `line ${tokenEffect.index}: context surface requires non-empty ${TOKEN_EFFECT_LABEL}: text` })
|
||||
surfaceError = true
|
||||
break
|
||||
}
|
||||
const expectedHeadingLine = previousTokenEffect?.index === undefined ? modelHeading.index + 2 : previousTokenEffect.index + 2
|
||||
if (heading.index !== expectedHeadingLine || modelView.index !== heading.index + 2 || tokenEffect.index !== modelView.index + 2) {
|
||||
failures.push({ path: readme, message: `line ${heading.index}: context-surface heading and fields require one blank line between each element` })
|
||||
surfaceError = true
|
||||
break
|
||||
}
|
||||
surfaceFragments.add(fragment)
|
||||
surfaces.push({ heading, modelView, tokenEffect })
|
||||
previousTokenEffect = tokenEffect
|
||||
}
|
||||
if (content[0] !== header) {
|
||||
failures.push({ path: readme, message: 'Model Experience table must be the first nonblank content in the section' })
|
||||
if (surfaceError) continue
|
||||
|
||||
const lastSurface = surfaces.at(-1) as { heading: Line; modelView: Line; tokenEffect: Line }
|
||||
if (appendixIndex >= 0 && (content[appendixIndex] as Line).index !== lastSurface.tokenEffect.index + 2) {
|
||||
failures.push({ path: readme, message: `${VERBATIM_HEADING} must follow the final context surface after one blank line` })
|
||||
continue
|
||||
}
|
||||
const tableLines = new Set([header, section[headerIndex + 1], ...rows])
|
||||
const extra = content.filter(line => !tableLines.has(line))
|
||||
if (extra.length > 0 && (extra[0]?.raw !== VERBATIM_HEADING || extra.slice(1).some(line => !/^#### \S/.test(line.raw)))) {
|
||||
const first = extra[0] as Line
|
||||
failures.push({ path: readme, message: `line ${first.index}: invalid content after Model Experience table: ${first.raw}` })
|
||||
continue
|
||||
}
|
||||
const lastRow = rows.at(-1) as Line
|
||||
const rawTail = rawLines.slice(lastRow.index, nextH2Line - 1)
|
||||
|
||||
const rawTail = rawLines.slice(lastSurface.tokenEffect.index, nextH2Line - 1)
|
||||
const verbatim = validateVerbatimTail(rawTail)
|
||||
if (verbatim.error !== undefined) {
|
||||
failures.push({ path: readme, message: verbatim.error })
|
||||
continue
|
||||
}
|
||||
const tableText = rows.map(row => row.raw).join('\n')
|
||||
const unlinked = verbatim.titles.find(title => !tableText.includes(`](#${headingFragment(title)})`))
|
||||
const modelViewText = surfaces.map(surface => surface.modelView.raw).join('\n')
|
||||
const unlinked = verbatim.titles.find(title => !modelViewText.includes(`](#${headingFragment(title)})`))
|
||||
if (unlinked !== undefined) {
|
||||
failures.push({ path: readme, message: `verbatim entry ${JSON.stringify(unlinked)} must be linked from a Model Experience table row` })
|
||||
failures.push({ path: readme, message: `verbatim entry ${JSON.stringify(unlinked)} must be linked from a context surface's ${MODEL_VIEW_LABEL} field` })
|
||||
continue
|
||||
}
|
||||
verbatimBlockCount += verbatim.blocks
|
||||
tableCount += 1
|
||||
contextSurfaceCount += surfaces.length
|
||||
structuredCount += 1
|
||||
}
|
||||
|
||||
if (failures.length === 0) {
|
||||
console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) checked (${tableCount} tables, ${noneCount} none, ${indirectCount} indirect, ${verbatimBlockCount} verbatim blocks), all conform.`)
|
||||
console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) checked (${structuredCount} structured, ${contextSurfaceCount} context surfaces, ${noneCount} none, ${indirectCount} indirect, ${verbatimBlockCount} verbatim markdown blocks), all conform.`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user