dsh-math-modeling-agent 0.2.0 → 0.2.1

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
@@ -81,14 +81,20 @@ NOT_CHECKED → DERIVED → EXECUTED → VERIFIED → INDEPENDENTLY_VERIFIED →
81
81
  默认运行目录 `math-modeling-runs/<task-id>/`:
82
82
 
83
83
  ```text
84
- run.json # 原子状态快照
85
- ledger.json # 范围、假设、主张、义务、子问题、候选、issue
86
- events.jsonl # 追加式转移日志
87
- attempts/<n>/ # 每轮报告与产物
88
- research/ walls/ # 文献检索 / 放弃方向的突破备忘录
84
+ run.json ledger.json events.jsonl # 原子状态 + 契约(interactions/decisionStack)+ 日志
85
+ problem-brief.md inputs.json # 冻结的问题重述 + 输入清单
86
+ attempts/<n>/ # 每轮:report.md + code/ + data/ + plots/ + _drafts/
87
+ failed/directions/<id>/ # 方向级失败:wall memo + 代码 + 放弃理由
88
+ failed/code-drafts/<attempt>-<name>/ # 实现级失败:bug 版/超时版/弃用版(不删除)
89
+ failed/issues.md # 失败账本(DATA/TOOL/IMPLEMENTATION/MODEL/VALIDATION/EVIDENCE/RESEARCH_GAP)
90
+ research/ walls/ # 文献检索 / 突破备忘录
89
91
  reproducibility.json final-report.md
90
92
  ```
91
93
 
94
+ v0.2.0 起:每个会改变模型结构的决策在对话中交互确认并写入 ledger(D0 重述/D1 路由/
95
+ D2 子问题假设/D3 方向/D4 裁决),`run-state.mjs gate` 在每次状态转移前强制校验;
96
+ 终态前强制鲁棒性敏感度分析;失败尝试按类归档到独立目录。
97
+
92
98
  崩溃恢复:跨进程互斥锁 + stale 锁与 reclaim guard 回收 + Windows 共享冲突重试;已完成且输入未变的工作不重跑;任何失败都保留最佳候选与原始产物。
93
99
 
94
100
  ## MCM / ICM 终审(audit Skill)
@@ -100,7 +106,7 @@ reproducibility.json final-report.md
100
106
  安装:
101
107
 
102
108
  ```bash
103
- dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.0
109
+ dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.1
104
110
  dsh --profile web --dump-config # 检查组合层
105
111
  dsh web # 然后重启
106
112
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-math-modeling-agent",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Evidence-driven mathematical modeling and verification skills for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,4 +1,4 @@
1
- # Minimal Run Example
1
+ # Minimal Run Example (v2)
2
2
 
3
3
  Problem: solve 2x + 3 = 11.
4
4
 
@@ -11,4 +11,10 @@ Evidence E-002: substitution 2(4) + 3 = 11.
11
11
 
12
12
  Status: SOLVED.
13
13
 
14
+ v2 interaction records (ledger.scope.interactions): D0 restatement (auto), D1 routing
15
+ (algebra / Fast), D2 assumptions (none beyond the equation), D3 direction
16
+ (direct inverse), D4 verdict (user accepted). scope.robustnessExempt = true —
17
+ a pure algebraic equation has no perturbable parameters, so the robustness
18
+ gate is exempted with that reason. decisionStack holds the D3 entry.
19
+
14
20
  No Python, Lean, Wolfram, research directory, or empty artifact directory is required.
@@ -7,7 +7,9 @@ Resume sequence:
7
7
  1. run `node scripts/run-state.mjs validate <run-root>`;
8
8
  2. run `node scripts/run-state.mjs recover <run-root>`;
9
9
  3. confirm C-003 remains the best candidate;
10
- 4. rerun only the incomplete verifier;
11
- 5. append a new evidence-linked transition.
10
+ 4. run `node scripts/run-state.mjs gate <run-root> --to REVISE --reason "回退到 D3-SP1:..."` (the reason must reference the decisionStack entry);
11
+ 5. rerun only the incomplete verifier;
12
+ 6. append a new evidence-linked transition (the CLI enforces the gate).
12
13
 
13
- Previously hash-stable Python outputs are not recomputed.
14
+ Previously hash-stable Python outputs are not recomputed. Interaction records,
15
+ the decision stack, and the failure ledger survive the crash unchanged.
@@ -421,17 +421,27 @@ function decisionStackIds(scope) {
421
421
  ? scope.decisionStack.filter(entry => entry && typeof entry.id === 'string').map(entry => entry.id)
422
422
  : []
423
423
  }
424
- function interactionContractViolations(run, ledger, to, reason) {
424
+ /** D2 records carry the subproblem id(s) they cover: {decisionPoint:'D2', scope:['SP1'], ...} */
425
+ function hasSubproblemD2(scope, subproblem) {
426
+ if (!subproblem || !Array.isArray(scope?.interactions)) return true
427
+ return scope.interactions.some(entry => entry && entry.decisionPoint === 'D2' &&
428
+ (Array.isArray(entry.scope) ? entry.scope.includes(subproblem) : entry.subproblem === subproblem))
429
+ }
430
+ function interactionContractViolations(run, ledger, to, reason, subproblem) {
425
431
  if (ledger.schemaVersion !== SCHEMA_VERSION) return []
426
432
  const scope = ledger.scope ?? {}
427
433
  const violations = []
428
434
  const transitional = to !== undefined
429
435
  const earlyStop = ['BLOCKED', 'CANCELLED'].includes(run.status)
430
436
  if ((transitional && to === 'SCOPE_FROZEN') || (!transitional && run.status !== 'TRIAGE' && !earlyStop)) {
437
+ if (!hasDecisionRecord(scope, 'D0')) violations.push('D0 restatement interaction record missing (ledger.scope.interactions)')
431
438
  if (!hasDecisionRecord(scope, 'D1')) violations.push('D1 routing interaction record missing (ledger.scope.interactions)')
432
439
  }
433
440
  const enteringAttemptFromCandidates = transitional ? (to === 'ATTEMPT' && run.status === 'CANDIDATES_READY') : (run.currentAttempt >= 1)
434
- if (enteringAttemptFromCandidates && !hasDecisionRecord(scope, 'D3')) violations.push('D3 direction-selection interaction record missing')
441
+ if (enteringAttemptFromCandidates) {
442
+ 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>)`)
444
+ }
435
445
  const enteringTerminalFromVerify = transitional
