dsh-vibe-math 1.3.3 → 1.3.5

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
@@ -51,6 +51,10 @@
51
51
  > 🔧 **v1.3.2 审计修复**:修复上下文压缩按占比失效(`contextPct` 单位错配,现按百分比保存)、常驻工具按"调用者身份"路由(各常驻库归属正确)、`vibe_v4_read_progress` 真正返回文本、跨进程断点续跑重建常驻、`message(all)` 广播真正投递、`addMember` 增开无 id 碰撞。详见 `vibe-math-v4/实现方案.md` §17。
52
52
  >
53
53
  > 🔧 **v1.3.3 审计修复**:非全票验证把平均概率写回源卡(兑现"留库附概率")、方法型验证标 `类型: 方法`(不再误标命题)、同进程 abort→resume 重建常驻、记录命题也触发自动同步会议、唤醒信号改用 `activityTimeoutMs`、`verdictMaxRounds`/`meetingKeepEvery` 可调且展示。详见 §18。
54
+ >
55
+ > 🔧 **v1.3.4 和谐修复**:重排 `框架图-v4.png` 消除文字/箭头重叠遮挡(子标签不再溢出框、`/compact` 移入独立的"上下文"能力块、底部"产物沉淀·断点续跑"居中排布);补齐 `agent.cordis.yml` 常驻描述中的"deadlines"一词(v4 并无截止机制),并移除 `实现方案.md` 中不存在的 `vibe_v4_inject`(实际用 `vibe_v4_message`)。
56
+ >
57
+ > 🔧 **v1.3.5 边界 A 落地 + 真实 `/compact`**:`maxParallel` 真正限流(在途上限),`activityTimeoutMs` 作为**心跳门控**(空闲超时才触发 CHECKPOINT 唤醒,推动收敛/停止而非无限烧 token);并用 DSH 真实 `ctx.compaction.compactIfNeeded(常驻 agent, 'pressure', signal)` 压缩常驻自身上下文(回退到自述指令)。详见 §19。
54
58
 
55
59
  ---
56
60
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-vibe-math",
3
3
  "description": "Multi-agent mathematical problem-solving & verification frameworks for DeepSeek Harness — FOUR agent presets in one install: vibe-math-v1 (classic pipeline), vibe-math-v2 (probability-driven: qs.json + Propos knowledge base + explorer→solver→review/debate verdict), vibe-math-v3 (THIRD-generation, recommended: paper-style Markdown knowledge base with Problems/Progress/Propos/Methods/Verified + planner-agent scheduling that decides the next N actions + universal theory/method invention library + agents write their own Markdown directly via a per-file write lock), and vibe-math-v4 (FOURTH-generation: persistent self-organizing resident subagents that message & meet to decide all tasks, verify only by unanimous consensus, /compact at a context threshold, and stop only when all agree the problem is solved). Installing this bundle auto-installs all four presets into the DSH preset root.",
4
- "version": "1.3.3",
4
+ "version": "1.3.5",
5
5
  "type": "module",
6
6
  "main": "installer.js",
