diff --git a/python/development.i18n.yaml b/python/development.i18n.yaml
index f1ada26c49..7e2f051761 100644
--- a/python/development.i18n.yaml
+++ b/python/development.i18n.yaml
@@ -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 python/development.md
-development.md: aa0144d7eaa66711d0f08316d4445da060918ca8
-development.zh.md: a35f6fc8de1bdbd282fd8999a1440fde0c400b34
+development.md: 9b8e401a995b42434d3817f80bcabac910af9d6c
+development.zh.md: c5f68b4f01f213d6f34d37c664617f7f4232125f
diff --git a/python/development.md b/python/development.md
index aa0144d7ea..9b8e401a99 100644
--- a/python/development.md
+++ b/python/development.md
@@ -34,7 +34,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \
--scenario sdk-minimal --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64
```
-Three scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the Linux/macOS `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages; `minimal/win-x64/model-visible.json` pins its PowerShell counterpart. A plugin that contributes an unintended system section or user message therefore fails the job, and every message the profile emits is compared. `advanced/` pins one complex process's SDK result and parent/child session logs across every target. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs across every target. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it.
+Four scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the Linux/macOS `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages; `minimal/win-x64/model-visible.json` pins its PowerShell counterpart. A plugin that contributes an unintended system section or user message therefore fails the job, and every message the profile emits is compared. `advanced/` pins one complex process's SDK result and parent/child session logs across every target. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs across every target. `sdk-minimal-in-history` reuses the persistent-shell and editor scenario with a section that changes after the first successful shell call. `minimal-in-history/prompt-history.json` pins both prompt versions, the unchanged leading prompt in later requests, appended SDK system-message events, and `request/context.systemPromptUpdate`; tool schemas stay fixed and the editor file is checked independently. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it.
Trusted pull requests also run `--scenario sdk-live --installed-wheel` on every native target. That scenario performs two tool-using turns against `https://api.deepseek.com`, verifies the created file externally, and fails when the repository secret is absent instead of self-skipping. Fork and Dependabot pull requests run the complete keyless installed-wheel path but receive no key.
diff --git a/python/development.zh.md b/python/development.zh.md
index a35f6fc8de..c5f68b4f01 100644
--- a/python/development.zh.md
+++ b/python/development.zh.md
@@ -34,7 +34,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \
--scenario sdk-minimal --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64
```
-其中三个场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定 Linux/macOS `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息;`minimal/win-x64/model-visible.json` 固定对应的 PowerShell 版本。因此,插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败,且该 profile 发出的每条消息都会参与比对。`advanced/` 跨所有目标固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并跨所有目标固定其彼此隔离的模型历史、高层结果与独立持久日志。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。
+其中四个场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定 Linux/macOS `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息;`minimal/win-x64/model-visible.json` 固定对应的 PowerShell 版本。因此,插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败,且该 profile 发出的每条消息都会参与比对。`advanced/` 跨所有目标固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并跨所有目标固定其彼此隔离的模型历史、高层结果与独立持久日志。`sdk-minimal-in-history` 复用持久 shell 与编辑器场景,并在首次 shell 调用成功后更改一个分段。`minimal-in-history/prompt-history.json` 固定两个提示词版本、后续请求中不变的首条提示词、追加的 SDK system-message 事件及 `request/context.systemPromptUpdate`;工具 schema 保持不变,并独立检查编辑器创建的文件。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。
可信拉取请求还会在每个原生目标上运行 `--scenario sdk-live --installed-wheel`。该场景面向 `https://api.deepseek.com` 执行两个使用工具的轮次,从外部验证已创建文件,并在仓库密钥缺失时失败而不是自行 skip。Fork 与 Dependabot 拉取请求会运行完整的 keyless 安装后 wheel 路径,但不会获得密钥。
diff --git a/scripts/fixtures/python-sdk-in-history-prompt.mjs b/scripts/fixtures/python-sdk-in-history-prompt.mjs
new file mode 100644
index 0000000000..aa7c947834
--- /dev/null
+++ b/scripts/fixtures/python-sdk-in-history-prompt.mjs
@@ -0,0 +1,18 @@
+/** Change one ordinary section after a successful persistent shell call. */
+export const name = 'python-sdk-in-history-prompt'
+export const inject = ['systemPrompt']
+
+/** @param {import('@deepseek-ai/cordis').Context} ctx - Composed runtime services. */
+export function apply(ctx) {
+ let version = 1
+ ctx.effect(() => ctx.systemPrompt.section({
+ name: 'python-sdk:in-history',
+ order: 400,
+ text: () => `Python SDK prompt version ${version}.`,
+ }))
+ ctx.on('tools/post-execute', async (exec, result, next) => {
+ const downstream = await next()
+ if (!result.isError && (exec.name === 'bash' || exec.name === 'pwsh')) version = 2
+ return downstream
+ })
+}
diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py
index a97b5970d7..3f14738cb2 100644
--- a/scripts/smoke-python-runtime.py
+++ b/scripts/smoke-python-runtime.py
@@ -134,6 +134,10 @@ MINIMAL_SNAPSHOT_DIRECTORY = (
if IS_WINDOWS:
MINIMAL_SNAPSHOT_DIRECTORY /= "win-x64"
MINIMAL_SNAPSHOT_FILENAMES = ("model-visible.json",)
+IN_HISTORY_SNAPSHOT_DIRECTORY = (
+ Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "minimal-in-history"
+)
+IN_HISTORY_SNAPSHOT_FILENAMES = ("prompt-history.json",)
RESTART_SNAPSHOT_DIRECTORY = (
Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "restart"
)
@@ -313,7 +317,8 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
messages = body.get("messages")
if not isinstance(messages, list) or not messages:
raise AssertionError(f"model request has no messages: {body}")
- latest = messages[-1]
+ # A system prompt update may follow the tool result without replacing it.
+ latest = next(message for message in reversed(messages) if message.get("role") != "system")
if not isinstance(latest, dict):
raise AssertionError(f"model request has an invalid latest message: {body}")
@@ -769,7 +774,7 @@ def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--scenario",
- choices=("all", "sdk-default", "sdk-custom", "sdk-minimal", "sdk-fs-search", "sdk-spawn-node", "sdk-mcp", "sdk-snapshot", "sdk-restart", "sdk-profile-plugin", "sdk-live", "direct"),
+ choices=("all", "sdk-default", "sdk-custom", "sdk-minimal", "sdk-minimal-in-history", "sdk-fs-search", "sdk-spawn-node", "sdk-mcp", "sdk-snapshot", "sdk-restart", "sdk-profile-plugin", "sdk-live", "direct"),
default="all",
)
parser.add_argument("--exe", type=Path)
@@ -788,10 +793,10 @@ def main() -> None:
parser.error("--scenario sdk-profile-plugin requires --installed-wheel")
if args.installed_wheel:
args.exe = assert_installed_wheel_environment()
- if args.scenario in {"all", "sdk-custom", "sdk-minimal", "sdk-fs-search", "sdk-spawn-node", "sdk-snapshot", "sdk-restart", "direct"} and args.exe is None:
+ if args.scenario in {"all", "sdk-custom", "sdk-minimal", "sdk-minimal-in-history", "sdk-fs-search", "sdk-spawn-node", "sdk-snapshot", "sdk-restart", "direct"} and args.exe is None:
parser.error("--exe is required for custom, minimal, snapshot, and direct scenarios")
- if args.update_snapshots and args.scenario not in {"all", "sdk-minimal", "sdk-snapshot", "sdk-restart"}:
- parser.error("--update-snapshots requires --scenario sdk-minimal, sdk-snapshot, sdk-restart, or all")
+ if args.update_snapshots and args.scenario not in {"all", "sdk-minimal", "sdk-minimal-in-history", "sdk-snapshot", "sdk-restart"}:
+ parser.error("--update-snapshots requires --scenario sdk-minimal, sdk-minimal-in-history, sdk-snapshot, sdk-restart, or all")
if args.exe is not None and not args.exe.is_file():
parser.error(f"runtime executable does not exist: {args.exe}")
@@ -809,6 +814,9 @@ def main() -> None:
if args.scenario in {"all", "sdk-minimal"}:
assert args.exe is not None
smoke_sdk_minimal(model.url, args.exe.resolve(), args.update_snapshots)
+ if args.scenario in {"all", "sdk-minimal-in-history"}:
+ assert args.exe is not None
+ smoke_sdk_minimal(model.url, args.exe.resolve(), args.update_snapshots, in_history=True)
if args.scenario in {"all", "sdk-fs-search"}:
assert args.exe is not None
smoke_sdk_fs_search(model.url, args.exe.resolve())
@@ -1038,7 +1046,9 @@ def smoke_sdk_custom(base_url: str, executable: Path) -> None:
assert_session_log(sessions, root, EXPECTED_TEXT, CODE_WORKER_TEXT, WORKFLOW_WORKER_TEXT)
-def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -> None:
+def smoke_sdk_minimal(
+ base_url: str, executable: Path, update_snapshots: bool, *, in_history: bool = False,
+) -> None:
"""Exercise the shipped standalone minimal profile through the packaged executable."""
from deepseek_harness import DeepSeekHarness
@@ -1050,6 +1060,19 @@ 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"
+ patches = ()
+ if in_history:
+ patch = root / "in-history.patch.yml"
+ patch.write_text(json.dumps([
+ {"id": "llm-deepseek", "config": {"models": [
+ {"id": "smoke-model", "systemPromptUpdate": "in-history"},
+ ]}},
+ {"insert": [{
+ "id": "in-history-prompt",
+ "name": (Path(__file__).resolve().parent / "fixtures/python-sdk-in-history-prompt.mjs").as_uri(),
+ }]},
+ ]))
+ patches = (str(patch),)
with DeepSeekHarness(
provider="deepseek-official",
model="smoke-model",
@@ -1057,6 +1080,7 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -
dsh_bin=str(executable),
dsh_home=str(dsh_home),
profile="sdk-minimal",
+ patches=patches,
api_key="sk-keyless-smoke",
base_url=base_url,
request_timeout_seconds=60,
@@ -1070,10 +1094,18 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -
raise AssertionError(f"packaged editor wrote unexpected content: {editor_path.read_text()!r}")
assert_session_log(sessions, root, MINIMAL_TEXT, "COUNT=1", "COUNT=2")
- files = build_minimal_snapshot_files(MockModelHandler.requests[first_request:], root)
- compare_snapshot_files(
- files, update_snapshots, MINIMAL_SNAPSHOT_DIRECTORY, MINIMAL_SNAPSHOT_FILENAMES,
- )
+ requests = MockModelHandler.requests[first_request:]
+ if in_history:
+ logs = read_session_logs(sessions)
+ files = build_in_history_snapshot_files(result, requests, logs[result.session_id])
+ compare_snapshot_files(
+ files, update_snapshots, IN_HISTORY_SNAPSHOT_DIRECTORY, IN_HISTORY_SNAPSHOT_FILENAMES,
+ )
+ else:
+ files = build_minimal_snapshot_files(requests, root)
+ compare_snapshot_files(
+ files, update_snapshots, MINIMAL_SNAPSHOT_DIRECTORY, MINIMAL_SNAPSHOT_FILENAMES,
+ )
def smoke_sdk_fs_search(base_url: str, executable: Path) -> None:
@@ -1686,6 +1718,56 @@ def snapshot_child_ids(result: "RunResult") -> list[str]:
return child_ids
+def build_in_history_snapshot_files(
+ result: "RunResult",
+ requests: list[dict[str, object]],
+ log: list[dict[str, object]],
+) -> dict[str, str]:
+ """Assert live requests, SDK subscriptions, and persistence retain both prompts."""
+ systems = [event for event in result.events if event.get("type") == "system/message"]
+ assert len(systems) == 2, systems
+ assert [event.get("surfaceOp") for event in systems] == ["append", "append"], systems
+ prompts = [message_text(event["data"]["message"]["content"]) for event in systems]
+ assert "Python SDK prompt version 1." in prompts[0], prompts
+ assert "Python SDK prompt version 2." not in prompts[0], prompts
+ assert "Python SDK prompt version 2." in prompts[1], prompts
+ assert "Python SDK prompt version 1." not in prompts[1], prompts
+ assert prompts[0] != prompts[1], prompts
+ assert [event for event in log if event.get("type") == "system/message"] == systems
+ subscribed = [
+ notification.payload["event"]
+ for notification in result.notifications
+ if notification.method == "session.event"
+ and notification.payload.get("event", {}).get("type") == "system/message"
+ ]
+ assert subscribed == systems, subscribed
+ contexts = [event["data"] for event in result.events if event.get("type") == "request/context"]
+ assert contexts and all(context.get("systemPromptUpdate") == "in-history" for context in contexts), contexts
+ assert len([event for event in result.events if event.get("type") == "request/header"]) == 1
+ assert all(event.get("surfaceOp") in (None, "append") for event in result.events)
+ first_tool = next(index for index, event in enumerate(result.events) if event.get("type") == "tool/result")
+ assert result.events.index(systems[1]) > first_tool
+ assert len(requests) == 4, requests
+ request_prompts = []
+ for index, request in enumerate(requests):
+ messages = request["messages"]
+ assert messages[0]["role"] == "system" and message_text(messages[0]["content"]) == prompts[0]
+ assert request["tools"] == requests[0]["tools"], "prompt update changed tool schemas"
+ positions = [position for position, message in enumerate(messages) if message["role"] == "system"]
+ texts = [message_text(messages[position]["content"]) for position in positions]
+ assert texts == (prompts[:1] if index == 0 else prompts), texts
+ if index > 0:
+ assert messages[positions[1] - 1]["role"] == "tool", messages
+ request_prompts.append(texts)
+ evidence = {
+ "requestSystemPrompts": request_prompts,
+ "systemMessageOperations": [event["surfaceOp"] for event in systems],
+ "subscribedSystemPrompts": prompts,
+ "requestContexts": contexts,
+ }
+ return {"prompt-history.json": json.dumps(evidence, indent=2, ensure_ascii=False) + "\n"}
+
+
def build_minimal_snapshot_files(
requests: list[dict[str, object]],
cwd: Path,
diff --git a/scripts/snapshots/python-sdk-single-exe/minimal-in-history/prompt-history.json b/scripts/snapshots/python-sdk-single-exe/minimal-in-history/prompt-history.json
new file mode 100644
index 0000000000..9fe5365ee8
--- /dev/null
+++ b/scripts/snapshots/python-sdk-single-exe/minimal-in-history/prompt-history.json
@@ -0,0 +1,35 @@
+{
+ "requestSystemPrompts": [
+ [
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1."
+ ],
+ [
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1.",
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 2."
+ ],
+ [
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1.",
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 2."
+ ],
+ [
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1.",
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 2."
+ ]
+ ],
+ "systemMessageOperations": [
+ "append",
+ "append"
+ ],
+ "subscribedSystemPrompts": [
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1.",
+ "You are a helpful software engineer assistant.\n\nPython SDK prompt version 2."
+ ],
+ "requestContexts": [
+ {
+ "provider": "deepseek-official",
+ "model": "smoke-model",
+ "contextWindow": 1000000,
+ "systemPromptUpdate": "in-history"
+ }
+ ]
+}
diff --git a/snapshots/sdk/sdk.snapshot.ts b/snapshots/sdk/sdk.snapshot.ts
index 2bb0b65254..3c821f246d 100644
--- a/snapshots/sdk/sdk.snapshot.ts
+++ b/snapshots/sdk/sdk.snapshot.ts
@@ -893,6 +893,28 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => {
expect(normalizedResult).toBe(await readFile(resultExpectedPath, 'utf8'))
}
+ if (scenario.name === 'system-prompt-in-history') {
+ const events = notifications.flatMap(notification => {
+ const event = notificationEvent(notification)
+ return event === undefined ? [] : [event]
+ })
+ const systems = events.filter(event => event.type === 'system/message')
+ expect(systems).toHaveLength(2)
+ expect(systems.map(event => event.surfaceOp)).toEqual(['append', 'append'])
+ const prompts = systems.map(event => (event.data as {
+ message: { content: Array<{ text: string }> }
+ }).message.content[0]!.text)
+ expect(prompts[0]).not.toContain('Snapshot guidance added after the first read')
+ expect(prompts[1]).toContain('Snapshot guidance added after the first read')
+ expect(prompts[1]).not.toBe(prompts[0])
+ expect(events.indexOf(systems[1]!)).toBeGreaterThan(events.findIndex(event => event.type === 'tool/result'))
+ expect(events.filter(event => event.type === 'request/header')).toHaveLength(1)
+ expect(events.filter(event => event.type === 'request/context')).toMatchObject([
+ { data: { systemPromptUpdate: 'in-history' } },
+ ])
+ expect(events.filter(event => event.surfaceOp !== undefined).every(event => event.surfaceOp === 'append')).toBe(true)
+ }
+
// Wire-shape invariants that must hold in every mode.
if (scenario.manifest.workspace?.final === true) {
const expectedWorkspace = await captureExpectedWorkspaceSnapshot(join(scenario.dir, 'workspace.expected'))
diff --git a/snapshots/sdk/system-prompt-in-history/notifications.expected.jsonl b/snapshots/sdk/system-prompt-in-history/notifications.expected.jsonl
new file mode 100644
index 0000000000..461049e2ea
--- /dev/null
+++ b/snapshots/sdk/system-prompt-in-history/notifications.expected.jsonl
@@ -0,0 +1,21 @@
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}}}
+{"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"running"}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":10,"time":0,"data":{"title":"Read task.txt with the read","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":11,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":12,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","systemPromptUpdate":"in-history"}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_task_read"},"content":[{"type":"tool-result","toolCallId":"call_task_read","content":[{"type":"text","text":"{{cwd}}/task.txt\nfile\n\n1: Reply with the single word DONE after reading this file.\n\n(End of file - total 1 lines)\n"}],"isError":false}],"role":"user","id":"{{sessionId}}"},"meta":{"path":"{{cwd}}/task.txt","offset":1,"lines":[{"number":1,"text":"Reply with the single word DONE after reading this file."}],"totalLines":1}},"sourceEventSeqs":[14],"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":17,"time":0,"data":{"turn":1,"step":2}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":18,"time":0,"data":{"turn":1,"step":2,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":19,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":20,"time":0,"data":{"turn":1,"step":2}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":21,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}}
+{"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}}
diff --git a/snapshots/sdk/system-prompt-in-history/replay.override.json b/snapshots/sdk/system-prompt-in-history/replay.override.json
new file mode 100644
index 0000000000..73179c6ca3
--- /dev/null
+++ b/snapshots/sdk/system-prompt-in-history/replay.override.json
@@ -0,0 +1,22 @@
+[
+ {
+ "kind": "chunks",
+ "chunks": [
+ { "type": "block-start", "index": 0, "blockType": "tool-call" },
+ { "type": "tool-call-delta", "index": 0, "id": "call_task_read", "name": "read", "argumentsDelta": "{\"file_path\":\"task.txt\"}" },
+ { "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_task_read", "name": "read", "arguments": "{\"file_path\":\"task.txt\"}" } },
+ { "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
+ { "type": "finish", "reason": { "kind": "tool-calls" } }
+ ]
+ },
+ {
+ "kind": "chunks",
+ "chunks": [
+ { "type": "block-start", "index": 0, "blockType": "text" },
+ { "type": "text-delta", "index": 0, "text": "DONE" },
+ { "type": "block-end", "index": 0, "block": { "type": "text", "text": "DONE" } },
+ { "type": "usage", "usage": { "inputTokens": 12, "outputTokens": 2, "cacheReadTokens": 10 } },
+ { "type": "finish", "reason": { "kind": "stop" } }
+ ]
+ }
+]
diff --git a/snapshots/sdk/system-prompt-in-history/result.expected.json b/snapshots/sdk/system-prompt-in-history/result.expected.json
new file mode 100644
index 0000000000..a078961bf8
--- /dev/null
+++ b/snapshots/sdk/system-prompt-in-history/result.expected.json
@@ -0,0 +1 @@
+{"sessionId":"{{sessionId}}","finalResponse":"DONE"}
diff --git a/snapshots/sdk/system-prompt-in-history/session.v2.jsonl b/snapshots/sdk/system-prompt-in-history/session.v2.jsonl
new file mode 100644
index 0000000000..4d178df2e6
--- /dev/null
+++ b/snapshots/sdk/system-prompt-in-history/session.v2.jsonl
@@ -0,0 +1,23 @@
+{"type":"session","version":2,"id":"{{session:1}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0}
+{"type":"permission/preset","data":{"preset":"danger-full-access"}}
+{"type":"sandbox/mode","data":{"mode":"danger-full-access"}}
+{"type":"approval/policy","data":{"policy":"never"}}
+{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}}
+{"type":"turn/start","data":{"turn":1}}
+{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
+{"type":"step/start","data":{"turn":1,"step":1}}
+{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"session/title","data":{"title":"Read task.txt with the read","messageSeqs":[8],"source":{"kind":"fallback"}}}
+{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
+{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","systemPromptUpdate":"in-history"}}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}
+{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_task_read"},"content":[{"type":"tool-result","toolCallId":"call_task_read","content":[{"type":"text","text":"{{cwd}}/task.txt\nfile\n\n1: Reply with the single word DONE after reading this file.\n\n(End of file - total 1 lines)\n"}],"isError":false}],"role":"user","id":"{{message:5}}"},"meta":{"path":"{{cwd}}/task.txt","offset":1,"lines":[{"number":1,"text":"Reply with the single word DONE after reading this file."}],"totalLines":1}},"sourceEventSeqs":[14],"surfaceOp":"append"}
+{"type":"step/end","data":{"turn":1,"step":1}}
+{"type":"step/start","data":{"turn":1,"step":2}}
+{"type":"system/message","data":{"turn":1,"step":2,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:6}}"}},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"step/end","data":{"turn":1,"step":2}}
+{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
diff --git a/snapshots/sdk/system-prompt-in-history/snapshot.yml b/snapshots/sdk/system-prompt-in-history/snapshot.yml
new file mode 100644
index 0000000000..45c99997a7
--- /dev/null
+++ b/snapshots/sdk/system-prompt-in-history/snapshot.yml
@@ -0,0 +1,9 @@
+version: 1
+scenario: system-prompt-in-history
+profile: sdk
+composition: system-prompt-in-history
+recording: authored
+header:
+ class: system-prompt-in-history
+replay:
+ override: true
diff --git a/snapshots/sdk/system-prompt-in-history/workspace/task.txt b/snapshots/sdk/system-prompt-in-history/workspace/task.txt
new file mode 100644
index 0000000000..57548edaf3
--- /dev/null
+++ b/snapshots/sdk/system-prompt-in-history/workspace/task.txt
@@ -0,0 +1 @@
+Reply with the single word DONE after reading this file.