dsh-math-modeling-agent 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,19 +108,21 @@ D3 方向/D4 裁决),`run-state.mjs gate` 在每次状态转移前强制校
108
108
  安装:
109
109
 
110
110
  ```bash
111
- dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.4
111
+ dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.6
112
112
  dsh --profile web --dump-config # 检查组合层(应看到 dsh-math-modeling-agent-skills 行)
113
113
  dsh web # 重启以加载插件
114
114
  ```
115
115
 
116
- (npm 通道装最新版:`dsh plugin --profile web add dsh-math-modeling-agent`;
117
- 如本机配置了镜像源且同步滞后,可显式走官方源:
116
+ (npm 源通道:`dsh plugin --profile web add dsh-math-modeling-agent` —— 从 npm registry 拉最新版;
117
+ `dsh plugin` 会在 profile 目录执行 pnpm 安装并自动把插件注册进 `dsh.profile.bundles`,
118
+ 不要用 `npm install` 代替,那会装到错误位置。如本机镜像源同步滞后,可显式走官方源:
118
119
  `dsh plugin --profile web add dsh-math-modeling-agent --registry=https://registry.npmjs.org/`)
119
120
 
120
- 开始建模——直接描述任务即可。每个会改变模型结构的决策都会在对话中交互确认
121
- (任务路由、各子问题假设、候选方向、每轮裁决),每轮在对话直接输出 runlog 式摘要
122
- (问题重述/分析/假设/建模求解/验证/鲁棒性/评价改进/参考文献),失败尝试按类归档到独立目录,
123
- 终态前强制鲁棒性敏感度分析:
121
+ 开始建模——直接描述任务即可。每个环节都是人与 LLM 的深度信息交换:LLM 给出带依据链的
122
+ 完整分析(题面原句/数据证据/文献/显式判断标记),你纠正、补充背景或提供自己的参考文献,
123
+ LLM 更新并展示差异。建模前必须先做文献调研(AI 检索原理与方法文献,你可增删),每个候选
124
+ 方向都有文献出处;每轮在对话直接输出 runlog 式摘要(问题重述/分析/假设/建模求解/验证/鲁棒性/
125
+ 评价改进/参考文献),失败尝试按类归档,终态前强制鲁棒性敏感度分析:
124
126
 
125
127
  ```text
126
128
  建立这个数学建模问题的模型,先分析题目和数据。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-math-modeling-agent",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Evidence-driven mathematical modeling and verification skills for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "files": [
@@ -15,7 +15,7 @@ Move one mathematical problem from scoped intake to an evidence-backed conclusio
15
15
 
16
16
  Accept problem text, attachments, or an existing run directory.
17
17
 
18
- Interaction model (references/interaction-protocol.md): execution details stay autonomous (file names, tool adapters, state fields), but every model-shaping decision is interactive — D0 restatement / D1 routing once per task, D2 assumptions and D3 direction per subproblem, D2' on mid-subproblem discoveries, D4 verdict per round. Show the runlog digest in the chat after every round (references/report-contract.md); announce one status line before every transition; the user may interrupt at any time.
18
+ Interaction model (references/interaction-protocol.md): execution details stay autonomous (file names, tool adapters, state fields), but every model-shaping decision is a DEEP three-beat exchange LLM presents a complete analysis with its evidence chain, the user responds with substance (corrections, background, their own references), the LLM updates the artifact and shows the delta. Decision points: D0 six-section restatement / D1 routing once per task, D-R literature research before any candidate is built, D2 assumptions and D3 direction per subproblem, D2' on mid-subproblem discoveries, D4 verdict per round. Show the runlog digest in the chat after every round (references/report-contract.md); announce one status line before every transition; the user may interrupt at any time.
19
19
 
20
20
  Return a concise current status, supported claims, unresolved obligations, attempt delta, artifact path, and next action or final report.
21
21
 
@@ -26,13 +26,14 @@ Return a concise current status, supported claims, unresolved obligations, attem
26
26
  3. Create or validate run state with `scripts/run-state.mjs` and follow `references/state-recovery.md`. Gate EVERY transition: `run-state.mjs gate <dir> --to <status> --reason "..."`.
27
27
  4. Profile inputs and build the subproblem DAG using `references/data-subproblems.md`.
28
28
  5. Register assumptions (per subproblem), claims, and verification obligations from `references/claims-evidence.md`; ask D2 for the subproblem being started.
29
- 6. Build a baseline and materially different candidates using `references/modeling-methodology.md`; ask D3 (candidate matrix).
30
- 7. Probe tools with `scripts/capability-probe.mjs`; create a run-local Python environment with `scripts/python-environment.mjs` only when computation is required. Announce the tool choice and fallback for the phase (references/tool-policy.md).
31
- 8. Execute, verify, critique, and revise according to `references/workflow.md` and `references/tool-policy.md`; ask D4 after each VERIFY.
32
- 9. Dispatch subagents for parallel exploration, blind verification, and audits per `references/subagent-dispatch.md`.
33
- 10. Escalate evidence gaps or stalled directions through `references/research-breakthrough.md`; write wall memos and the failure ledger per `references/run-directory.md`.
34
- 11. Write only incremental attempt and terminal reports defined by `references/report-contract.md`; output the per-round runlog digest in the chat.
35
- 12. Before any terminal transition, run the cleanup gate (references/run-directory.md) and the robustness gate (a PASSED robustness-kind obligation, or scope.robustnessExempt with reason).
29
+ 6. **Literature research (mandatory, before candidates)**: transition CLAIMS_REGISTERED → RESEARCH, search principles and methods by direction (never contest solutions), write `research/sources.jsonl`, attach a literature column to the candidate matrix, then ask D-R (user endorses/rejects/adds references) and move to CANDIDATES_READY.
30
+ 7. Build a baseline and materially different candidates using `references/modeling-methodology.md`; ask D3 (candidate matrix).
31
+ 8. Probe tools with `scripts/capability-probe.mjs`; create a run-local Python environment with `scripts/python-environment.mjs` only when computation is required. Announce the tool choice and fallback for the phase (references/tool-policy.md).
32
+ 9. Execute, verify, critique, and revise according to `references/workflow.md` and `references/tool-policy.md`; ask D4 after each VERIFY.
33
+ 10. Dispatch subagents for parallel exploration, blind verification, and audits per `references/subagent-dispatch.md`.
34
+ 11. Escalate evidence gaps or stalled directions through `references/research-breakthrough.md`; write wall memos and the failure ledger per `references/run-directory.md`.
35
+ 12. Write only incremental attempt and terminal reports defined by `references/report-contract.md`; output the per-round runlog digest in the chat.
36
+ 13. Before any terminal transition, run the cleanup gate (references/run-directory.md) and the robustness gate (a PASSED robustness-kind obligation, or scope.robustnessExempt with reason).
36
37
 
37
38
  ## Invariants
38
39
 
@@ -8,6 +8,8 @@ Store claim ID, exact wording, type, scope/quantifiers, assumptions, risk, verif
8
8
 
9
9
  Store id, statement, scope (subproblem ids or global), source category, risk, sensitivity plan, validation status, affected claims, status (active/revised/superseded), and revisionHistory [{at, reason, changedTo}]. Allowed source categories are problem, data, theory, verified literature, domain instruction, and modeling simplification.
10
10
 
11
+ Every assumption additionally carries a `basis` field — the evidence chain behind it: `problem-quote` (exact original wording), `data` (evidence id + statistic), `literature` (source id + excerpt location), or `llm-judgment` (explicitly flagged as the LLM's own inference, challengeable in D2).
12
+
11
13
  Revisions are hard operations: when an assumption is revised, every claim/obligation that depended on it is marked `stale: true` with the revising assumption id, and the affected verification reruns (or the conclusion is explicitly downgraded) before any terminal state.
12
14
 
13
15
  ## Robustness obligation (v2, terminal gate)
@@ -3,28 +3,85 @@
3
3
  Three interaction layers. Execution details stay autonomous; every
4
4
  model-shaping decision is visible and user-owned.
5
5
 
6
+ ## Deep-exchange protocol (v2.6)
7
+
8
+ Every L1 decision point follows a three-beat exchange, never a one-way
9
+ "here is my conclusion, confirm it":
10
+
11
+ 1. **LLM presents a complete analysis with its evidence chain.** Every claim
12
+ inside the analysis carries its basis: an exact quote from the problem
13
+ text, a data statistic, a literature source id, or an explicit
14
+ "LLM judgment" marker. A naked conclusion is not a presentation.
15
+ 2. **The user responds with substance**: confirmation, correction, background
16
+ knowledge, their own references, or their own judgment. A pure "ok" is
17
+ accepted, but the protocol must have OFFERED the space for substance —
18
+ the questions must be open, not yes/no gates.
19
+ 3. **The LLM updates the artifact and shows the delta.** The restatement,
20
+ analysis log, assumption list, or candidate matrix is edited and the chat
21
+ shows exactly which points changed because of the user's input.
22
+
23
+ High-impact points (D0, D-R, D2) may loop beats 2-3 as many times as needed
24
+ until the user says "可以了". Mathematical modeling is a deep activity;
25
+ "skim and start solving" is a protocol violation.
26
+
6
27
  ## L1 decision points (MUST ask via the question tool)
7
28
 
8
29
  | Point | Trigger | Question | Default |
9
30
  |---|---|---|---|
10
- | D0 问题重述 | intake complete | confirm the full problem restatement (all sub-questions, constraints, deliverables) | accept |
31
+ | D0 问题重述 | intake complete | the six-section deep restatement below + open clarification questions | accept + no additions |
11
32
  | D1 路由 | TRIAGE | task type (prediction/optimization/explanation/proof/…) + assurance mode (Fast/Standard/High-Assurance) | auto-routing result |
12
- | D2 假设确认 | **each subproblem starts** | the assumptions registered for THIS subproblem (numbered list: statement + source + risk); user confirms or edits | accept all |
33
+ | D-R 文献研究 | CLAIMS_REGISTERED (before CANDIDATES_READY) | the literature list by direction (AI-found); user endorses / rejects / adds their own | accept list |
34
+ | D2 假设确认 | **each subproblem starts** | the assumptions for THIS subproblem, each with statement + basis chain (problem quote / data evidence / literature / LLM-judgment marker) + risk + challengeable point | accept all |
13
35
  | D2' 新假设插问 | mid-subproblem discovery that changes model structure | the new assumption + its effect | record-and-continue |
14
- | D3 方向选择 | CANDIDATES_READY | candidate matrix (mechanism/assumptions/cost/verification/expected gain); user picks or authorizes auto | baseline |
15
- | D4 结果裁决 | VERIFY done | verdict (PASS/FAIL/INCONCLUSIVE) + evidence digest; user chooses accept/REVISE/switch direction/end | verdict-implied |
36
+ | D3 方向选择 | CANDIDATES_READY | candidate matrix with a literature column per direction; user picks or authorizes auto | baseline |
37
+ | D4 结果裁决 | VERIFY done | verdict + evidence digest + key intermediate results (residual/constraint-violation distributions, figure paths) + failure details | verdict-implied |
38
+
39
+ ## D0 deep restatement — six sections (MANDATORY structure)
40
+
41
+ 1. **题目要点摘录**:the problem's own numbers and constraints, quoted.
42
+ 2. **背景解读**:the engineering/physical mechanism behind the problem.
43
+ 3. **数据语义**:every attachment, column by column — meaning, units,
44
+ coordinate system, magnitudes; unreadable artifacts are flagged with the
45
+ minimum representation needed.
46
+ 4. **隐含信息与歧义清单**:numbered; each entry = the LLM's reading + its
47
+ basis + the risk if the reading is wrong. This is the heart of the
48
+ exchange: the user corrects entries here.
49
+ 5. **三问数学本质判定**:each sub-question classified (fitting/optimization/
50
+ geometric optics/simulation/…), with why.
51
+ 6. **待澄清问题**:open questions inviting the user to add background,
52
+ correct a reading, or supply their own references — never a yes/no list.
53
+
54
+ After the user answers, problem-brief.md is updated and the delta is shown.
55
+
56
+ ## D-R literature research (pre-modeling, mandatory)
57
+
58
+ 1. The LLM searches by direction × method for principles and methods only —
59
+ engineering background, methodology papers, textbooks, official docs.
60
+ **Never** search for solutions/answers to the specific contest problem.
61
+ 2. Findings are written to research/sources.jsonl (author/title/year/venue/
62
+ URL/retrieval date/excerpt with section or theorem/table location/mapped
63
+ direction). Every candidate in the method matrix gets a literature column
64
+ referencing these source ids; a direction with no literature must be
65
+ explicitly marked "原创方向" with its derivation basis.
66
+ 3. The user is asked: endorse / reject / add their own references. User-added
67
+ references join sources.jsonl with the same fields.
68
+ 4. The D-R interaction record goes into ledger.scope.interactions; the gate
69
+ refuses CANDIDATES_READY without it.
70
+
71
+ ## Rules
16
72
 
17
- Rules:
18
- - D0/D1: once per task. D2/D3: once per subproblem. D2': only when a discovery changes model structure. D4: once per attempt round.
73
+ - D0/D1/D-R: once per task. D2/D3: once per subproblem. D2': only when a
74
+ discovery changes model structure. D4: once per attempt round.
19
75
  - Every L1 record is appended to `ledger.scope.interactions` as
20
76
  `{id, decisionPoint, question, options, userAnswer, timestamp, effectOnModel}`
21
77
  or, for explicit auto authorization,
22
78
  `{auto: true, decisionPoint, reason}`.
23
79
  - Run the gate before every transition: `run-state.mjs gate <dir> --to <status> --reason "<...>"`.
24
- The gate refuses SCOPE_FROZEN without D1, ATTEMPT-from-CANDIDATES_READY without D3,
25
- terminals-from-VERIFY without D4, and REVISE/RESEARCH whose reason fails to
26
- reference a `decisionStack` entry id. The CLI `transition` command enforces
27
- the same gate (escape hatch `--skip-gate true` is for mechanism testing only).
80
+ The gate refuses SCOPE_FROZEN without D0+D1, CANDIDATES_READY without D-R,
81
+ ATTEMPT-from-CANDIDATES_READY without D3, terminals-from-VERIFY without D4,
82
+ and REVISE/RESEARCH (from VERIFY) whose reason fails to reference a
83
+ `decisionStack` entry id. The CLI `transition` command enforces the same
84
+ gate (escape hatch `--skip-gate true` is for mechanism testing only).
28
85
 
29
86
  ## L2 display points (MUST show, never ask)
30
87
 
@@ -46,8 +103,8 @@ Rules:
46
103
 
47
104
  - Every L1 decision also appends to `ledger.scope.decisionStack`:
48
105
  `{id (e.g. D3-SP1), point, choice, at, reason}`.
49
- - REVISE/RESEARCH reasons MUST reference the stack entry being revisited
50
- (e.g. "回退到 D3-SP1:M2-area 违反 0.07% 边长约束,改 M1-cheb").
106
+ - REVISE/RESEARCH (from VERIFY) reasons MUST reference the stack entry being
107
+ revisited (e.g. "回退到 D3-SP1:M2-area 违反 0.07% 边长约束,改 M1-cheb").
51
108
  - A wrong direction returns to its fork point: FORK records the fork entry in
52
109
  the stack; each failed branch leaves a wall memo before the next branch runs.
53
110
  - Assumption revisions keep history: `revisionHistory: [{at, reason, changedTo}]`,
@@ -28,12 +28,21 @@ Ask one question at a time. Read the problem and attachments first. Ask only que
28
28
  - The 16 Problem Brief fields are filled once at intake for the whole problem;
29
29
  subproblem-level analysis and assumptions roll forward per attempt.
30
30
 
31
- ## Rules
31
+ ## Exchange depth per field
32
32
 
33
+ - **用户必须输入**:目标(1,2)、误差/精度要求(12)、验证标准(13)、最终交付物(14)——这些只能来自用户,LLM 不得替用户定。
34
+ - **LLM 可推断但必须展示依据**:子问题(3)、决策变量(6)、参数(7)、目标函数(8)、约束(9)、假设(10)、任务类型(11)——每条附依据链(题面原句/数据证据/文献/显式 LLM 判断标记)。
35
+ - **LLM 可纯推断但必须展示证据**:数据质量(5)——附画像统计;背景(1)、风险(15)、不适用字段(16)——附理由。
36
+
37
+ ## Deep-interaction rules
38
+
39
+ - Every answer the LLM infers is PRESENTED with its basis before freezing; the user may challenge any item.
40
+ - Open questions, not yes/no gates: after the LLM's analysis, ask "你有哪些补充、纠正或自己的资料?" — not merely "确认吗?".
41
+ - High-impact exchanges loop until the user says "可以了".
33
42
  - Infer before asking.
34
43
  - Ask the highest-impact unknown first.
35
44
  - Mark irrelevant fields “not applicable” with a reason.
36
- - Turn “自行判断” into an explicit modeling assumption with risk and sensitivity plan.
45
+ - Turn “自行判断” into an explicit modeling assumption with risk and sensitivity plan, listed in D2 for later challenge.
37
46
  - Assumptions are revisable: record revisionHistory and mark dependent claims stale.
38
47
  - Do not freeze scope while a missing answer can change the problem type or claim strength.
39
48
  - Do not force a proof problem to invent data or an exploratory analysis to invent an objective function.
@@ -28,11 +28,16 @@ State the frozen question, final scientific status, answer/recommendation, verif
28
28
 
29
29
  The following is shown DIRECTLY IN THE CHAT, not only on disk.
30
30
 
31
- ### Intake digest (once, at intake completion)
31
+ ### Intake digest (once, at intake completion) — the six-section deep restatement
32
32
 
33
- 【问题重述】full restatement: all sub-questions, constraints, deliverables
34
- 【问题分析】input profile, geometric/physical structure, subproblem DAG, key difficulties
35
- 【模型假设】the assumptions known at intake (numbered: statement + source + risk + sensitivity plan)
33
+ 【题目要点摘录】problem's own numbers and constraints, quoted
34
+ 【背景解读】engineering/physical mechanism
35
+ 【数据语义】every attachment column by column (meaning/units/coordinate system/magnitudes)
36
+ 【隐含信息与歧义清单】numbered; each = LLM reading + basis + risk (the user corrects entries here)
37
+ 【三问数学本质判定】fitting/optimization/geometric optics/… + why
38
+ 【待澄清问题】open questions inviting user background/corrections/references
39
+
40
+ After the D0 exchange, show the delta applied to problem-brief.md.
36
41
 
37
42
  ### Per-round digest (after EVERY attempt round)
38
43
 
@@ -8,7 +8,7 @@ Record author, title, year, venue, DOI/stable URL, version, retrieval date, exac
8
8
 
9
9
  ## Method matrix
10
10
 
11
- For each candidate method record applicability, assumptions, data needs, implementation cost, verification route, reason to try, and reason to reject.
11
+ For each candidate method record applicability, assumptions, data needs, implementation cost, verification route, reason to try, and reason to reject — plus a mandatory `literature: [sourceId]` column. Every direction must be traceable to at least one source (engineering background, methodology paper, textbook, official documentation); a literature-free direction is marked "原创方向" with its derivation basis written out. This matrix is built during the pre-modeling RESEARCH hop (D-R exchange, interaction-protocol.md), not after solving.
12
12
 
13
13
  ## Isolated campaign
14
14
 
@@ -13,6 +13,17 @@ TRIAGE → SCOPE_FROZEN → INPUT_PROFILED → CLAIMS_REGISTERED → CANDIDATES_
13
13
  VERIFY may move to REVISE, RESEARCH, FORK, an independent audit, or one terminal status:
14
14
  SOLVED, PARTIAL, CONDITIONAL, INCONCLUSIVE, REFUTED, INFEASIBLE, UNIDENTIFIABLE, BLOCKED, CANCELLED.
15
15
 
16
+ ## Pre-modeling literature research (v2.6, mandatory)
17
+
18
+ CLAIMS_REGISTERED → RESEARCH → CANDIDATES_READY. Before any candidate is
19
+ built, run the D-R exchange (interaction-protocol.md): search principles and
20
+ methods by direction, write research/sources.jsonl, attach a literature column
21
+ to the candidate matrix. Every direction must be traceable to at least one
22
+ source — engineering background, methodology paper, textbook, or official
23
+ documentation; a literature-free direction must be explicitly marked
24
+ "原创方向" with its derivation basis. Never search for solutions to the
25
+ specific contest problem.
26
+
16
27
  ## Interaction gates (v2)
17
28
 
18
29
  Before EVERY transition run `run-state.mjs gate <dir> --to <status> --reason "..."`
@@ -28,7 +28,7 @@
28
28
  "independentAuditPassed": { "type": "boolean" },
29
29
  "interactions": {
30
30
  "type": "array",
31
- "description": "L1 decision-point records: {id, decisionPoint: D1|D2|D3|D4, question, options, userAnswer, timestamp, effectOnModel} or {auto: true, decisionPoint, reason}",
31
+ "description": "L1 decision-point records: {id, decisionPoint: D0|D1|D-R|D2|D3|D4, question, options, userAnswer, timestamp, effectOnModel} or {auto: true, decisionPoint, reason}; D-R is the pre-modeling literature-research exchange",
32
32
  "items": { "type": "object" }
33
33
  },
34
34
  "decisionStack": {
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "subproblems": {
57
57
  "type": "array",
58
- "description": "Subproblem DAG nodes: {id, task, inputs, dependencies, acceptance, status, analysisLog: [{at, entry, trigger}]} — analysisLog is the rolling analysis ledger.",
58
+ "description": "Subproblem DAG nodes: {id, task, inputs, dependencies, acceptance, status, analysisLog: [{at, entry, trigger}]} — analysisLog is the rolling analysis ledger, updated after every deep-exchange beat.",
59
59
  "items": { "type": "object" }
60
60
  },
61
61
  "candidates": {
@@ -326,24 +326,34 @@ export async function createPythonEnvironment(input) {
326
326
  const finalPythonPath = environmentPython(environmentDir, platform, resolvePath)
327
327
  try {
328
328
  let adapter
329
+ let fallbackReason = null
329
330
  const installLogPath = ensureContained(runDir, resolvePath(runDir, 'python-environment-install.log'), platform)
330
331
  const installOptions = { cwd: runDir, stdoutFile: installLogPath, timeout: 20 * 60 * 1000, label: 'package install' }
331
332
  if (uv) {
332
- adapter = 'uv'
333
- const createArgs = ['venv', '--seed', environmentDir]
334
- commands.push({ command: uv.executable, args: createArgs })
335
- assertSuccess(await runCommand(uv.executable, createArgs, { cwd: runDir }), 'uv venv')
336
- if (packages.length) {
337
- const installArgs = ['pip', 'install', '--python', finalPythonPath, ...packages]
338
- commands.push({ command: uv.executable, args: installArgs, stdoutFile: installLogPath })
339
- process.stderr.write(`python-environment: uv installing ${packages.length} package(s); network-bound, may take minutes; log: ${installLogPath}\n`)
340
- assertSuccess(await runCommand(uv.executable, installArgs, installOptions), 'uv pip install')
333
+ try {
334
+ adapter = 'uv'
335
+ const createArgs = ['venv', '--seed', environmentDir]
336
+ commands.push({ command: uv.executable, args: createArgs })
337
+ assertSuccess(await runCommand(uv.executable, createArgs, { cwd: runDir }), 'uv venv')
338
+ if (packages.length) {
339
+ const installArgs = ['pip', 'install', '--python', finalPythonPath, ...packages]
340
+ commands.push({ command: uv.executable, args: installArgs, stdoutFile: installLogPath })
341
+ process.stderr.write(`python-environment: uv installing ${packages.length} package(s); network-bound, may take minutes; log: ${installLogPath}\n`)
342
+ assertSuccess(await runCommand(uv.executable, installArgs, installOptions), 'uv pip install')
343
+ }
344
+ } catch (error) {
345
+ fallbackReason = error.message
346
+ adapter = null
347
+ process.stderr.write(`python-environment: uv failed (${fallbackReason}); falling back to the user's pip (which honors the system pip index configuration)\n`)
341
348
  }
