dsh-math-modeling-agent 0.2.6 → 0.2.8

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,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.6
111
+ dsh plugin --profile web add github:yohanchen1/MathModelingAgent#v0.2.8
112
112
  dsh --profile web --dump-config # 检查组合层(应看到 dsh-math-modeling-agent-skills 行)
113
113
  dsh web # 重启以加载插件
114
114
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-math-modeling-agent",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Evidence-driven mathematical modeling and verification skills for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "files": [
@@ -46,7 +46,8 @@ until the user says "可以了". Mathematical modeling is a deep activity;
46
46
  4. **隐含信息与歧义清单**:numbered; each entry = the LLM's reading + its
47
47
  basis + the risk if the reading is wrong. This is the heart of the
48
48
  exchange: the user corrects entries here.
49
- 5. **三问数学本质判定**:each sub-question classified (fitting/optimization/
49
+ 5. **各子问题数学本质判定**:every sub-question of the problem — however
50
+ many there are (one or ten) — is classified (fitting/optimization/
50
51
  geometric optics/simulation/…), with why.
51
52
  6. **待澄清问题**:open questions inviting the user to add background,
52
53
  correct a reading, or supply their own references — never a yes/no list.
@@ -68,6 +69,16 @@ After the user answers, problem-brief.md is updated and the delta is shown.
68
69
  4. The D-R interaction record goes into ledger.scope.interactions; the gate
69
70
  refuses CANDIDATES_READY without it.
70
71
 
72
+ ## Honest boundary of the gate
73
+
74
+ The gate is a MECHANISM guarantee, not an anti-fraud guarantee: it proves a
75
+ record exists, never that the record is true — every interaction record is
76
+ written by the agent itself. Falsified records are therefore out of the
77
+ gate's reach; the defense against them is the blind verifier subagent
78
+ (subagent-dispatch.md), which re-derives key claims from data alone, and the
79
+ artifact checks (research/sources.jsonl, attempts/N/report.md) that tie each
80
+ record to real files on disk.
81
+
71
82
  ## Rules
72
83
 
73
84
  - D0/D1/D-R: once per task. D2/D3: once per subproblem. D2': only when a
@@ -81,7 +92,26 @@ After the user answers, problem-brief.md is updated and the delta is shown.
81
92
  ATTEMPT-from-CANDIDATES_READY without D3, terminals-from-VERIFY without D4,
82
93
  and REVISE/RESEARCH (from VERIFY) whose reason fails to reference a
83
94
  `decisionStack` entry id. The CLI `transition` command enforces the same
84
- gate (escape hatch `--skip-gate true` is for mechanism testing only).
95
+ gate and has NO escape hatch. Every D4 record carries the attempt number it
96
+ adjudicates (`attempt: N`); the terminal gate requires the D4 for the
97
+ CURRENT attempt. ATTEMPT from CANDIDATES_READY requires `--subproblem <id>`.
98
+ Terminal gates additionally require `scope.cleanupPassed: true` and the
99
+ current attempt's report.md on disk. Mechanism-level tests and unattended
100
+ batch runs may declare `scope.contractExempt: true` — production modeling
101
+ must never set it.
102
+
103
+ ## Phase pace — interactive beats between phases (v2.8)
104
+
105
+ The exposition is delivered PHASE BY PHASE, never as a firehose. The phase
106
+ sequence is: restatement → analysis → assumptions → modeling → solving →
107
+ verification → robustness → evaluation. Between every two phases there MUST
108
+ be a user interaction point: the completed phase is presented, one line asks
109
+ whether to proceed to the next phase, and the user may interrupt, ask for a
110
+ deeper explanation of any step of the just-finished phase, or contribute
111
+ knowledge. Sensitivity/robustness NEVER follows solving automatically — it
112
+ only starts after the verification phase, the user's go-ahead, and its own
113
+ D-interaction. Within a phase with ≥3 major steps, offer the user one
114
+ interruption opportunity after the first step.
85
115
 
86
116
  ## L2 display points (MUST show, never ask)
87
117
 
@@ -24,21 +24,67 @@ Do not copy full prior solutions or private reasoning.
24
24
 
25
25
  State the frozen question, final scientific status, answer/recommendation, verified claims and evidence links, conditional/unresolved claims, assumptions and sensitivity, candidate comparison, data and parameter sources, validation, failure cases, limitations, reproducibility commands, and resume conditions when non-SOLVED.
26
26
 
27
- ## Chat display — the runlog digest (MUST output, v2)
27
+ ## Chat display — the runlog digest (MUST output, v2.7)
28
28
 
29
29
  The following is shown DIRECTLY IN THE CHAT, not only on disk.
30
30
 
31
+ ### Readability standard — write for a human reader, never a bullet list
32
+
33
+ Every section is an EXPOSITION, not a summary: state WHY the step is taken,
34
+ HOW the reasoning runs (derivation, mechanism, first-principles chain), WHAT
35
+ came out (numbers with units), and HOW it is checked. A reader who knows
36
+ nothing about the problem must be able to follow each section without
37
+ guessing.
38
+
39
+ Style: step-by-step numbered exposition with plain direct language (this is
40
+ a working document, not a final paper — no paper-style formal phrasing
41
+ required), but with MORE detail than a paper would carry: every parameter
42
+ choice gets its reason, every formula is followed by a plain-language
43
+ explanation of each symbol, every step ends with a transition line
44
+ ("这一步保证了…,为下一步…做好准备"), and every conclusion states what it
45
+ would take to be wrong. Minimum expectations per section:
46
+
47
+ - 问题重述:restate the question in your own words, explaining every
48
+ parameter's meaning and where it comes from in the problem text.
49
+ - 问题分析:walk the physical/geometric mechanism step by step (e.g. why the
50
+ nodes move radially, how the focal geometry pins the paraboloid), each
51
+ step justified by problem text, data, or derivation.
52
+ - 模型假设:for EVERY assumption give the reason it is justified
53
+ (original wording / data statistic / derivation / literature) and what
54
+ happens if it fails.
55
+ - 模型建立与求解:write the derivation in full (equations, variable
56
+ definitions, units, how the equation is solved, numerical method and why
57
+ it was chosen), not just the final formula.
58
+ - 验证:explain the verification LOGIC for each obligation (what would
59
+ falsify it, what tolerance is used and why), then the outcome.
60
+ - 鲁棒性:for each perturbation say what was varied, why that parameter,
61
+ and what would count as a conclusion flip.
62
+ - 评价与改进 / 参考文献:state the comparison criterion and map each
63
+ reference to the exact method it supports.
64
+
31
65
  ### Intake digest (once, at intake completion) — the six-section deep restatement
32
66
 
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
67
+ 【题目要点摘录】the problem's own numbers and constraints, quoted, each explained
68
+ 【背景解读】engineering/physical mechanism, walked through
69
+ 【数据语义】every attachment column by column (meaning/units/coordinate system/magnitudes), with observed statistics
70
+ 【隐含信息与歧义清单】numbered; each = LLM reading + derivation/basis + risk (the user corrects entries here)
71
+ 【各子问题数学本质判定】every sub-question (any count) classified + why
38
72
  【待澄清问题】open questions inviting user background/corrections/references
39
73
 
40
74
  After the D0 exchange, show the delta applied to problem-brief.md.
41
75
 
76
+ ### Quality self-check (MUST run before every D4 verdict)
77
+
78
+ Before presenting a D4 verdict, run the self-check on the current attempt's
79
+ report and show the result in the chat: for each of the eight digest sections,
80
+ confirm whether it answers all four questions — WHY (why this step), HOW
81
+ (how the reasoning runs), WHAT (result with units), CHECK (how it is
82
+ verified, or what would falsify it). Any section failing one of the four is
83
+ flagged as incomplete and the round is NOT presented for verdict until fixed.
84
+ The self-check is a human-facing declaration: the user decides whether the
85
+ exposition is actually sufficient. This is the quality gate; the machine
86
+ gates above only guarantee existence and structure.
87
+
42
88
  ### Per-round digest (after EVERY attempt round)
43
89
 
44
90
  ```text
@@ -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, scope: { independentAuditPassed: false, interactions: [], decisionStack: [], robustnessExempt: false },
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)
@@ -439,16 +446,17 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
439
446
  }
440
447
  const afterCandidates = ['CANDIDATES_READY', 'ATTEMPT', 'EXECUTE', 'VERIFY', 'REVISE', 'FORK', ...FINAL_STATES]
441
448
  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)')
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)')
443
450
  const enteringAttemptFromCandidates = transitional ? (to === 'ATTEMPT' && run.status === 'CANDIDATES_READY') : (run.currentAttempt >= 1)
444
451
  if (enteringAttemptFromCandidates) {
452
+ if (transitional && !subproblem) violations.push('ATTEMPT from CANDIDATES_READY requires --subproblem <id>')
445
453
  if (!hasDecisionRecord(scope, 'D3')) violations.push('D3 direction-selection interaction record missing')
446
- 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>)`)
447
455
  }