436
446
  ? (FINAL_STATES.includes(to) && run.status === 'VERIFY')
437
447
  : (FINAL_STATES.includes(run.status) && !['BLOCKED', 'CANCELLED'].includes(run.status))
@@ -539,6 +549,17 @@ async function validateRunUnlocked(root, expectedTaskId) {
539
549
  for (const violation of interactionContractViolations(run, ledger, undefined, undefined)) {
540
550
  warnings.push(`interaction contract: ${violation}`)
541
551
  }
552
+ if (run.currentAttempt >= 1) {
553
+ const reportPath = join(root, 'attempts', String(run.currentAttempt), 'report.md')
554
+ try {
555
+ const reportText = await readFile(reportPath, 'utf8')
556
+ for (const section of ['问题重述', '问题分析', '模型假设', '模型建立与求解', '验证', '鲁棒性', '评价与改进', '参考文献']) {
557
+ if (!reportText.includes(section)) warnings.push(`attempt ${run.currentAttempt} report.md missing runlog digest section: ${section}`)
558
+ }
559
+ } catch {
560
+ warnings.push(`attempt ${run.currentAttempt} report.md unreadable (runlog digest required)`)
561
+ }
562
+ }
542
563
  }
543
564
  return { valid: diagnostics.length === 0, diagnostics, warnings, run: clone(run) }
544
565
  }
@@ -552,12 +573,12 @@ export async function validateRun(root, expectedTaskId, runtime = {}) {
552
573
  * mutating anything. The agent workflow must call this before `transition`;
553
574
  * the CLI `transition` command enforces it internally unless --skip-gate.
554
575
  */
555
- export async function gateTransition(root, { to, reason }, runtime = {}) {
576
+ export async function gateTransition(root, { to, reason, subproblem }, runtime = {}) {
556
577
  return withLock(root, MUTATION_LOCK_FILE, 'gate', async () => {
557
578
  const p = paths(root)
558
579
  const run = await readJson(p.run)
559
580
  const ledger = await readJson(p.ledger)
560
- const violations = interactionContractViolations(run, ledger, to, reason)
581
+ const violations = interactionContractViolations(run, ledger, to, reason, subproblem)
561
582
  return { allowed: violations.length === 0, violations, status: run.status }
562
583
  }, runtime)
563
584
  }
@@ -603,13 +624,13 @@ async function cli(argv) {
603
624
  const options = parseOptions(rest)
604
625
  if (command === 'init') return initRun(root, { taskId: options['task-id'], mode: options.mode ?? 'standard' })
605
626
  if (command === 'gate') {
606
- const result = await gateTransition(root, { to: options.to, reason: options.reason })
627
+ const result = await gateTransition(root, { to: options.to, reason: options.reason, subproblem: options.subproblem })
607
628
  if (!result.allowed) process.exitCode = 1
608
629
  return result
609
630
  }
610
631
  if (command === 'transition') {
611
632
  if (options['skip-gate'] !== 'true') {
612
- const gate = await gateTransition(root, { to: options.to, reason: options.reason })
633
+ const gate = await gateTransition(root, { to: options.to, reason: options.reason, subproblem: options.subproblem })
613
634
  if (!gate.allowed) {
614
635
  process.exitCode = 1
615
636
  return { error: `interaction contract violated: ${gate.violations.join('; ')}`, gate }