Skip to content

Commit 4655215

Browse files
committed
test(deepagent-code): add prompt intent real LLM coverage
1 parent da90e22 commit 4655215

8 files changed

Lines changed: 488 additions & 12 deletions

File tree

design/real-llm-testing.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
## 0. 权威状态与硬合同
88

9-
截至 2026-07-31,聚合 runner 注册 55 条命令,其中 48 条调用真实模型;headless 矩阵为 48 条命令、43 条真实模型 suite,进一步跳过 EVAL 和安装后为 46 条命令、42 条真实模型 suite。数字由 `script/run-live-llm-all.ts` 动态注册表决定,文档数字发生漂移时以注册表和 `validateSuiteManifest()` 为准。
9+
截至 2026-08-06,聚合 runner 注册 58 条命令,其中 51 条调用真实模型;headless 矩阵为 51 条命令、46 条真实模型 suite,进一步跳过 EVAL 和安装后为 49 条命令、45 条真实模型 suite。数字由 `script/run-live-llm-all.ts` 动态注册表决定,文档数字发生漂移时以注册表和 `validateSuiteManifest()` 为准。
1010

1111
`qualifiedLiveRuns` 当前为空。注册、单次通过和 EXT 可达都不代表 pre-push 资格;LIVE suite 只有完成本节资格合同后才能进入该集合。
1212

