Merge remote-tracking branch 'origin/master' into worktree/preset-plane-fallout-p1

`minimal` was rewritten upstream into a fixed-prompt PTY surface that now
carries its own compaction group, so this branch's token-meter move applies to
it too: the row leaves the group and `tokenMeter` leaves the realm, exactly as
in the other three presets. Master's header prose is kept whole — the note this
branch had added there described a composition that no longer exists.

`docs/event-producer-consumer.*` is generated; regenerated after the merge.
This commit is contained in:
Yichen Jiang
2026-08-10 23:50:28 +08:00
132 changed files with 2456 additions and 1359 deletions
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write examples/jsonrpc-agent/README.md
README.md: bcc1027d2edb30ab374dfa2ed13ad8e6360d923b
README.zh.md: ce255e4dd70bf8c5c6edc51afbe03bb4c66560a0
README.md: 5f60a64a4888c64e4fd68835f78e4a334ffed263
README.zh.md: 8d2f9807ff259000b5a6823357f8c41b43bfa434
+4 -4
View File
@@ -21,16 +21,16 @@ The surrounding runtime also loads JSONL session persistence and automatic conte
| `DEEPSEEK_BASE_URL` | Host endpoint used by `dsh-llm-deepseek` |
| `DSH_CWD` | Agent workspace for bash and filesystem tools |
| `DSH_MAX_TOKENS_AS_SUCCESS` | `true` (default) accepts token-limited results; `false` reports them as errors |
| `DSH_SESSION_ROOT` | JSONL trajectory directory |
| `DSH_SESSION_ROOT` | JSONL session directory |
| `DSH_SYSTEM_PROMPT` | Deployment-provided coding persona |
Pass the config path through the Python SDK's `cordis` option or `DSH_CORDIS_CONFIG`. The bundled executable already carries every plugin named by this file; the target machine does not need Node.js.
## Persistent tools variant
## Minimal variant
[`persistent-tools.cordis.yml`](persistent-tools.cordis.yml) is a minimal runnable variant whose model-facing surface is exactly:
[`minimal.cordis.yml`](minimal.cordis.yml) is the complete standalone counterpart of the Web `minimal` preset. It fixes the system prompt and compaction policy, and its model-facing surface is exactly:
- owner-scoped persistent `bash`
- `str_replace_editor` with `view`, `create`, `str_replace`, and `insert`
It composes the local PTY, filesystem intent policy, and session sandbox policy.
It composes the local PTY, filesystem intent policy, session sandbox policy, and JSONL persistence needed by the bundled runtime. [`minimal.py`](minimal.py) runs it through the Python SDK; the [Python SDK tutorial](../../docs/user/guide/python-sdk.md) uses this configuration to cover setup, session management, and the security boundary.
+4 -4
View File
@@ -21,16 +21,16 @@
| `DEEPSEEK_BASE_URL` | `dsh-llm-deepseek` 使用的宿主端点 |
| `DSH_CWD` | bash 和文件系统工具使用的 agent workspace |
| `DSH_MAX_TOKENS_AS_SUCCESS` | `true`(默认)接受受 token 上限限制的结果;`false` 将其报告为错误 |
| `DSH_SESSION_ROOT` | JSONL 轨迹目录 |
| `DSH_SESSION_ROOT` | JSONL 会话目录 |
| `DSH_SYSTEM_PROMPT` | 由部署提供的编码人格 |
通过 Python SDK 的 `cordis` 选项或 `DSH_CORDIS_CONFIG` 传入配置路径。内置可执行文件已携带此文件中指定的每个插件;目标机器无需 Node.js。
## 持久化工具变体
## 极简变体
[`persistent-tools.cordis.yml`](persistent-tools.cordis.yml) 是一个最小可运行变体,面向模型的能力严格只有:
[`minimal.cordis.yml`](minimal.cordis.yml) 是 Web `minimal` preset 的完整独立版本。它固定系统提示词与压缩策略,面向模型的能力严格只有:
- 所有者作用域内持久化的 `bash`
- 提供 `view``create``str_replace``insert``str_replace_editor`
它组合了本地 PTY、文件系统意图策略会话沙箱策略。
它组合了内置运行时所需的本地 PTY、文件系统意图策略会话沙箱策略与 JSONL 持久化。[`minimal.py`](minimal.py) 通过 Python SDK 运行该配置;[Python SDK 教程](../../docs/user/guide/python-sdk.md)以此配置介绍设置方式、会话管理与安全边界
+91
View File
@@ -0,0 +1,91 @@
# Complete unattended minimal-agent composition for the Python SDK. The model
# sees one fixed system prompt and only the owner-scoped persistent Bash and
# string-replace editor tools.
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
config:
maxTokensAsSuccess: false
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
- id: sandbox
name: '@deepseek-ai/dsh-sandbox-local'
- id: sandbox-policy
name: '@deepseek-ai/dsh-sandbox-policy'
config:
mode: danger-full-access
workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
- id: subprocess
name: '@deepseek-ai/dsh-subprocess-local'
- id: pty
name: '@deepseek-ai/dsh-pty'
- id: pty-local
name: '@deepseek-ai/dsh-pty-local'
config:
timeoutMs: 300000
# The sandbox-aware filesystem backend applies the same per-session policy as
# Bash. danger-full-access permits unrestricted workspace behavior while
# keeping one policy boundary for both tools.
- id: fs-sandbox
name: '@deepseek-ai/dsh-fs-sandbox'
config:
cwd: !!js process.env.DSH_CWD ?? process.cwd()
- id: fs-policy
name: '@deepseek-ai/dsh-fs-policy'
- id: agent-spine
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
includeHarnessIdentity: false
persona: You are a helpful software engineer assistant.
workspaceContext: false
skills:
enabled: false
toolBash: false
toolTasks: false
- id: persistent-bash
name: '@deepseek-ai/dsh-tool-bash-persistent'
config:
timeoutMs: 300000
description: |-
Run commands in a bash shell
* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
* You don't have access to the internet via this tool.
* You do have access to a mirror of common linux and python packages via apt and pip.
* State is persistent across command calls and discussions with the user.
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
* Please avoid commands that may produce a very large amount of output.
* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.
- id: str-replace-editor
name: '@deepseek-ai/dsh-tool-str-replace-editor'
config:
maxOutputChars: 16000
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
compression: none
- id: token-meter
name: '@deepseek-ai/dsh-token-meter'
- id: compact-basic
name: '@deepseek-ai/dsh-compact-basic'
config:
thresholdRatio: 0.8
retainTokens: 20480
summarizationProvider: ''
summarizationModel: ''
maxTokens: 8192
compactionRetries: 1
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""Run one minimal-agent turn through the bundled Python SDK runtime."""
from __future__ import annotations
import argparse
from pathlib import Path
from deepseek_harness import DeepSeekHarness
CONFIG = Path(__file__).with_name("minimal.cordis.yml")
def main() -> None:
"""Parse one task and print the agent's final response."""
parser = argparse.ArgumentParser()
parser.add_argument("prompt", help="Task for the minimal agent")
parser.add_argument("--workspace", type=Path, default=Path.cwd())
parser.add_argument("--session-root", type=Path, default=Path(".dsh-sessions"))
parser.add_argument("--session-id")
parser.add_argument("--provider", default="deepseek-official")
parser.add_argument("--model", default="deepseek-v4-flash")
parser.add_argument("--max-tokens", type=int)
args = parser.parse_args()
workspace = args.workspace.resolve()
session_root = args.session_root.resolve()
with DeepSeekHarness(
provider=args.provider,
model=args.model,
max_tokens=args.max_tokens,
cwd=str(workspace),
session_root=str(session_root),
cordis=str(CONFIG.resolve()),
) as harness:
result = harness.run(args.prompt, session_id=args.session_id)
print(result.final_response)
if __name__ == "__main__":
main()
@@ -1,12 +1,10 @@
# Keyless replay keeps the persistent-tool composition intact and replaces
# only its live DeepSeek adapter with the fixture-backed provider. The catalog
# below claims the same `deepseek-official` route the agent asks for: an
# unowned route makes the SDK server mount the real adapter, which then demands
# a key this keyless lane has no way to supply.
# Keyless replay keeps the complete minimal composition intact and replaces
# only its live DeepSeek adapter with the fixture-backed provider. The replay
# catalog claims the same route initialized by the SDK.
- id: base
name: '@deepseek-ai/cordis-plugin-include'
config:
path: ./persistent-tools.cordis.yml
path: ./minimal.cordis.yml
patches:
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
@@ -1,59 +0,0 @@
# Minimal unattended composition for the persistent Bash and string-replace
# editor. It is runnable through the JSON-RPC example runtime and intentionally
# keeps the model-facing surface to exactly these two tools.
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
- id: sandbox
name: '@deepseek-ai/dsh-sandbox-local'
- id: sandbox-policy
name: '@deepseek-ai/dsh-sandbox-policy'
config:
mode: danger-full-access
workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
- id: subprocess
name: '@deepseek-ai/dsh-subprocess-local'
- id: pty
name: '@deepseek-ai/dsh-pty'
- id: pty-local
name: '@deepseek-ai/dsh-pty-local'
- id: fs-sandbox
name: '@deepseek-ai/dsh-fs-sandbox'
config:
cwd: !!js process.env.DSH_CWD ?? process.cwd()
- id: fs-policy
name: '@deepseek-ai/dsh-fs-policy'
- id: agent-spine
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
includeHarnessIdentity: false
persona: 'You are a helpful software engineer assistant.'
workspaceContext: false
skills:
enabled: false
toolBash: false
toolTasks: false
- id: persistent-bash
name: '@deepseek-ai/dsh-tool-bash-persistent'
- id: str-replace-editor
name: '@deepseek-ai/dsh-tool-str-replace-editor'
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
compression: none
+49 -6
View File
@@ -33,11 +33,21 @@ const testsDir = dirOf(import.meta.url)
const snapshotsDir = join(testsDir, 'snapshots')
const liveConfig = join(testsDir, '..', 'cordis.yml')
const replayConfig = join(testsDir, '..', 'cordis.snapshot.yml')
const persistentToolsLiveConfig = join(testsDir, '..', 'persistent-tools.cordis.yml')
const persistentToolsReplayConfig = join(testsDir, '..', 'persistent-tools.snapshot.cordis.yml')
const minimalLiveConfig = join(testsDir, '..', 'minimal.cordis.yml')
const minimalReplayConfig = join(testsDir, '..', 'minimal.snapshot.cordis.yml')
const runtimeBin = fileURLToPath(new URL('../../../packages/examples/jsonrpc-demo/src/bin.ts', import.meta.url))
const repoTsconfig = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
const MINIMAL_SYSTEM_PROMPT = 'You are a helpful software engineer assistant.'
const MINIMAL_BASH_DESCRIPTION = `Run commands in a bash shell
* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
* You don't have access to the internet via this tool.
* You do have access to a mirror of common linux and python packages via apt and pip.
* State is persistent across command calls and discussions with the user.
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
* Please avoid commands that may produce a very large amount of output.
* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.`
const mode = process.env.DSH_SNAPSHOT ?? 'replay'
const recording = mode === 'record'
const refreshing = mode === 'refresh'
@@ -61,6 +71,10 @@ interface SdkScenario {
expectedFiles?: Readonly<Record<string, string>>
/** Assembled model-facing tool names and required argument keys. */
expectedTools?: Readonly<Record<string, readonly string[]>>
/** Exact assembled system prompt for the root request. */
expectedSystem?: string
/** Exact model-facing descriptions for selected tools. */
expectedToolDescriptions?: Readonly<Record<string, string>>
/** Stable policy-context clauses the real assembled request must include or omit. */
policyContext?: { includes: readonly string[]; excludes: readonly string[] }
}
@@ -89,9 +103,11 @@ const SCENARIOS: SdkScenario[] = [
prompt: 'Prove that bash state persists. Then create {{cwd}}/note.txt with a tab-indented line, view it, replace that literal tab-indented line, and make the persistent shell exit with code 9.',
sessionId: 'persistent-tools-snapshot',
children: 0,
configs: { live: persistentToolsLiveConfig, replay: persistentToolsReplayConfig },
configs: { live: minimalLiveConfig, replay: minimalReplayConfig },
expectedFiles: { 'note.txt': 'target:\n\tnew\n' },
expectedTools: { bash: ['command'], str_replace_editor: ['command', 'path'] },
expectedSystem: MINIMAL_SYSTEM_PROMPT,
expectedToolDescriptions: { bash: MINIMAL_BASH_DESCRIPTION },
policyContext: {
includes: ['Current DSH file policy: danger-full-access.', 'file modifications by available operations'],
excludes: ['write and edit tools', 'terminal sessions', 'one-shot bash commands'],
@@ -125,16 +141,33 @@ async function persistedLogs(sessionsRoot: string): Promise<PersistedLog[]> {
interface LoggedRequestHeader {
type?: string
data?: { header?: { system?: unknown; tools?: Array<{ name: string; parameters: { required?: string[] } }> } }
data?: { header?: { system?: unknown; tools?: LoggedTool[] } }
}
function assembledToolRequirements(log: PersistedLog): Record<string, string[]> {
interface LoggedTool {
readonly name: string
readonly description?: unknown
readonly parameters: { readonly required?: string[] }
}
function assembledTools(log: PersistedLog): LoggedTool[] {
const event = log.content.trimEnd().split('\n')
.map(line => JSON.parse(line) as LoggedRequestHeader)
.find(candidate => candidate.type === 'request/header')
const tools = event?.data?.header?.tools
if (tools === undefined) throw new Error('session log has no request/header tools')
return Object.fromEntries(tools.map(tool => [tool.name, tool.parameters.required ?? []]))
return tools
}
function assembledToolRequirements(log: PersistedLog): Record<string, string[]> {
return Object.fromEntries(assembledTools(log).map(tool => [tool.name, tool.parameters.required ?? []]))
}
function assembledToolDescriptions(log: PersistedLog): Record<string, string> {
return Object.fromEntries(assembledTools(log).map((tool) => {
if (typeof tool.description !== 'string') throw new Error(`tool ${tool.name} has no description`)
return [tool.name, tool.description]
}))
}
function assembledSystem(log: PersistedLog): string {
@@ -400,6 +433,16 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => {
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
expect(assembledToolRequirements(parent)).toEqual(scenario.expectedTools)
}
if (scenario.expectedSystem !== undefined) {
const parent = ordered[0]
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
expect(assembledSystem(parent)).toBe(scenario.expectedSystem)
}
if (scenario.expectedToolDescriptions !== undefined) {
const parent = ordered[0]
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
expect(assembledToolDescriptions(parent)).toMatchObject(scenario.expectedToolDescriptions)
}
if (scenario.policyContext !== undefined) {
const parent = ordered[0]
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)