dsh-math-modeling-agent 0.2.5 → 0.2.7
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 +6 -5
- package/package.json +1 -1
- package/skills/math-modeling-agent/SKILL.md +9 -8
- package/skills/math-modeling-agent/references/claims-evidence.md +2 -0
- package/skills/math-modeling-agent/references/interaction-protocol.md +85 -12
- package/skills/math-modeling-agent/references/math-grill.md +11 -2
- package/skills/math-modeling-agent/references/report-contract.md +9 -4
- package/skills/math-modeling-agent/references/research-breakthrough.md +1 -1
- package/skills/math-modeling-agent/references/workflow.md +11 -0
- package/skills/math-modeling-agent/schemas/ledger.schema.json +2 -2
- package/skills/math-modeling-agent/scripts/python-environment.mjs +24 -13
- package/skills/math-modeling-agent/scripts/run-state.mjs +48 -19
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ D3 方向/D4 裁决),`run-state.mjs gate` 在每次状态转移前强制校
|
|
|
108
108
|
安装:
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.
|
|
111
|
+
dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.7
|
|
112
112
|
dsh --profile web --dump-config # 检查组合层(应看到 dsh-math-modeling-agent-skills 行)
|
|
113
113
|
dsh web # 重启以加载插件
|
|
114
114
|
```
|
|
@@ -118,10 +118,11 @@ dsh web # 重启以加载插件
|
|
|
118
118
|
不要用 `npm install` 代替,那会装到错误位置。如本机镜像源同步滞后,可显式走官方源:
|
|
119
119
|
`dsh plugin --profile web add dsh-math-modeling-agent --registry=https://registry.npmjs.org/`)
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
开始建模——直接描述任务即可。每个环节都是人与 LLM 的深度信息交换:LLM 给出带依据链的
|
|
122
|
+
完整分析(题面原句/数据证据/文献/显式判断标记),你纠正、补充背景或提供自己的参考文献,
|
|
123
|
+
LLM 更新并展示差异。建模前必须先做文献调研(AI 检索原理与方法文献,你可增删),每个候选
|
|
124
|
+
方向都有文献出处;每轮在对话直接输出 runlog 式摘要(问题重述/分析/假设/建模求解/验证/鲁棒性/
|
|
125
|
+
评价改进/参考文献),失败尝试按类归档,终态前强制鲁棒性敏感度分析:
|
|
125
126
|
|
|
126
127
|
```text
|
|
127
128
|
建立这个数学建模问题的模型,先分析题目和数据。
|
package/package.json
CHANGED
|
@@ -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
|
|
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.
|
|
30
|
-
7.
|
|
31
|
-
8.
|
|
32
|
-
9.
|
|
33
|
-
10.
|
|
34
|
-
11.
|
|
35
|
-
12.
|
|
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,101 @@
|
|
|
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 |
|
|
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
|
-
|
|
|
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
|
|
15
|
-
| D4 结果裁决 | VERIFY done | verdict
|
|
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
|
+
## Honest boundary of the gate
|
|
72
|
+
|
|
73
|
+
The gate is a MECHANISM guarantee, not an anti-fraud guarantee: it proves a
|
|
74
|
+
record exists, never that the record is true — every interaction record is
|
|
75
|
+
written by the agent itself. Falsified records are therefore out of the
|
|
76
|
+
gate's reach; the defense against them is the blind verifier subagent
|
|
77
|
+
(subagent-dispatch.md), which re-derives key claims from data alone, and the
|
|
78
|
+
artifact checks (research/sources.jsonl, attempts/N/report.md) that tie each
|
|
79
|
+
record to real files on disk.
|
|
80
|
+
|
|
81
|
+
## Rules
|
|
16
82
|
|
|
17
|
-
|
|
18
|
-
|
|
83
|
+
- D0/D1/D-R: once per task. D2/D3: once per subproblem. D2': only when a
|
|
84
|
+
discovery changes model structure. D4: once per attempt round.
|
|
19
85
|
- Every L1 record is appended to `ledger.scope.interactions` as
|
|
20
86
|
`{id, decisionPoint, question, options, userAnswer, timestamp, effectOnModel}`
|
|
21
87
|
or, for explicit auto authorization,
|
|
22
88
|
`{auto: true, decisionPoint, reason}`.
|
|
23
89
|
- Run the gate before every transition: `run-state.mjs gate <dir> --to <status> --reason "<...>"`.
|
|
24
|
-
The gate refuses SCOPE_FROZEN without D1,
|
|
25
|
-
terminals-from-VERIFY without D4,
|
|
26
|
-
|
|
27
|
-
|
|
90
|
+
The gate refuses SCOPE_FROZEN without D0+D1, CANDIDATES_READY without D-R,
|
|
91
|
+
ATTEMPT-from-CANDIDATES_READY without D3, terminals-from-VERIFY without D4,
|
|
92
|
+
and REVISE/RESEARCH (from VERIFY) whose reason fails to reference a
|
|
93
|
+
`decisionStack` entry id. The CLI `transition` command enforces the same
|
|
94
|
+
gate and has NO escape hatch. Every D4 record carries the attempt number it
|
|
95
|
+
adjudicates (`attempt: N`); the terminal gate requires the D4 for the
|
|
96
|
+
CURRENT attempt. ATTEMPT from CANDIDATES_READY requires `--subproblem <id>`.
|
|
97
|
+
Terminal gates additionally require `scope.cleanupPassed: true` and the
|
|
98
|
+
current attempt's report.md on disk. Mechanism-level tests and unattended
|
|
99
|
+
batch runs may declare `scope.contractExempt: true` — production modeling
|
|
100
|
+
must never set it.
|
|
28
101
|
|
|
29
102
|
## L2 display points (MUST show, never ask)
|
|
30
103
|
|
|
@@ -46,8 +119,8 @@ Rules:
|
|
|
46
119
|
|
|
47
120
|
- Every L1 decision also appends to `ledger.scope.decisionStack`:
|
|
48
121
|
`{id (e.g. D3-SP1), point, choice, at, reason}`.
|
|
49
|
-
- REVISE/RESEARCH reasons MUST reference the stack entry being
|
|
50
|
-
(e.g. "回退到 D3-SP1:M2-area 违反 0.07% 边长约束,改 M1-cheb").
|
|
122
|
+
- REVISE/RESEARCH (from VERIFY) reasons MUST reference the stack entry being
|
|
123
|
+
revisited (e.g. "回退到 D3-SP1:M2-area 违反 0.07% 边长约束,改 M1-cheb").
|
|
51
124
|
- A wrong direction returns to its fork point: FORK records the fork entry in
|
|
52
125
|
the stack; each failed branch leaves a wall memo before the next branch runs.
|
|
53
126
|
- 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
|
-
##
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
}
|
|
349
|
+
}
|
|
350
|
+
if (!adapter && python) {
|
|
343
351
|
adapter = 'venv'
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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],
|
|
@@ -366,7 +366,7 @@ function validateJournal(events, diagnostics) {
|
|
|
366
366
|
})
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
export async function initRun(root, { taskId, mode = 'standard', budget, budgets }, runtime = {}) {
|
|
369
|
+
export async function initRun(root, { taskId, mode = 'standard', budget, budgets, contract }, runtime = {}) {
|
|
370
370
|
assertTaskId(taskId); assertMode(mode)
|
|
371
371
|
const effectiveBudget = { ...MODE_DEFAULTS[mode], ...(budget ?? budgets ?? {}) }
|
|
372
372
|
const budgetDiagnostics = []
|
|
@@ -379,7 +379,8 @@ export async function initRun(root, { taskId, mode = 'standard', budget, budgets
|
|
|
379
379
|
bestCandidateId: null, budget: effectiveBudget, createdAt: timestamp, updatedAt: timestamp,
|
|
380
380
|
}
|
|
381
381
|
const ledger = {
|
|
382
|
-
schemaVersion: SCHEMA_VERSION, taskId,
|
|
382
|
+
schemaVersion: SCHEMA_VERSION, taskId,
|
|
383
|
+
scope: { independentAuditPassed: false, interactions: [], decisionStack: [], robustnessExempt: false, ...(contract === false ? { contractExempt: true } : {}) },
|
|
383
384
|
assumptions: [], claims: [], obligations: [], subproblems: [], candidates: [], issues: [],
|
|
384
385
|
}
|
|
385
386
|
const event = { schemaVersion: SCHEMA_VERSION, sequence: 0, type: 'RUN_INITIALIZED', taskId, timestamp, snapshot: clone(run) }
|
|
@@ -412,9 +413,14 @@ export async function initRun(root, { taskId, mode = 'standard', budget, budgets
|
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
/** The v2 interaction contract: decision records, decision-stack backtracking, and the robustness gate. */
|
|
415
|
-
function hasDecisionRecord(scope, decisionPoint) {
|
|
416
|
+
function hasDecisionRecord(scope, decisionPoint, { allowAuto = true } = {}) {
|
|
416
417
|
return Array.isArray(scope?.interactions) && scope.interactions.some(entry =>
|
|
417
|
-
entry && (entry.decisionPoint === decisionPoint || entry.auto === true))
|
|
418
|
+
entry && (entry.decisionPoint === decisionPoint || (allowAuto && entry.auto === true)))
|
|
419
|
+
}
|
|
420
|
+
/** D4 verdict records carry the attempt number they adjudicate: {decisionPoint:'D4', attempt: 2, ...} */
|
|
421
|
+
function hasD4ForAttempt(scope, attempt) {
|
|
422
|
+
return Array.isArray(scope?.interactions) && scope.interactions.some(entry =>
|
|
423
|
+
entry && (entry.auto === true || (entry.decisionPoint === 'D4' && entry.attempt === attempt)))
|
|
418
424
|
}
|
|
419
425
|
function decisionStackIds(scope) {
|
|
420
426
|
return Array.isArray(scope?.decisionStack)
|
|
@@ -430,6 +436,7 @@ function hasSubproblemD2(scope, subproblem) {
|
|
|
430
436
|
function interactionContractViolations(run, ledger, to, reason, subproblem) {
|
|
431
437
|
if (ledger.schemaVersion !== SCHEMA_VERSION) return []
|
|
432
438
|
const scope = ledger.scope ?? {}
|
|
439
|
+
if (scope.contractExempt === true) return []
|
|
433
440
|
const violations = []
|
|
434
441
|
const transitional = to !== undefined
|
|
435
442
|
const earlyStop = ['BLOCKED', 'CANCELLED'].includes(run.status)
|
|
@@ -437,16 +444,20 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
|
|
|
437
444
|
if (!hasDecisionRecord(scope, 'D0')) violations.push('D0 restatement interaction record missing (ledger.scope.interactions)')
|
|
438
445
|
if (!hasDecisionRecord(scope, 'D1')) violations.push('D1 routing interaction record missing (ledger.scope.interactions)')
|
|
439
446
|
}
|
|
447
|
+
const afterCandidates = ['CANDIDATES_READY', 'ATTEMPT', 'EXECUTE', 'VERIFY', 'REVISE', 'FORK', ...FINAL_STATES]
|
|
448
|
+
const candidatesGate = transitional ? (to === 'CANDIDATES_READY') : (afterCandidates.includes(run.status) && !earlyStop)
|
|
449
|
+
if (candidatesGate && !hasDecisionRecord(scope, 'D-R', { allowAuto: false })) violations.push('D-R literature-research interaction record missing (pre-modeling literature survey is mandatory; auto-authorization is not accepted)')
|
|
440
450
|
const enteringAttemptFromCandidates = transitional ? (to === 'ATTEMPT' && run.status === 'CANDIDATES_READY') : (run.currentAttempt >= 1)
|
|
441
451
|
if (enteringAttemptFromCandidates) {
|
|
452
|
+
if (transitional && !subproblem) violations.push('ATTEMPT from CANDIDATES_READY requires --subproblem <id>')
|
|
442
453
|
if (!hasDecisionRecord(scope, 'D3')) violations.push('D3 direction-selection interaction record missing')
|
|
443
|
-
if (transitional && !hasSubproblemD2(scope, subproblem)) violations.push(`D2 assumption interaction record missing for subproblem ${subproblem} (run the gate with --subproblem <id>)`)
|
|
454
|
+
if (transitional && subproblem && !hasSubproblemD2(scope, subproblem)) violations.push(`D2 assumption interaction record missing for subproblem ${subproblem} (run the gate with --subproblem <id>)`)
|
|
444
455
|
}
|
|
445
456
|
const enteringTerminalFromVerify = transitional
|
|
446
457
|
? (FINAL_STATES.includes(to) && run.status === 'VERIFY')
|
|
447
458
|
: (FINAL_STATES.includes(run.status) && !['BLOCKED', 'CANCELLED'].includes(run.status))
|
|
448
|
-
if (enteringTerminalFromVerify && !
|
|
449
|
-
if (transitional && (to === 'REVISE' || to === 'RESEARCH')) {
|
|
459
|
+
if (enteringTerminalFromVerify && !hasD4ForAttempt(scope, run.currentAttempt)) violations.push(`D4 verdict interaction record for attempt ${run.currentAttempt} missing`)
|
|
460
|
+
if (transitional && (to === 'REVISE' || (to === 'RESEARCH' && run.status === 'VERIFY'))) {
|
|
450
461
|
const ids = decisionStackIds(scope)
|
|
451
462
|
if (ids.length > 0 && !ids.some(id => typeof reason === 'string' && reason.includes(id))) {
|
|
452
463
|
violations.push(`REVISE/RESEARCH reason must reference a decisionStack entry id (available: ${ids.join(', ')})`)
|
|
@@ -455,9 +466,12 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
|
|
|
455
466
|
const terminalAndExempt = transitional
|
|
456
467
|
? (FINAL_STATES.includes(to) && !['BLOCKED', 'CANCELLED'].includes(to))
|
|
457
468
|
: (FINAL_STATES.includes(run.status) && !['BLOCKED', 'CANCELLED'].includes(run.status))
|
|
458
|
-
if (terminalAndExempt
|
|
459
|
-
|
|
460
|
-
|
|
469
|
+
if (terminalAndExempt) {
|
|
470
|
+
if (scope.robustnessExempt !== true) {
|
|
471
|
+
const passed = Array.isArray(ledger.obligations) && ledger.obligations.some(o => o && o.kind === 'robustness' && o.status === 'PASS')
|
|
472
|
+
if (!passed) violations.push('terminal state requires a PASSED robustness-kind obligation (or scope.robustnessExempt: true)')
|
|
473
|
+
}
|
|
474
|
+
if (scope.cleanupPassed !== true) violations.push('terminal state requires scope.cleanupPassed: true (run the cleanup checklist in run-directory.md)')
|
|
461
475
|
}
|
|
462
476
|
return violations
|
|
463
477
|
}
|
|
@@ -547,7 +561,7 @@ async function validateRunUnlocked(root, expectedTaskId) {
|
|
|
547
561
|
if (ledger.schemaVersion !== SCHEMA_VERSION) warnings.push(`legacy run (ledger schema v${ledger.schemaVersion}): interaction contract not enforced`)
|
|
548
562
|
else {
|
|
549
563
|
for (const violation of interactionContractViolations(run, ledger, undefined, undefined)) {
|
|
550
|
-
|
|
564
|
+
diagnostics.push(`interaction contract: ${violation}`)
|
|
551
565
|
}
|
|
552
566
|
if (run.currentAttempt >= 1) {
|
|
553
567
|
const reportPath = join(root, 'attempts', String(run.currentAttempt), 'report.md')
|
|
@@ -571,7 +585,7 @@ export async function validateRun(root, expectedTaskId, runtime = {}) {
|
|
|
571
585
|
/**
|
|
572
586
|
* Check the v2 interaction contract for a PROSPECTIVE transition without
|
|
573
587
|
* mutating anything. The agent workflow must call this before `transition`;
|
|
574
|
-
* the CLI `transition` command enforces it internally
|
|
588
|
+
* the CLI `transition` command enforces it internally and has NO escape hatch.
|
|
575
589
|
*/
|
|
576
590
|
export async function gateTransition(root, { to, reason, subproblem }, runtime = {}) {
|
|
577
591
|
return withLock(root, MUTATION_LOCK_FILE, 'gate', async () => {
|
|
@@ -579,6 +593,23 @@ export async function gateTransition(root, { to, reason, subproblem }, runtime =
|
|
|
579
593
|
const run = await readJson(p.run)
|
|
580
594
|
const ledger = await readJson(p.ledger)
|
|
581
595
|
const violations = interactionContractViolations(run, ledger, to, reason, subproblem)
|
|
596
|
+
const exempt = ledger.scope?.contractExempt === true
|
|
597
|
+
if (to === 'CANDIDATES_READY' && !exempt) {
|
|
598
|
+
const sourcesPath = join(root, 'research', 'sources.jsonl')
|
|
599
|
+
try {
|
|
600
|
+
if (!(await readFile(sourcesPath, 'utf8')).trim()) violations.push('research/sources.jsonl is empty (pre-modeling literature survey artifacts are mandatory)')
|
|
601
|
+
} catch {
|
|
602
|
+
violations.push('research/sources.jsonl missing (pre-modeling literature survey artifacts are mandatory)')
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (FINAL_STATES.includes(to) && !['BLOCKED', 'CANCELLED'].includes(to) && !exempt) {
|
|
606
|
+
const reportPath = join(root, 'attempts', String(run.currentAttempt), 'report.md')
|
|
607
|
+
try {
|
|
608
|
+
await readFile(reportPath, 'utf8')
|
|
609
|
+
} catch {
|
|
610
|
+
violations.push(`attempts/${run.currentAttempt}/report.md missing (write the runlog report before a terminal transition)`)
|
|
611
|
+
}
|
|
612
|
+
}
|
|
582
613
|
return { allowed: violations.length === 0, violations, status: run.status }
|
|
583
614
|
}, runtime)
|
|
584
615
|
}
|
|
@@ -629,12 +660,10 @@ async function cli(argv) {
|
|
|
629
660
|
return result
|
|
630
661
|
}
|
|
631
662
|
if (command === 'transition') {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
return { error: `interaction contract violated: ${gate.violations.join('; ')}`, gate }
|
|
637
|
-
}
|
|
663
|
+
const gate = await gateTransition(root, { to: options.to, reason: options.reason, subproblem: options.subproblem })
|
|
664
|
+
if (!gate.allowed) {
|
|
665
|
+
process.exitCode = 1
|
|
666
|
+
return { error: `interaction contract violated: ${gate.violations.join('; ')}`, gate }
|
|
638
667
|
}
|
|
639
668
|
return transitionRun(root, { to: options.to, reason: options.reason, evidenceIds: options.evidence ? options.evidence.split(',').filter(Boolean) : [], issueIds: options.issues ? options.issues.split(',').filter(Boolean) : [], patch: options.candidate ? { bestCandidateId: options.candidate } : undefined })
|
|
640
669
|
}
|