448
456
  const enteringTerminalFromVerify = transitional
449
457
  ? (FINAL_STATES.includes(to) && run.status === 'VERIFY')
450
458
  : (FINAL_STATES.includes(run.status) && !['BLOCKED', 'CANCELLED'].includes(run.status))
451
- if (enteringTerminalFromVerify && !hasDecisionRecord(scope, 'D4')) violations.push('D4 verdict interaction record missing')
459
+ if (enteringTerminalFromVerify && !hasD4ForAttempt(scope, run.currentAttempt)) violations.push(`D4 verdict interaction record for attempt ${run.currentAttempt} missing`)
452
460
  if (transitional && (to === 'REVISE' || (to === 'RESEARCH' && run.status === 'VERIFY'))) {
453
461
  const ids = decisionStackIds(scope)
454
462
  if (ids.length > 0 && !ids.some(id => typeof reason === 'string' && reason.includes(id))) {
@@ -458,9 +466,12 @@ function interactionContractViolations(run, ledger, to, reason, subproblem) {
458
466
  const terminalAndExempt = transitional
459
467
  ? (FINAL_STATES.includes(to) && !['BLOCKED', 'CANCELLED'].includes(to))
460
468
  : (FINAL_STATES.includes(run.status) && !['BLOCKED', 'CANCELLED'].includes(run.status))
461
- if (terminalAndExempt && scope.robustnessExempt !== true) {
462
- const passed = Array.isArray(ledger.obligations) && ledger.obligations.some(o => o && o.kind === 'robustness' && o.status === 'PASS')
463
- if (!passed) violations.push('terminal state requires a PASSED robustness-kind obligation (or scope.robustnessExempt: true)')
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)')
464
475
  }
465
476
  return violations
466
477
  }
@@ -550,7 +561,7 @@ async function validateRunUnlocked(root, expectedTaskId) {
550
561
  if (ledger.schemaVersion !== SCHEMA_VERSION) warnings.push(`legacy run (ledger schema v${ledger.schemaVersion}): interaction contract not enforced`)
551
562
  else {
552
563
  for (const violation of interactionContractViolations(run, ledger, undefined, undefined)) {
553
- warnings.push(`interaction contract: ${violation}`)
564
+ diagnostics.push(`interaction contract: ${violation}`)
554
565
  }
555
566
  if (run.currentAttempt >= 1) {
556
567
  const reportPath = join(root, 'attempts', String(run.currentAttempt), 'report.md')
@@ -574,7 +585,7 @@ export async function validateRun(root, expectedTaskId, runtime = {}) {
574
585
  /**
575
586
  * Check the v2 interaction contract for a PROSPECTIVE transition without
576
587
  * mutating anything. The agent workflow must call this before `transition`;
577
- * the CLI `transition` command enforces it internally unless --skip-gate.
588
+ * the CLI `transition` command enforces it internally and has NO escape hatch.
578
589
  */
579
590
  export async function gateTransition(root, { to, reason, subproblem }, runtime = {}) {
580
591
  return withLock(root, MUTATION_LOCK_FILE, 'gate', async () => {
@@ -582,6 +593,23 @@ export async function gateTransition(root, { to, reason, subproblem }, runtime =
582
593
  const run = await readJson(p.run)
583
594
  const ledger = await readJson(p.ledger)
584
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
+ }
585
613
  return { allowed: violations.length === 0, violations, status: run.status }
586
614
  }, runtime)
587
615
  }
@@ -632,12 +660,10 @@ async function cli(argv) {
632
660
  return result
633
661
  }
634
662
  if (command === 'transition') {
635
- if (options['skip-gate'] !== 'true') {
636
- const gate = await gateTransition(root, { to: options.to, reason: options.reason, subproblem: options.subproblem })
637
- if (!gate.allowed) {
638
- process.exitCode = 1
639
- return { error: `interaction contract violated: ${gate.violations.join('; ')}`, gate }
640
- }
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 }
641
667
  }
642
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 })
643
669
  }