docs: address model experience review

This commit is contained in:
Tianyi Cui
2026-07-12 02:55:26 +08:00
parent a08485fc80
commit b5cd511f35
64 changed files with 443 additions and 410 deletions

View File

@@ -2,12 +2,6 @@
Local-subprocess implementation of the `@deepseek-ai/dsh-bash` executor seam: `LocalBashExecutor` spawns `bash -c <command>` per call in its own process group, collects bounded output with full-stream spill files, and escalates kills SIGTERM→SIGKILL across the whole group.
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the retained stdout and stderr tail, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. |
## Config
```yaml
@@ -31,6 +25,12 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi;
- **Model-friendly env + credential scrub** — `process.env` minus credential-shaped vars (`*KEY*`/`*SECRET*`/`*TOKEN*`), then `NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` (Codex's hardcoded set) so pagers and ANSI color don't garble results. This scrub is the security control that keeps the harness's *ambient* credentials out of a spawned command. A spec's `env` is merged LAST (after the scrub), so a caller's explicit entry — a value it already holds — wins even on a credential-shaped name. The spec's `stdin`, when supplied, is written to the child and closed; with none supplied, fd 0 is `/dev/null` — the exact pre-seam default, so a command that probes stdin's file type is unaffected. Both `env`/`stdin` are set by in-process plugins (the hooks bridges); the model-facing tool doesn't expose them. See [the bash-stdin-env RFC](../../../docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md).
- **Background tasks** — `start()` returns immediately, no timeout applies (Claude Code detaches timeouts when backgrounding), `readOutput()` is incremental with whole-stream byte offsets, and disposal kills everything. The spec's opaque `owner` token is stored on the tracked task and returned by `ownerOf(id)` — the executor never interprets it (the consumer's access policy does), and because it lives with the task here it survives a `tool-bash` HMR reload.
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the retained stdout and stderr tail, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. |
## Known Limitations and Deferred Work
- **Unconfined by itself** — this executor always runs commands with the harness process's authority; deployments needing confinement compose [`dsh-bash-sandbox`](../bash-sandbox/README.md), while per-call allow/deny/ask policy belongs on `tools/pre-execute`.

View File

@@ -13,12 +13,6 @@ This package is the interface quarter of the bash capability, split so each conc
The split mirrors the LLM seam (`LlmService`/`LlmAdapter`) and the agent-tool survey: pi hides execution behind a `BashOperations` interface (local shell / SSH / VM backends), Codex behind an exec-server protocol. `dsh-bash-sandbox` is exactly that swap in action — a sandboxing executor behind the same interface, tool schemas untouched; a containerized or remote executor slots in the same way.
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| None directly | This interface registers no prompt, tool schema, or message. `dsh-tool-bash` turns an implementation's stdout, stderr, task state, and sandbox facts into model-visible tool results and guidance. | Zero direct tokens. Result size and sandbox state affect input tokens only when a consumer renders them. |
## Service API (`ctx.bash`)
| Member | Semantics |
@@ -42,6 +36,12 @@ The seam also owns the per-session mode override vocabulary (the sandbox RFC §
`stdin` and `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload on stdin and its `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` env. The model-facing `dsh-tool-bash` tool does not expose them as parameters — a model already has equivalent power through shell syntax (`FOO=bar cmd`, a heredoc), so they would be redundant tool params. This is not a security boundary: the implementation's credential scrub (not these fields) is what keeps the harness's ambient secrets out of a spawned command. They are plain optionals on the resolved spec (unlike `owner`'s required-but-nullable): a missing one means "none", the safe default. See [the bash-stdin-env RFC](../../../docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md).
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| None directly | This interface registers no prompt, tool schema, or message. `dsh-tool-bash` turns an implementation's stdout, stderr, task state, and sandbox facts into model-visible tool results and guidance. | Zero direct tokens. Result size and sandbox state affect input tokens only when a consumer renders them. |
## Known Limitations and Deferred Work
- **No interactive-input vocabulary** — `stdin` is written once at spawn and closed; the seam has no channel to feed a running task and no PTY session concept.

View File

@@ -6,14 +6,6 @@ Requires a loaded executor implementation (e.g. `@deepseek-ai/dsh-bash-local`);
The plugin also contributes the `tool:bash` prompt section (order 105) — the cross-call habit the per-tool descriptions cannot carry: check the `[exit code: N]` marker on every result and investigate failures before moving on. Under a sandboxing executor it additionally contributes the per-agent `env:bash-sandbox` section (order 110) stating each session's EFFECTIVE mode, and the pre-step narrator — see [Per-session mode](#per-session-mode-switching-and-visibility).
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| System prompt | Every request for an agent that can see these tools carries the short `tool:bash` exit-code instruction. With a sandboxing executor it also carries that session's effective sandbox mode, plus a logged context notice after a mode change. | Small fixed input cost per request; a mode-change notice is conditional and then remains in conversation history. |
| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. |
| Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. |
## Tools
### `bash`
@@ -66,6 +58,14 @@ On top of a denial sits the escalation gate ([the sandbox RFC § Escalation](../
Under a sandboxing executor this plugin makes the session's standing mode override ([the sandbox RFC § Per-session mode switching](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md); the `bash/sandbox-mode` fold owned by [`dsh-bash`](../bash/README.md)) real at EXECUTION: every call is stamped `escalation grant > session override > undefined` onto `BashExecRequest.sandboxMode`; without either, the executor's `resolve()` applies its configured default. Nothing is stamped under a non-sandboxing executor (nothing would honor it) or for an agent-less caller (no session to fold). The prompt deliberately does NOT state the mode and a switch is not narrated: a standing declaration teaches the model to refuse preemptively, while the denial marker already names the mode the command ran under exactly when the boundary is hit — behavior, not belief, carries the state.
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| System prompt | Every request for an agent that can see these tools carries the short `tool:bash` exit-code instruction. With a sandboxing executor it also carries that session's effective sandbox mode, plus a logged context notice after a mode change. | Small fixed input cost per request; a mode-change notice is conditional and then remains in conversation history. |
| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. |
| Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. |
## Known Limitations and Deferred Work
- **Replay exit pills parse from result text** — output whose final line happens to be exactly `[exit code: N]` / `[killed by signal: …]` shows a wrong pill on session replay; a display-only known residual.