refactor(python): launch the minimal example through sdk-minimal

Make minimal.py select the shipped sdk-minimal profile directly and pass its selected model into the profile-owned adapter catalog. The Python SDK still starts only the bundled dsh CLI with an explicit Harness home; it no longer supplies an invocation overlay for this mode.

Drive both the source keyless process test and installed-wheel smoke through the same named profile. The keyless test pins the generated profile manifest and exact two-tool model request, while the packaged smoke keeps the persistent-shell, editor, session-log, and model-visible snapshot evidence.

Delete minimal.patch.yml and the unused complete-config/replay fixtures. Their composition now has one owner in @deepseek-ai/dsh-sdk-minimal, so the example and tests cannot drift into separate launch trees.
This commit is contained in:
Tianyi Cui
2026-08-24 17:28:28 +08:00
parent 8dc3b0380e
commit 79a8f667f7
6 changed files with 18 additions and 241 deletions
@@ -1,90 +0,0 @@
# Complete minimal-agent composition fixture for lower-level snapshot tests.
# Python users apply `minimal.patch.yml` to `dsh --profile sdk`. This fixture
# retains the same two-tool behavior without defining a supported launch path.
- id: sdk-jsonrpc-server
name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
config:
maxTokensAsSuccess: false
- id: deepseek-llm-api-extensions
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
- id: session-log-deepseek
name: '@deepseek-ai/dsh-session-log-deepseek'
- id: plugin-package-inventory-deepseek
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKeyEnv: DEEPSEEK_API_KEY
streamIdleTimeoutMs: 172800000
models:
- id: !!js process.env.DSH_MODEL ?? 'deepseek-v4-flash'
contextWindow: !!js Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000)
- 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-terminal'
- id: terminal-bash
name: '@deepseek-ai/dsh-terminal-bash'
config:
timeoutMs: 300000
# The editor uses the bare local filesystem; persistent Bash still consumes the
# shared danger-full-access sandbox policy above.
- id: fs-local
name: '@deepseek-ai/dsh-fs-local'
config:
cwd: !!js process.env.DSH_CWD ?? process.cwd()
- id: agent-spine
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
includeHarnessIdentity: false
includeRuntimeContext: false
persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.'
workspaceContext: false
skills:
enabled: false
toolBash: false
toolJobs: 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
@@ -1,47 +0,0 @@
# Minimal Python SDK overlay for `dsh --profile sdk`. The SDK server applies
# the model-facing allowlist to every root session, so later base-bundle tools
# cannot appear implicitly. The profile still owns JSON-RPC and persistence.
- id: system-prompt
config:
includeHarnessIdentity: false
includeRuntimeContext: false
persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.'
personaComplete: true
- id: sdk-jsonrpc-server
config:
maxTokensAsSuccess: !!js "process.env.DSH_MAX_TOKENS_AS_SUCCESS === undefined ? true : JSON.parse(process.env.DSH_MAX_TOKENS_AS_SUCCESS)"
toolFilter:
allow:
- bash
- str_replace_editor
# Remove non-tool model inputs and turn rewriting, plus the one-shot Bash row
# that would conflict with the persistent Bash tool inserted below.
- id: agent-instructions
disabled: true
- id: tool-bash
disabled: true
- id: compaction-basic
disabled: true
- id: command-compact
disabled: true
- id: tool-result-pruner
disabled: true
- id: tool-str-replace-editor
config:
maxOutputChars: 16000
- insert:
- id: pty
name: '@deepseek-ai/dsh-terminal'
- id: terminal-bash
name: '@deepseek-ai/dsh-terminal-bash'
config:
timeoutMs: 300000
- id: persistent-bash
name: '@deepseek-ai/dsh-tool-bash-persistent'
config:
timeoutMs: 300000
+2 -5
View File
@@ -10,9 +10,6 @@ from pathlib import Path
from deepseek_harness import DeepSeekHarness
PATCH = Path(__file__).with_name("minimal.patch.yml")
def main() -> None:
"""Parse one task and print the agent's final response."""
parser = argparse.ArgumentParser()
@@ -24,7 +21,7 @@ def main() -> None:
type=Path,
default=Path(configured_home) if configured_home.strip() else None,
)
parser.add_argument("--profile", default="sdk")
parser.add_argument("--profile", default="sdk-minimal")
parser.add_argument("--session-id")
parser.add_argument("--provider", default="deepseek-official")
parser.add_argument("--model", default=os.environ.get("DSH_MODEL", "deepseek-v4-flash"))
@@ -42,7 +39,7 @@ def main() -> None:
cwd=str(workspace),
dsh_home=str(dsh_home),
profile=args.profile,
patches=(str(PATCH.resolve()),),
env={"DSH_MODEL": args.model},
) as harness:
result = harness.run(args.prompt, session_id=args.session_id)
print(result.final_response)
@@ -1,20 +0,0 @@
# 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: ./minimal.cordis.yml
patches:
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
disabled: true
- insert:
- id: llm-replay
name: '@deepseek-ai/dsh-llm-replay'
config:
providers:
- id: deepseek-official
name: DeepSeek
models:
- id: deepseek-v4-flash
@@ -10,7 +10,6 @@ import { describe, expect, it } from 'vitest'
const binScript = fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url))
const patchPath = fileURLToPath(new URL('./keyless.patch.yml', import.meta.url))
const minimalPatchPath = fileURLToPath(new URL('../minimal.patch.yml', import.meta.url))
const repoRoot = fileURLToPath(new URL('../../..', import.meta.url))
const decompress = promisify(zstdDecompress)
@@ -181,7 +180,7 @@ describe('Python SDK dsh profile keyless smoke', () => {
}
}, 40_000)
it('keeps the minimal overlay on an explicit model-facing allowlist', async () => {
it('boots the standalone minimal profile with its exact model-facing roster', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-python-sdk-minimal-'))
const modelRequests: Record<string, unknown>[] = []
const modelServer = createServer((request, response) => {
@@ -205,16 +204,13 @@ describe('Python SDK dsh profile keyless smoke', () => {
'tsx/esm',
binScript,
'--profile',
'sdk',
'--patch',
minimalPatchPath,
'sdk-minimal',
], {
cwd: repoRoot,
env: {
DSH_HOME: join(root, '.dsh'),
DSH_PERMISSION_MODE: 'danger-full-access',
DSH_MODEL: 'deepseek-v4-pro',
DSH_SYSTEM_PROMPT: 'Minimal allowlist prompt.',
DSH_TELEMETRY_DISABLED: '1',
DEEPSEEK_API_KEY: 'keyless-smoke-no-call',
DEEPSEEK_BASE_URL: `http://127.0.0.1:${address.port}`,
},
@@ -259,6 +255,13 @@ describe('Python SDK dsh profile keyless smoke', () => {
}
expect(request.messages?.[0]).toMatchObject({ role: 'system', content: 'Minimal allowlist prompt.' })
expect(request.tools?.map(tool => tool.function?.name).sort()).toEqual(['bash', 'str_replace_editor'])
const profile = JSON.parse(
await readFile(join(root, '.dsh', 'profiles', 'sdk-minimal', 'package.json'), 'utf8'),
) as { dsh?: { profile?: { bundles?: string[]; patchReload?: string } } }
expect(profile.dsh?.profile).toEqual({
bundles: ['@deepseek-ai/dsh-sdk-minimal'],
patchReload: 'startup',
})
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'shutdown' })}\n`)
await waitForLine(lines, value => value.id === 3, () => stderr)
+6 -72
View File
@@ -46,14 +46,6 @@ MINIMAL_BASH_COMMAND = (
"printf 'COUNT=%s CWD=%s\\n' \"$counter\" \"$PWD\"; "
"if [ \"$counter\" -eq 1 ]; then cd /tmp; fi"
)
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."""
LEGACY_CUSTOM_DISABLED_ROWS = (
"agent-instructions",
"goal",
@@ -121,10 +113,6 @@ RESTART_SNAPSHOT_DIRECTORY = (
Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "restart"
)
RESTART_SNAPSHOT_FILENAMES = ("result.json", "requests.json", "session.1.jsonl", "session.2.jsonl")
# The agent loop's dynamic runtime-context snapshot is the one model-visible message this
# expected output cannot carry: the same composition emits it on macOS and not on Linux
# (deepseek-harness#2488), and the file must replay on both. Everything else is compared.
RUNTIME_CONTEXT_PREFIX = "Current runtime context"
MCP_SERVER_SCRIPT = """\
import json
import os
@@ -972,7 +960,7 @@ def smoke_sdk_custom(base_url: str, executable: Path) -> None:
def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -> None:
"""Exercise the checked-in minimal composition through the packaged executable."""
"""Exercise the shipped standalone minimal profile through the packaged executable."""
from deepseek_harness import DeepSeekHarness
# One mock model serves every scenario of a run, so the snapshot takes this turn's slice.
@@ -983,59 +971,15 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -
prompt = f"{MINIMAL_PROMPT}\n{MINIMAL_EDITOR_PATH_PREFIX}{editor_path}"
dsh_home = root / "home"
sessions = dsh_home / "sessions"
disabled = [
"agent-instructions",
"tool-bash",
"compaction-basic",
"command-compact",
"tool-result-pruner",
]
patch = write_profile_patch(root, "minimal.patch.yml", sessions, [
{
"id": "system-prompt",
"config": {
"includeHarnessIdentity": False,
"includeRuntimeContext": False,
"persona": "You are a helpful software engineer assistant.",
"personaComplete": True,
},
},
{
"id": "sdk-jsonrpc-server",
"config": {
"maxTokensAsSuccess": True,
"toolFilter": {"allow": ["bash", "str_replace_editor"]},
},
},
*({"id": row_id, "disabled": True} for row_id in disabled),
{"id": "tool-str-replace-editor", "config": {"maxOutputChars": 16000}},
{"insert": [
{"id": "pty", "name": "@deepseek-ai/dsh-terminal"},
{
"id": "terminal-bash",
"name": "@deepseek-ai/dsh-terminal-bash",
"config": {"timeoutMs": 300000},
},
{
"id": "persistent-bash",
"name": "@deepseek-ai/dsh-tool-bash-persistent",
"config": {
"timeoutMs": 300000,
"description": MINIMAL_BASH_DESCRIPTION,
},
},
]},
])
with DeepSeekHarness(
provider="deepseek-official",
model="smoke-model",
cwd=str(root),
dsh_bin=str(executable),
dsh_home=str(dsh_home),
patches=(str(patch),),
profile="sdk-minimal",
env={
"DSH_PERMISSION_MODE": "danger-full-access",
"DSH_TELEMETRY_DISABLED": "1",
"DSH_MODEL": "smoke-model",
},
api_key="sk-keyless-smoke",
base_url=base_url,
@@ -1550,9 +1494,9 @@ def build_minimal_snapshot_files(
Every assembled system prompt, advertised tool schema, and system or user message is
kept verbatim: they carry what the deployment actually shows the model, so a plugin
that contributes an unintended system section or user message cannot pass unnoticed.
Assistant and tool payloads keep only their call identity, and the dynamic
runtime-context snapshot is dropped, because their text differs across the platforms
this expected output must replay on.
Assistant and tool payloads keep only their call identity because their text differs
across the platforms this expected output must replay on. The shipped profile omits
dynamic runtime context, so every message it emits is compared.
"""
snapshot = []
for body in requests:
@@ -1564,21 +1508,11 @@ def build_minimal_snapshot_files(
"messages": [
minimal_snapshot_message(message, cwd)
for message in messages
if not is_runtime_context_message(message)
],
})
return {"model-visible.json": json.dumps(snapshot, indent=2, ensure_ascii=False) + "\n"}
def is_runtime_context_message(message: object) -> bool:
"""Identify the agent loop's dynamic runtime-context snapshot, current or cleared."""
return (
isinstance(message, dict)
and message.get("role") == "user"
and message_text(message.get("content")).startswith(RUNTIME_CONTEXT_PREFIX)
)
def minimal_snapshot_message(message: object, cwd: Path) -> dict[str, object]:
"""Reduce one model-visible message to its stable, behavior-carrying parts."""
if not isinstance(message, dict):