7
7
  "exports": {
@@ -32,7 +32,7 @@
32
32
  Data lives under {{cwd}}/VibeMath/Projects/<project>/:
33
33
  Problems/ (original problem card), Progress/<resident>/ (each resident's progress),
34
34
  Propos/<resident>/ (each resident's propositions), Methods/<resident>/ (theories/tools),
35
- Subproblems/<resident>/, Shared/ (meeting transcripts / task board / deadlines / debates),
35
+ Subproblems/<resident>/, Shared/ (meeting transcripts / task board / debates),
36
36
  Verified/ (read-only, ONLY after unanimous consensus), State/ (framework-private),
37
37
  Reliable/ (references), Notes/.
38
38
 
@@ -11,6 +11,7 @@ export function apply(ctx) {
11
11
  const commands = ctx.commands
12
12
  const subprocess = ctx.get('subprocess')
13
13
  const sandboxPolicy = ctx.get('sandboxPolicy')
14
+ const compaction = ctx.get('compaction') // @deepseek-ai/dsh-compaction (CompactionEngine); optional
14
15
 
15
16
  const sessions = new Map() // rootAgentId -> Session
16
17
  const childOwner = new Map() // childId -> rootAgentId
@@ -36,7 +37,7 @@ export function apply(ctx) {
36
37
  let problemText = '', problemId = 'problem', runId = 'run-' + shortId()
37
38
  let meetingState = null, verifyState = null, pendingVerify = null
38
39
  let busy = new Set(), wakeKind = new Map(), currentResident = ''
39
- let lastActivityAt = now(), artifactCount = 0, lastSyncMeetingAt = 0, persistedEpoch = ''
40
+ let lastActivityAt = now(), artifactCount = 0, lastSyncMeetingAt = 0, persistedEpoch = '', heartbeatTimer = null
40
41
  const activityLogCap = 200
41
42
 
42
43
  // ---- utils ----
@@ -156,6 +157,7 @@ export function apply(ctx) {
156
157
  }
157
158
  async function wakeResident(r, promptText, kind){
158
159
  if(!r.childId) return false
160
+ clearHeartbeat()
159
161
  busy.add(r.rId); wakeKind.set(r.rId,kind||'normal'); currentResident=r.rId
160
162
  r.lastActiveAt=now(); r.rounds+=1; r.roundsSinceCompact+=1
161
163
  // context / /compact: if the resident reports high context (or reached the round proxy),
@@ -218,6 +220,7 @@ export function apply(ctx) {
218
220
  // ---- meeting ----
219
221
  async function startMeeting(agenda,type,targetId){
220
222
  if(meetingState) return {ok:false,message:'meeting already in progress'}
223
+ clearHeartbeat()
221
224
  meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[]}
222
225
  logActivity('meeting','start: '+agenda); await saveAll(); await scheduleNext(); return {ok:true,id:meetingState.id}
223
226
  }
@@ -252,6 +255,7 @@ export function apply(ctx) {
252
255
 
253
256
  // ---- verification (unanimous) ----
254
257
  async function beginVerify(pv){
258
+ clearHeartbeat()
255
259
  pendingVerify=null
256
260
  verifyState={targetId:pv.targetId,targetType:pv.targetType,stage:'independent',round:0,asked:[],verdicts:{},transcript:[]}
257
261
  logActivity('verify','debate begin: '+pv.targetId+' ('+pv.targetType+')'); await saveAll(); await scheduleNext()
@@ -323,19 +327,68 @@ export function apply(ctx) {
323
327
  }
324
328
  function guessTargetType(id){ if(/^p-/.test(id)) return 'proposition'; if(/^m-/.test(id)) return 'method'; if(/^s-/.test(id)) return 'subproblem'; return 'proposition' }
325
329
 
330
+ // ---- heartbeat / liveness helpers (boundary-A: event-driven + gated heartbeat) ----
331
+ // A checkpoint wake is NOT "keep working forever": it nudges the least-recently-active
332
+ // resident, after an idle timeout, to either make concrete progress or push the group
333
+ // toward a decision (meeting / verify / solved). This adds convergence pressure instead
334
+ // of infinite token-burning, matching the "framework never assigns work" philosophy.
335
+ function heartbeatPrompt(r){
336
+ return (params.residentPersona?params.residentPersona+'\n':'')
337
+ +'You are resident researcher '+r.rId+'. CHECKPOINT (idle): the group is waiting for direction.\n'
338
+ +'State in one line what you will do next. If you have nothing further to add, or you believe the '
339
+ +'problem is solved / close to solved, PROPOSE a meeting (propose_meeting), propose a verification '
340
+ +'(propose_verify), or set solved=true so the group can reach a decision — do NOT produce filler work.\n'
341
+ +'\nReply with ONLY a JSON object (```json fence):\n'
342
+ +'{"summary":"<what you do next or a declaration>","solved":false,"propose_verify":"<id|null>","propose_meeting":"<agenda|null>","claim_task":"<id|null>"}'
343
+ }
344
+ function clearHeartbeat(){ if(heartbeatTimer!==null){ try{ clearTimeout(heartbeatTimer) }catch(e){} heartbeatTimer=null } }
345
+ function armHeartbeat(){
346
+ clearHeartbeat()
347
+ const ms=Number(params.activityTimeoutMs)||120000
348
+ if(!(ms>0)) return
349
+ heartbeatTimer=setTimeout(()=>{ heartbeatTimer=null; scheduleNext().catch(()=>{}) }, ms)
350
+ }
351
+ // Real DSH /compact of a resident's OWN session via ctx.compaction (if the host provides it);
352
+ // falling back silently to the resident self-summary directive when the service is absent.
353
+ async function realCompact(r){
354
+ if(!r || !r.childId) return
355
+ if(compaction===undefined || !compaction.compactIfNeeded) return
356
+ let agent
357
+ try { agent = agents.get(r.childId) } catch(e){ agent = undefined }
358
+ if(!agent || !agent.session) return
359
+ try {
360
+ const signal = makeSignal(params.activityTimeoutMs||60000)
361
+ const result = await compaction.compactIfNeeded(agent, 'pressure', signal)
362
+ if(result && (result.shadowedSeqs||[]).length>0){
363
+ r.roundsSinceCompact=0; r.needCompact=false; r.contextPct=Math.min(r.contextPct||15,25)
364
+ logActivity('compact', r.rId+' real /compact (shadowed '+result.shadowedSeqs.length+' items, ~'+String(result.shadowedTokenCount||0)+' tokens)')
365
+ }
366
+ } catch(e){ /* real compaction unavailable/failed; the soft directive already covers it */ }
367
+ }
368
+
326
369
  // ---- liveness / scheduling ----
327
370
  async function scheduleNext(){
328
- if(!running||autoDone) return
371
+ if(!running||autoDone){ clearHeartbeat(); return }
329
372
  if(phase==='brainstorm'){ await maybeFinishBrainstorm(); return }
330
373
  if(meetingState){ await continueMeetingRound(); return }
331
374
  if(verifyState){ await continueVerifyRound(); return }
332
375
  if(pendingVerify){ const pv=pendingVerify; await beginVerify(pv); return }
333
376
  // mailbox delivery
334
377
  const delivered=await deliverNextMailbox(); if(delivered) return
335
- // fairness / coordination wake of the least-recently-active resident
378
+ // maxParallel: don't start a new wake when the in-flight cap is reached
379
+ const mp=Number(params.maxParallel)||0
380
+ if(mp>0 && busy.size>=mp){ armHeartbeat(); return }
381
+ // heartbeat / coordination wake of the least-recently-active resident, ONLY after idle timeout
382
+ clearHeartbeat()
336
383
  let target=null, oldest=-1
337
384
  for(const [,r] of residents){ if(busy.has(r.rId)) continue; const idle=now()-r.lastActiveAt; if(idle>oldest){ oldest=idle; target=r } }
338
- if(target){ await wakeResident(target, await normalPrompt(target), 'normal'); await saveAll(); return }
385
+ const atOs=Number(params.activityTimeoutMs)||120000
386
+ if(target && oldest>=atOs){
387
+ // an idle timeout has elapsed: checkpoint wake (event-driven work is unchanged)
388
+ await wakeResident(target, await heartbeatPrompt(target), 'normal'); await saveAll(); return
389
+ }
390
+ // everyone is busy or not idle-enough: arm a heartbeat to re-check later (no infinite spin)
391
+ armHeartbeat()
339
392
  }
340
393
  async function maybeFinishBrainstorm(){
341
394
  const pending=[]; for(const [,r] of residents){ if(r.status==='brainstorm' && !r.insight) pending.push(r.rId) }
@@ -359,6 +412,7 @@ export function apply(ctx) {
359
412
  async function onResidentEnd(childId, info){
360
413
  const r=byChild(childId); if(!r) return
361
414
  busy.delete(r.rId)
415
+ realCompact(r).catch(()=>{}) // best-effort real DSH /compact of this resident while idle
362
416
  const output=blocksToText(info&&info.lastAssistantMessage)
363
417
  const parsed=parseReply(output)
364
418
  const kind=wakeKind.get(r.rId)||'normal'
@@ -398,7 +452,7 @@ export function apply(ctx) {
398
452
  if(residentCount) params.residentCount=Number(residentCount)||4
399
453
  running=true; autoDone=false; phase='brainstorm'
400
454
  await writeText('Problems/'+problemId+'.md','# 问题|'+problemId+'\n- ID: '+problemId+'\n- 类型: 问题\n- 状态: 求解中\n- 优先级: 1\n- 依赖: []\n\n## 陈述\n'+problemText+'\n')
401
- residents=new Map(); mailboxes=new Map(); taskboard=[]; decisions=[]; meetings=[]; reports=[]; verifyState=null; meetingState=null; pendingVerify=null; residentSeq=0; artifactCount=0
455
+ residents=new Map(); mailboxes=new Map(); taskboard=[]; decisions=[]; meetings=[]; reports=[]; verifyState=null; meetingState=null; pendingVerify=null; residentSeq=0; artifactCount=0; clearHeartbeat()
402
456
  const dirs=Array.isArray(seedDirections)?seedDirections.slice(0,params.residentCount):[]
403
457
  for(let i=0;i<params.residentCount;i++){ const r=newResident(dirs[i]||''); await spawnResident(r) }
404
458
  await saveAll(); return {ok:true,message:'v4 started: '+params.residentCount+' resident(s) brainstorming',project:currentProject}
@@ -426,8 +480,8 @@ export function apply(ctx) {
426
480
  async function addMember(direction){ const r=newResident(direction||''); await spawnResident(r); return {ok:true,id:r.rId,direction:r.direction} }
427
481
  async function removeMember(id){ const r=residents.get(id); if(!r) return {ok:false}; if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } residents.delete(id); busy.delete(id); mailboxes.delete(id); await saveAll(); return {ok:true} }
428
482
  function setParams(upd){ for(const k of Object.keys(upd||{})){ if(k in params) params[k]=upd[k] } return {ok:true} }
429
- async function initAbort(){ running=false; phase='idle'; autoDone=false; for(const [,r] of residents){ if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } r.childId=''; r.lastActiveAt=0; r.roundsSinceCompact=0 } await saveAll(); return {ok:true,message:'aborted'} }
430
- function setPause(){ running=false; return {ok:true,message:'paused'} }
483
+ async function initAbort(){ clearHeartbeat(); running=false; phase='idle'; autoDone=false; for(const [,r] of residents){ if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } r.childId=''; r.lastActiveAt=0; r.roundsSinceCompact=0 } await saveAll(); return {ok:true,message:'aborted'} }
484
+ function setPause(){ clearHeartbeat(); running=false; return {ok:true,message:'paused'} }
431
485
 
432
486
  return {
433
487
  sessionId, running:()=>running, autoDone:()=>autoDone, phase:()=>phase,
@@ -124,7 +124,7 @@ VibeMath/Projects/<project>/
124
124
  | **共识验证** | `vibe_v4_propose_verify(targetId)` → 常驻们同意后开辩论 | 独立初评→公开辩论→**全票真/假才入 Verified/**;否则留库附概率 |
125
125
  | **停止条件** | 会议中全体常驻对"原问题已解决"投票,**全部同意** → 停止唤醒 | 人工 `abort` 始终可用 |
126
126
  | **上下文/compact** | 监测每常驻上下文占比 ≥ 阈值 → 触发 DSH `/compact` | `compactThreshold` 默认 66,可调 |
127
- | **人工/助手干预** | `vibe_v4_message(all, content)` / `vibe_v4_meeting` / `vibe_v4_add_member` / `remove_member` / `vibe_v4_inject`;`/v4` slash | 不改变"之后由它们自组织" |
127
+ | **人工/助手干预** | `vibe_v4_message(all, content)` / `vibe_v4_meeting` / `vibe_v4_add_member` / `remove_member` / `vibe_v4_message(to, content)`;`/v4` slash | 不改变"之后由它们自组织" |
128
128
 
129
129
  ---
130
130
 
@@ -272,7 +272,7 @@ VibeMath/Projects/<project>/
272
272
  - **P3 会议 + 任务板 + 决策**:`meeting`/taskboard/decisions;分工涌现。
273
273
  - **P4 产物沉淀**:`record_proposition/method/subproblem` + `publish_progress`(价值/动机/概率必填校验)。
274
274
  - **P5 共识验证**:`propose_verify` + 独立初评 + 公开辩论 + 全票闭环;`Verified/` 生成与来源库回写。
275
- - **P6 停止 + 干预 + 恢复**:stop-vote(全票 solved)/abort/pause/resume;`add_member/remove_member/inject/message(all)`。
275
+ - **P6 停止 + 干预 + 恢复**:stop-vote(全票 solved)/abort/pause/resume;`add_member/remove_member/message(all)`。
276
276
  - **P7 测试**:`selfdrive-v4.mjs`(常驻互相留言/开会/沉淀/共识验证/compact/停止 的全流程记录器);E2E 断言:消息送达、会议记录、仅全票入 Verified、未全票留库附概率、compact 触发、仅全体一致才 stop、resume。
277
277
 
278
278
  ---
@@ -375,3 +375,26 @@ VibeMath/Projects/<project>/
375
375
 
376
376
  > **已知边界(未改)**:`maxParallel`/`activityTimeoutMs` 仍未实际限流/心跳门控(避免破坏"持续推进→收敛");`claim_write`/`release_write` 仍未落地为真锁;真实 DSH `/compact` API 为 TODO。
377
377
 
378
+ ---
379
+
380
+ ## 19. 边界 A 落地 + 真实 `/compact`(v1.3.5)
381
+
382
+ ### 边界 A:事件驱动 + `activityTimeoutMs` 心跳门控 + `maxParallel` 限流
383
+ 此前 `scheduleNext()` **无条件**唤醒"最久没动的人",导致"只要没达到全体一致 solved 就永远烧 token 推进"。现改为贴合哲学"框架绝不指派/促成"的做法:
384
+
385
+ - **事件驱动为主**:有信(邮箱)、任务板 open、验证提案、会议请求 → 才唤醒对应常驻(不变)。
386
+ - **心跳门控**:`scheduleNext` 只在"某常驻空闲时间 ≥ `activityTimeoutMs`"时,才唤醒"最久没动的人",并给它 **CHECKPOINT 提示词**:*"说明你下一步做什么;若已无产出/认为接近解决 → 提议开会/验证 或 声明 solved。"* 这样既防僵死,又施加**收敛/停止**压力,而非无限推进。
387
+ - **`maxParallel` 限流**:`scheduleNext` 唤醒前检查 `busy.size < maxParallel`,超限则改为 `armHeartbeat()` 等待(brainstorm 阶段的"各自独立想"仍一次全开,常态/会议/验证轮受控)。
388
+ - 新增 `heartbeatTimer` 心跳定时器:`scheduleNext` 无事可做且无人空闲到阈值时,用 `activityTimeoutMs` 定时重查;每次唤醒/开会/验证/start/pause/abort 都会 `clearHeartbeat()` 避免僵尸定时器。
389
+
390
+ > 行为变化:真实运行里若常驻都在推进(每轮 < `activityTimeoutMs`),心跳基本不触发;只有真正"无事可做"才触发一次 CHECKPOINT,推动收敛。测试中把 `activityTimeoutMs` 设小(如 40ms)以驱动自组织收敛。
391
+
392
+ ### 真实 DSH `/compact`
393
+ 找到了 DSH 真实压缩服务:**`ctx.compaction`**(`@deepseek-ai/dsh-compaction` 的 `CompactionEngine`,`agent.cordis.yml` 已加载 compaction-basic)。v4 现在对每个常驻做**真实压缩**:
394
+
395
+ - 在 `onResidentEnd`(常驻空闲)里调用 `ctx.agents.get(r.childId)` 取该常驻的 Agent,然后 `ctx.compaction.compactIfNeeded(agent, 'pressure', signal)`。
396
+ - `compactIfNeeded` 用 `ctx.tokenMeter` **真实测量**该常驻会话 token 量,超过其模型上下文窗口阈值时把旧历史折叠成总结节点——真正的 `/compact` 效果(而非仅"注入浓缩指令+记账复位")。
397
+ - 成功后复位 `roundsSinceCompact/needCompact` 并记 `logActivity`。**若宿主未提供 `ctx.compaction`(或模型未配 `contextWindow`)则静默回退**到现有"自述指令"等效层。
398
+
399
+ > 局限:v4 的 mock 测试里 `compaction` 为 undefined,故这条**只在真实 DSH 上生效**;需在真实运行体确认 `ctx.compaction` 可用、且会话为常驻自身 session、模型已配 `contextWindow`。部署前请在当前运行 DSH 上验证。`claim_write`/`release_write` 仍保留为占位(常驻专属目录无写冲突)。
400
+