@@ -208,6 +208,7 @@ export DEEPAGENT_CODE_LIVE_LLM_TIMEOUT_MS="180000"
208208
(cd packages/deepagent-code && bun run test:llm-ext:subagent-intensity)
209209
(cd packages/deepagent-code && bun run test:llm-ext:subagent-resume)
210210
(cd packages/deepagent-code && bun run test:llm-ext:subagent-takeover)
211+
(cd packages/deepagent-code && bun run test:llm-ext:prompt-intent-fencing)
211212
(cd packages/desktop && bun run test:llm-release:ui)
212213
```
213214

@@ -251,7 +252,13 @@ Git 合同分入口定义:交互式写型 `task` 使用独立 worktree,并
251252

252253
审批后的完整闭环由直接生产状态机测试验证:Reviewer 首次返回 `request_changes` 后,使用原 `task_id` 恢复同一个 V4 child Session 和作者 worktree,重新提交到同一 PR;再次 `pr_finalize` 后绑定新 SHA 审阅、`--no-ff` merge、Senior Reviewer 和 worktree cleanup 全部完成。默认 V4 分区规则当前没有产生独立同 wave 节点,因此 V4 wave pool 的并行性使用确定性双 runner 同步屏障证明;真实模型并行性由 `multi-agent-parallel-worktrees``expert-panel` 覆盖,不能把两类证据混写。
253254

254-
### 5.5 Expert Panel 与缓存边界
255+
### 5.5 智能直达、Prompt Intent 与撤回重写
256+
257+
`prompt-intent-fencing` 在同一个真实 DeepSeek Session 中覆盖两条生产链。第一条先通过 intelligence prepare 得到 `route=general`,再以同一个 durable intent 提交原始输入;Oracle 要求 `session_intent``admitted/original/turn`、Session 中只有一条原始用户消息和一次真实 provider 执行。在 provider 仍为 busy 的窗口内,ACK 丢失后的 exact retry 不得增加用户消息,晚到的 rewritten 草稿必须以 `SessionPromptIntent.Conflict` 拒绝,不得排队或启动第二次执行。第二条对该消息执行生产 `SessionRevert`,要求 mutation epoch 精确递增一次,旧 intent retry 以 `SessionMutationEpoch.Stale` 拒绝,再以新 rewrite intent 完成一次真实 provider 执行;清理后的活动 transcript 只能保留一条 rewrite 用户消息,且不得残留旧回复、工具调用、权限请求或工作区改动。
258+
259+
该 EXT suite 只证明真实 refinement/provider 与 durable admission/revert 接线一致,不替代竞态正确性证明。并发 claim、事务原子性、ACK crash window、跨窗口相同 intent、revert 与 materialize 的交错顺序仍由 SQLite/Effect/前端 barrier 的确定性测试承担;不得用模型最终文本或单次 live 通过声称 exactly-once 状态机已经完备。
260+
261+
### 5.6 Expert Panel 与缓存边界
255262

256263
`expert-panel` 必须并行创建 correctness、security、architecture 三个 Reviewer Session。每个 Reviewer 只能成功读取自己的目标文件,必须经过独立的 structured finalizer,并由确定性 arbiter 重算最终 verdict 和 dissent。Oracle 要求每个 lens 至少有一条命中预埋代码事实且置信度为 `0.95` 的 finding;额外 finding 只要仍受目标文件、类别、证据隔离和置信度范围约束,就不应被误判为产品故障。模型对未授权路径的额外 read 尝试可以存在,但必须由权限层拒绝;任何额外成功读取或其他工具调用都应失败。
257264

packages/deepagent-code/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"test:llm-ext:compaction-retention": "bun run script/live-llm/compaction-retention.ts",
4646
"test:llm-ext:expert-panel": "bun run script/live-llm/expert-panel.ts",
4747
"test:llm-ext:intelligence-draft": "bun run script/live-llm/cli-intelligence.ts",
48+
"test:llm-ext:prompt-intent-fencing": "bun run script/live-llm/prompt-intent-fencing.ts",
4849
"test:llm-live:subagent-control-plane": "bun run script/live-llm/subagent-control-plane.ts",
4950
"test:llm-eval:autonomous": "bun run script/live-llm/autonomous-eval.ts",
5051
"test:httpapi": "bun run script/httpapi-exercise.ts --mode coverage --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode auth --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode effect --fail-on-missing --fail-on-skip",

packages/deepagent-code/script/live-llm/dispatcher.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,25 @@ const checkCommands: Record<DeterministicCheck, DispatcherCommand[]> = {
112112
"test/agent/subagent-plan-permission.test.ts",
113113
),
114114
],
115+
"prompt-intent": [
116+
command(
117+
"packages/deepagent-code",
118+
"bun",
119+
"test",
120+
"test/session/prompt-intent.test.ts",
121+
"test/session/revert-compact.test.ts",
122+
"test/session/steer.test.ts",
123+
),
124+
command(
125+
"packages/app",
126+
"bun",
127+
"test",
128+
"--preload",
129+
"./happydom.ts",
130+
"src/components/prompt-input/submit.test.ts",
131+
"src/pages/session/followup-submission.test.ts",
132+
),
133+
],
115134
mcp: [
116135
command("packages/deepagent-code", "bun", "typecheck"),
117136
command("packages/deepagent-code", "bun", "test", "test/mcp", "test/deepagent/mcp-provenance.test.ts"),
@@ -247,6 +266,10 @@ const modelCommands = new Map<string, DispatcherCommand>([
247266
"ext:legacy-session:intelligence-draft-confirmation",
248267
command("packages/deepagent-code", "bun", "run", "test:llm-ext:intelligence-draft"),
249268
],
269+
[
270+
"ext:legacy-session:prompt-intent-fencing",
271+
command("packages/deepagent-code", "bun", "run", "test:llm-ext:prompt-intent-fencing"),
272+
],
250273
[
251274
"live:legacy-session:subagent-control-plane",
252275
command("packages/deepagent-code", "bun", "run", "test:llm-live:subagent-control-plane"),
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import path from "node:path"
2+
import { writeLiveArtifact } from "../../../llm/script/live-llm/config"
3+
import { finishLiveScript } from "./lifecycle"
4+
import { runLegacyLiveCases } from "./runtime"
5+
6+
const directMarker = `direct-intent-${crypto.randomUUID()}`
7+
const rewriteMarker = `rewrite-intent-${crypto.randomUUID()}`
8+
const directIntentID = `intent-direct-${crypto.randomUUID()}`
9+
const rewriteIntentID = `intent-rewrite-${crypto.randomUUID()}`
10+
// Keep classifier input natural; response markers belong to the agent prompt, not the user request.
11+
const directPrompt = "你好"
12+
const conflictingPrompt = "你好!"
13+
const rewritePrompt = "谢谢"
14+
15+
const artifact = await runLegacyLiveCases({
16+
suite: "prompt-intent-fencing-legacy",
17+
permission: { "*": "deny" },
18+
cases: [
19+
{
20+
name: "intelligence-direct",
21+
prompt: directPrompt,
22+
intelligence: { outputLanguage: "english", expectedRoute: "general" },
23+
admission: {
24+
intentID: directIntentID,
25+
source: "intelligence",
26+
variant: "original",
27+
exactRetry: true,
28+
conflictingRetry: { prompt: conflictingPrompt, variant: "rewritten" },
29+
},
30+
},
31+
{
32+
name: "revert-rewrite",
33+
prompt: rewritePrompt,
34+
admission: {
35+
intentID: rewriteIntentID,
36+
source: "rewrite",
37+
variant: "rewritten",
38+
exactRetry: true,
39+
},
40+
revertBefore: { targetCase: "intelligence-direct", retryTargetIntent: true },
41+
},
42+
],
43+
sharedSession: true,
44+
primaryPrompt:
45+
`When the user says "${directPrompt}", reply with exactly ${directMarker}. ` +
46+
`When the user says "${rewritePrompt}", reply with exactly ${rewriteMarker}. ` +
47+
"Do not call tools or add any other text.",
48+
modelMaxTokens: 128,
49+
maxProviderTurns: 2,
50+
})
51+
52+
const direct = requireCase("intelligence-direct")
53+
const rewrite = requireCase("revert-rewrite")
54+
55+
if (direct.sessionID !== rewrite.sessionID) throw new Error("Prompt intent fencing did not use one durable Session")
56+
if (direct.intelligenceDraft?.route !== "general" || direct.intelligenceDraft.preview !== directPrompt) {
57+
throw new Error("Intelligence preparation did not select the direct route with the original prompt")
58+
}
59+
if (
60+
direct.admission?.state !== "admitted" ||
61+
direct.admission.source !== "intelligence" ||
62+
direct.admission.variant !== "original" ||
63+
direct.admission.delivery !== "turn"
64+
) {
65+
throw new Error(`Direct intent receipt was invalid: ${JSON.stringify(direct.admission)}`)
66+
}
67+
const directRetry = direct.admission.retry
68+
if (
69+
directRetry?.exact?.accepted !== true ||
70+
!directRetry.activeBeforeRetry ||
71+
directRetry.exact.userCountBefore !== directRetry.exact.userCountAfter
72+
) {
73+
throw new Error("Exact direct-intent retry was not an active-turn admission no-op")
74+
}
75+
if (
76+
directRetry.conflict?.accepted !== false ||
77+
directRetry.conflict.error !== "SessionPromptIntent.Conflict"
78+
) {
79+
throw new Error(`Late rewritten draft was not rejected: ${JSON.stringify(directRetry.conflict)}`)
80+
}
81+
if (direct.users.length !== 1 || direct.users[0]?.text !== directPrompt) {
82+
throw new Error(`Direct route materialized ${direct.users.length} user messages instead of the original input once`)
83+
}
84+
if (direct.users[0].metadata?.deepagent?.prompt_pipeline?.mode !== "direct_override") {
85+
throw new Error("Direct-route user message did not preserve direct_override metadata")
86+
}
87+
if (
88+
direct.assistantTurns !== 1 ||
89+
direct.newTools.length !== 0 ||
90+
direct.providerErrors.length !== 0 ||
91+
!direct.finalText.includes(directMarker)
92+
) {
93+
throw new Error("Direct-route execution did not complete exactly once through the real provider")
94+
}
95+
96+
if (
97+
rewrite.revert?.targetCase !== "intelligence-direct" ||
98+
rewrite.revert.epochAfter !== rewrite.revert.epochBefore + 1 ||
99+
rewrite.revert.retry?.accepted !== false ||
100+
rewrite.revert.retry.error !== "SessionMutationEpoch.Stale"
101+
) {
102+
throw new Error(`Revert did not fence the old prompt intent: ${JSON.stringify(rewrite.revert)}`)
103+
}
104+
if (
105+
rewrite.admission?.state !== "admitted" ||
106+
rewrite.admission.source !== "rewrite" ||
107+
rewrite.admission.variant !== "rewritten" ||
108+
rewrite.admission.mutationEpoch !== rewrite.revert.epochAfter
109+
) {
110+
throw new Error(`Rewrite intent receipt was invalid: ${JSON.stringify(rewrite.admission)}`)
111+
}
112+
const rewriteRetry = rewrite.admission.retry
113+
if (
114+
rewriteRetry?.exact?.accepted !== true ||
115+
!rewriteRetry.activeBeforeRetry ||
116+
rewriteRetry.exact.userCountBefore !== rewriteRetry.exact.userCountAfter
117+
) {
118+
throw new Error("Exact rewrite retry was not an active-turn admission no-op")
119+
}
120+
if (rewrite.users.length !== 1 || rewrite.users[0]?.text !== rewritePrompt) {
121+
throw new Error(`Revert/rewrite materialized ${rewrite.users.length} current user messages instead of one`)
122+
}
123+
if (
124+
rewrite.assistantTurns !== 1 ||
125+
rewrite.newTools.length !== 0 ||
126+
rewrite.providerErrors.length !== 0 ||
127+
!rewrite.finalText.includes(rewriteMarker)
128+
) {
129+
throw new Error("Rewritten prompt did not execute exactly once through the real provider")
130+
}
131+
if (rewrite.allText.includes(directMarker) || artifact.workspace.status.trim()) {
132+
throw new Error("Revert left stale output in the active transcript or mutated the workspace")
133+
}
134+
if (
135+
artifact.cases.some(
136+
(testCase) => testCase.permissionRequests.length !== 0 || testCase.questionRequests.length !== 0,
137+
)
138+
) {
139+
throw new Error("Prompt intent fencing requested undeclared permission or question input")
140+
}
141+
142+
const result = {
143+
...artifact,
144+
mode: "ext" as const,
145+
evidence: {
146+
directIntentHash: Bun.hash(directIntentID).toString(16),
147+
rewriteIntentHash: Bun.hash(rewriteIntentID).toString(16),
148+
directMarkerHash: Bun.hash(directMarker).toString(16),
149+
rewriteMarkerHash: Bun.hash(rewriteMarker).toString(16),
150+
exactRetries: 2,
151+
conflictingDraftRejected: true,
152+
staleEpochRetryRejected: true,
153+
mutationEpochAdvance: rewrite.revert.epochAfter - rewrite.revert.epochBefore,
154+
userMessagesAfterRewrite: rewrite.users.length,
155+
providerTurns: direct.assistantTurns + rewrite.assistantTurns,
156+
},
157+
}
158+
159+
await writeLiveArtifact(
160+
{ artifactDirectory: path.resolve(import.meta.dir, "../../.artifacts/live-llm") },
161+
result.suite,
162+
result,
163+
{
164+
redactions: [
165+
{ value: directMarker, replacement: `<direct-marker hash=${result.evidence.directMarkerHash}>` },
166+
{ value: rewriteMarker, replacement: `<rewrite-marker hash=${result.evidence.rewriteMarkerHash}>` },
167+
{ value: directIntentID, replacement: `<direct-intent hash=${result.evidence.directIntentHash}>` },
168+
{ value: rewriteIntentID, replacement: `<rewrite-intent hash=${result.evidence.rewriteIntentHash}>` },
169+
],
170+
},
171+
)
172+
console.log(
173+
`${result.suite}: passed (${result.fingerprint.providerID}/${result.fingerprint.modelID}, ` +
174+
`${result.evidence.providerTurns} provider turns, epoch +${result.evidence.mutationEpochAdvance})`,
175+
)
176+
177+
finishLiveScript()
178+
179+
function requireCase(name: string) {
180+
const testCase = artifact.cases.find((item) => item.name === name)
181+
if (!testCase) throw new Error(`Missing prompt-intent case ${name}`)
182+
return testCase
183+
}

packages/deepagent-code/script/live-llm/routes.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const modelSuites = [
4545
"expert-panel",
4646
"goal-grader-cli-entry",
4747
"intelligence-draft-confirmation",
48+
"prompt-intent-fencing",
4849
"subagent-control-plane",
4950
] as const
5051

@@ -65,6 +66,7 @@ export type DeterministicCheck =
6566
| "llm-adapter"
6667
| "mcp"
6768
| "permission"
69+
| "prompt-intent"
6870
| "session-continuation"
6971
| "session-v2"
7072
| "tool-bash-sandbox"
@@ -115,6 +117,7 @@ const compactionRetention = modelRun("ext", "legacy-session", "compaction-retent
115117
const expertPanel = modelRun("ext", "legacy-session", "expert-panel")
116118
const goalGraderCliEntry = modelRun("ext", "cli-subprocess", "goal-grader-cli-entry")
117119
const intelligenceDraft = modelRun("ext", "legacy-session", "intelligence-draft-confirmation")
120+
const promptIntentFencing = modelRun("ext", "legacy-session", "prompt-intent-fencing")
118121
const subagentControlPlane = modelRun("live", "legacy-session", "subagent-control-plane")
119122
const allHarnessRuns = [
120123
adapterProvider,
@@ -151,6 +154,7 @@ const allHarnessRuns = [
151154
expertPanel,
152155
goalGraderCliEntry,
153156
intelligenceDraft,
157+
promptIntentFencing,
154158
subagentControlPlane,
155159
]
156160

@@ -954,6 +958,26 @@ export const routeManifest = [
954958
checks: ["llm-adapter"],
955959
runs: [intelligenceDraft],
956960
},
961+
{
962+
id: "prompt-intent-fencing-suite",
963+
paths: [
964+
"packages/app/src/components/prompt-input/**",
965+
"packages/app/src/pages/session.tsx",
966+
"packages/app/src/pages/session/followup-submission.ts",
967+
"packages/core/src/database/migration/20260806051000_session_prompt_intent.ts",
968+
"packages/core/src/database/migration/20260806060000_session_mutation_epoch.ts",
969+
"packages/core/src/session/sql.ts",
970+
"packages/deepagent-code/script/live-llm/prompt-intent-fencing.ts",
971+
"packages/deepagent-code/src/server/routes/instance/httpapi/handlers/session.ts",
972+
"packages/deepagent-code/src/session/mutation-epoch.ts",
973+
"packages/deepagent-code/src/session/prompt-intent.ts",
974+
"packages/deepagent-code/src/session/prompt.ts",
975+
"packages/deepagent-code/src/session/revert.ts",
976+
"packages/deepagent-code/src/session/session.ts",
977+
],
978+
checks: ["prompt-intent"],
979+
runs: [promptIntentFencing],
980+
},
957981
] satisfies Route[]
958982

959983
export const owningPaths = [

0 commit comments

Comments
 (0)