342
- } else {
349
+ }
350
+ if (!adapter && python) {
343
351
  adapter = 'venv'
344
- const createArgs = pythonVenvArgs(python, environmentDir, platform)
345
- commands.push({ command: python.executable, args: createArgs })
346
- assertSuccess(await runCommand(python.executable, createArgs, { cwd: runDir }), 'python venv')
352
+ if (!await exists(finalPythonPath, fileStat)) {
353
+ const createArgs = pythonVenvArgs(python, environmentDir, platform)
354
+ commands.push({ command: python.executable, args: createArgs })
355
+ assertSuccess(await runCommand(python.executable, createArgs, { cwd: runDir }), 'python venv')
356
+ }
347
357
  if (packages.length) {
348
358
  const installArgs = ['-m', 'pip', 'install', ...packages]
349
359
  commands.push({ command: finalPythonPath, args: installArgs, stdoutFile: installLogPath })
@@ -351,6 +361,7 @@ export async function createPythonEnvironment(input) {
351
361
  assertSuccess(await runCommand(finalPythonPath, installArgs, installOptions), 'pip install')
352
362
  }
353
363
  }
364
+ if (!adapter) throw new Error(`package installation failed${fallbackReason ? `: ${fallbackReason}` : ''}`)
354
365
  await mkdir(environmentDir, { recursive: true })
355
366
  await freezeRequirements(runCommand, finalPythonPath, finalPythonPath, runDir, stagedLockPath, lockPath, commands)
356
367
  const manifest = {
@@ -21,7 +21,7 @@ const TRANSITIONS = Object.freeze({
21
21
  TRIAGE: ['SCOPE_FROZEN', ...STOP_TRANSITIONS],
22
22
  SCOPE_FROZEN: ['INPUT_PROFILED', ...STOP_TRANSITIONS],
23
23
  INPUT_PROFILED: ['CLAIMS_REGISTERED', ...STOP_TRANSITIONS],
24
- CLAIMS_REGISTERED: ['CANDIDATES_READY', ...STOP_TRANSITIONS],
24
+ CLAIMS_REGISTERED: ['RESEARCH', 'CANDIDATES_READY', ...STOP_TRANSITIONS],
25
25
  CANDIDATES_READY: ['ATTEMPT', ...STOP_TRANSITIONS],
26
26
  ATTEMPT: ['EXECUTE', ...STOP_TRANSITIONS],
27
27
  EXECUTE: ['VERIFY', ...STOP_TRANSITIONS],
@@ -437,6 +437,9 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
437
437
  if (!hasDecisionRecord(scope, 'D0')) violations.push('D0 restatement interaction record missing (ledger.scope.interactions)')
438
438
  if (!hasDecisionRecord(scope, 'D1')) violations.push('D1 routing interaction record missing (ledger.scope.interactions)')
439
439
  }
440
+ const afterCandidates = ['CANDIDATES_READY', 'ATTEMPT', 'EXECUTE', 'VERIFY', 'REVISE', 'FORK', ...FINAL_STATES]
441
+ const candidatesGate = transitional ? (to === 'CANDIDATES_READY') : (afterCandidates.includes(run.status) && !earlyStop)
442
+ if (candidatesGate && !hasDecisionRecord(scope, 'D-R')) violations.push('D-R literature-research interaction record missing (pre-modeling literature survey is mandatory)')
440
443
  const enteringAttemptFromCandidates = transitional ? (to === 'ATTEMPT' && run.status === 'CANDIDATES_READY') : (run.currentAttempt >= 1)
441
444
  if (enteringAttemptFromCandidates) {
442
445
  if (!hasDecisionRecord(scope, 'D3')) violations.push('D3 direction-selection interaction record missing')
@@ -446,7 +449,7 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
446
449
  ? (FINAL_STATES.includes(to) && run.status === 'VERIFY')
447
450
  : (FINAL_STATES.includes(run.status) && !['BLOCKED', 'CANCELLED'].includes(run.status))
448
451
  if (enteringTerminalFromVerify && !hasDecisionRecord(scope, 'D4')) violations.push('D4 verdict interaction record missing')
449
- if (transitional && (to === 'REVISE' || to === 'RESEARCH')) {
452
+ if (transitional && (to === 'REVISE' || (to === 'RESEARCH' && run.status === 'VERIFY'))) {
450
453
  const ids = decisionStackIds(scope)
451
454
  if (ids.length > 0 && !ids.some(id => typeof reason === 'string' && reason.includes(id))) {
452
455
  violations.push(`REVISE/RESEARCH reason must reference a decisionStack entry id (available: ${ids.join(', ')})`)