dsh-vibe-math 2.0.8 → 2.0.10
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
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
**一句话流水线**:起始产生 N 个**常驻子代理**(continuable,持久上下文)先各自头脑风暴、产出初始见解/方向 → 此后**所有任务安排由它们互相留言 + 集体开会自主决定**(框架只做消息总线/会议/任务板/产物沉淀,**绝不分配任务**);每个常驻把有价值的产物按**价值程度 / 动机用途计划 / 自身概率估计**沉淀到**自己**的 `Progress/<id>/`、`Propos/<id>/`、`Methods/<id>/`、`Subproblems/<id>/` 库,并**可互相阅读**;验证由它们**自行商议**发起,**仅当全体常驻一致(真或假)**才写入 `Verified/`,否则留库附概率;常驻上下文量达阈值(默认 66%)自动 `/compact`;**仅当全体一致认为原问题已解决**才停止;可随时人工干预/增开/关闭常驻,支持断点续跑。
|
|
41
41
|
|
|
42
42
|
> 说明:V4 去掉 v3 的中央规划器与确定性角色(explorer/solver/verifier/planner/method-keeper),把"研究者"本身作为主体。详见 `vibe-math-v4/实现方案.md`。
|
|
43
|
-
> 保活机制(分级保活 A+B + 死锁看门狗):团伙空闲超 `activityTimeoutMs` 会收到**自驱动** CHECKPOINT(建议继续解决/发消息/提议任务,而非"是否要停止"
|
|
43
|
+
> 保活机制(分级保活 A+B + 死锁看门狗):团伙空闲超 `activityTimeoutMs` 会收到**自驱动** CHECKPOINT(建议继续解决/发消息/提议任务,而非"是否要停止"),且**并行填充**——A 分支一次尽量填满 `maxParallel` 并发预算(同一时刻唤醒多个空闲常驻,而非"只唤醒 r1、结束后再 r2"的串行),邮箱投递也并行送达多个空闲收件人;唤醒失败会自动重新武装心跳;若团队空闲且**无新产物**超过 `stallAutoMeetingMs`(默认 6 分钟),框架会自动召集一次同步会议让常驻们自行决定下一步;若某次**会议/验证卡死**(超过 2×`activityTimeoutMs` 仍无新的发言/投票),框架会自动**放弃该会议/验证**并回到正常自组织,避免一个坏掉的会议永久卡住整个团队;**会议不抢占验证**——验证进行时会议请求会暂存,验证做完再补开(保持一致共识的"求真"环节不被协调讨论打断)——框架始终只促成、从不指派任务。
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
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 — THREE agent presets in one install: 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 three presets (v1 was removed at v2.0.0).",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.10",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -46,7 +46,7 @@ export function apply(ctx) {
|
|
|
46
46
|
let residents = new Map(), mailboxes = new Map(), taskboard = [], decisions = []
|
|
47
47
|
let meetings = [], reports = [], activityLog = []
|
|
48
48
|
let problemText = '', problemId = 'problem', runId = 'run-' + shortId()
|
|
49
|
-
let meetingState = null, verifyState = null, pendingVerify = null
|
|
49
|
+
let meetingState = null, verifyState = null, pendingVerify = null, pendingMeeting = null
|
|
50
50
|
let busy = new Set(), wakeKind = new Map(), currentResident = ''
|
|
51
51
|
let lastActivityAt = now(), lastProgressAt = now(), artifactCount = 0, lastSyncMeetingAt = 0, persistedEpoch = '', heartbeatDisposer = null
|
|
52
52
|
const activityLogCap = 200
|
|
@@ -367,6 +367,10 @@ export function apply(ctx) {
|
|
|
367
367
|
// ---- meeting ----
|
|
368
368
|
async function startMeeting(agenda,type,targetId){
|
|
369
369
|
if(meetingState) return {ok:false,message:'meeting already in progress'}
|
|
370
|
+
// A meeting must NOT preempt an active or pending verification (unanimous-consensus is the
|
|
371
|
+
// group's truth-making step; preempting it would let every round resurface the same conflict).
|
|
372
|
+
// Wait instead of stealing the floor: park the request and resume it after the verify settles.
|
|
373
|
+
if(verifyState || pendingVerify){ pendingMeeting = { agenda, type:type||'general', targetId:targetId||null }; return {ok:true,deferred:true,during:'verify'} }
|
|
370
374
|
clearHeartbeat()
|
|
371
375
|
const ids=Array.from(residents.keys())
|
|
372
376
|
// Rotate the per-meeting speaking order so the SAME resident isn't always the "first speaker
|
|
@@ -415,7 +419,7 @@ export function apply(ctx) {
|
|
|
415
419
|
const votes=Object.values(st.inputs).map(x=>x.voteSolved).filter(v=>typeof v==='boolean')
|
|
416
420
|
const allSolved = allSpoke && votes.length>0 && votes.every(v=>v===true)
|
|
417
421
|
logActivity('meeting', 'concluded'+(allSolved?' → ALL agree solved':' (no unanimous solved vote)'))
|
|
418
|
-
if(allSolved){ running=false; autoDone=true; phase='done'; logActivity('stop','all residents agree: problem solved'); await saveAll(); return }
|
|
422
|
+
if(allSolved){ running=false; autoDone=true; phase='done'; clearHeartbeat(); logActivity('stop','all residents agree: problem solved'); await saveAll(); return }
|
|
419
423
|
meetingState=null; wakeKind.clear(); await saveAll(); await scheduleNext()
|
|
420
424
|
}
|
|
421
425
|
|
|
@@ -553,6 +557,9 @@ export function apply(ctx) {
|
|
|
553
557
|
if(meetingState){ await continueMeetingRound(); return }
|
|
554
558
|
if(verifyState){ await continueVerifyRound(); return }
|
|
555
559
|
if(pendingVerify){ const pv=pendingVerify; await beginVerify(pv); return }
|
|
560
|
+
// A meeting requested while a verify held the floor is parked in pendingMeeting; once the
|
|
561
|
+
// verify has truly settled (no verifyState / pendingVerify), resume it before anything else.
|
|
562
|
+
if(pendingMeeting){ const pm=pendingMeeting; pendingMeeting=null; await startMeeting(pm.agenda, pm.type, pm.targetId); return }
|
|
556
563
|
// mailbox delivery
|
|
557
564
|
const delivered=await deliverNextMailbox(); if(delivered) return
|
|
558
565
|
// maxParallel: don't start a new wake when the in-flight cap is reached
|
|
@@ -570,21 +577,34 @@ export function apply(ctx) {
|
|
|
570
577
|
return
|
|
571
578
|
}
|
|
572
579
|
}
|
|
573
|
-
// A) heartbeat / coordination: wake
|
|
574
|
-
//
|
|
575
|
-
//
|
|
576
|
-
//
|
|
580
|
+
// A) heartbeat / coordination: wake IDLE residents after an idle timeout to SELF-DRIVE (continue
|
|
581
|
+
// solving / message / propose task / meeting / verify). This is a CONCURRENCY FILL, not a
|
|
582
|
+
// single nudge: scheduleNext should wake up to `maxParallel` idle residents in one pass so the
|
|
583
|
+
// group can progress in parallel (design §A: "同一时刻可唤醒多个空闲常驻,受 maxParallel 上限").
|
|
584
|
+
// On a FAILED wake we re-arm the heartbeat so a single follow-up error NEVER permanently stops
|
|
585
|
+
// the group (a successful wake re-drives scheduleNext through its own onResidentEnd, which re-arms).
|
|
577
586
|
clearHeartbeat()
|
|
578
|
-
let target=null, oldest=-1
|
|
579
|
-
for(const [,r] of residents){ if(busy.has(r.rId)) continue; const idle=now()-r.lastActiveAt; if(idle>oldest){ oldest=idle; target=r } }
|
|
580
587
|
const atOs=Number(params.activityTimeoutMs)||120000
|
|
581
|
-
|
|
588
|
+
// `mp` (maxParallel) is already declared above in this function scope.
|
|
589
|
+
// Collect idle (not busy) residents sorted by idle time, oldest-first (round-robin fairness).
|
|
590
|
+
const idleCandidates = Array.from(residents.values())
|
|
591
|
+
.filter(r=>!busy.has(r.rId))
|
|
592
|
+
.sort((a,b)=>(now()-b.lastActiveAt)-(now()-a.lastActiveAt))
|
|
593
|
+
// Fill the concurrency budget: keep waking the most-idle resident until either everyone idle is
|
|
594
|
+
// started OR the in-flight cap (maxParallel) is reached. This turns the previous "one at a time"
|
|
595
|
+
// serialization into genuine parallel progress.
|
|
596
|
+
let started=0
|
|
597
|
+
for(const r of idleCandidates){
|
|
598
|
+
const free = mp>0 ? (mp - busy.size) : Number.MAX_SAFE_INTEGER
|
|
599
|
+
if(free<=0) break // concurrency cap reached → stop filling
|
|
600
|
+
if((now()-r.lastActiveAt)<atOs) break // the remaining are all busy-or-not-idle-enough
|
|
582
601
|
let ok=false
|
|
583
|
-
try { ok = await wakeResident(
|
|
602
|
+
try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){ ok=false }
|
|
603
|
+
if(ok) started++
|
|
584
604
|
await saveAll()
|
|
585
|
-
if(!ok)
|
|
586
|
-
return
|
|
605
|
+
if(!ok) continue // a failed wake must NOT stop the fill; try the next idle resident
|
|
587
606
|
}
|
|
607
|
+
if(started>0) { return } // at least one started; their onResidentEnd re-drives scheduleNext
|
|
588
608
|
// everyone is busy or not idle-enough: arm a heartbeat to re-check later (no infinite spin)
|
|
589
609
|
armHeartbeat()
|
|
590
610
|
}
|
|
@@ -597,13 +617,23 @@ export function apply(ctx) {
|
|
|
597
617
|
await writeText('Shared/meetings/brainstorm.md', lines.join('\n'))
|
|
598
618
|
}
|
|
599
619
|
async function deliverNextMailbox(){
|
|
620
|
+
// Deliver queued messages to ALL currently-idle recipients in one pass (parallel), bounded by the
|
|
621
|
+
// same maxParallel concurrency cap, so a group chat (relayToGroup → many non-busy recipients) is
|
|
622
|
+
// not serialized one-message-at-a-time. Returns true if anything was delivered. A busy recipient
|
|
623
|
+
// keeps its message queued (avoid starving others).
|
|
624
|
+
let delivered=false
|
|
600
625
|
for(const [to,msgs] of mailboxes){
|
|
601
626
|
if(msgs.length===0) continue
|
|
602
|
-
const
|
|
603
|
-
if(
|
|
604
|
-
|
|
627
|
+
const r=residents.get(to); if(!r){ continue }
|
|
628
|
+
if(busy.has(to)) continue // recipient busy → leave the message queued for a later pass
|
|
629
|
+
const mp=Number(params.maxParallel)||0
|
|
630
|
+
if(mp>0 && busy.size>=mp) break // concurrency cap reached → stop delivering more now
|
|
631
|
+
const m=msgs.shift()
|
|
632
|
+
currentResident=to
|
|
633
|
+
const ok = await wakeResident(r, (await normalPrompt(r))+'\n\n[MESSAGE from '+m.from+']\n'+m.content,'normal')
|
|
634
|
+
await saveAll(); if(!ok) msgs.unshift(m); delivered=delivered||ok
|
|
605
635
|
}
|
|
606
|
-
return
|
|
636
|
+
return delivered
|
|
607
637
|
}
|
|
608
638
|
|
|
609
639
|
// ---- resident end handler ----
|
|
@@ -653,8 +683,13 @@ export function apply(ctx) {
|
|
|
653
683
|
if(parsed.propose_task) await proposeTask(parsed.propose_task, parsed.task_desc||'', r.rId)
|
|
654
684
|
if(parsed.claim_task) await claimTask(parsed.claim_task, r.rId)
|
|
655
685
|
if(parsed.task_done) await taskDone(parsed.task_done, r.rId)
|
|
656
|
-
// a resident may self-trigger a meeting (resident-driven coordination, closest to the philosophy)
|
|
657
|
-
|
|
686
|
+
// a resident may self-trigger a meeting (resident-driven coordination, closest to the philosophy).
|
|
687
|
+
// If a verify is holding the floor the meeting is deferred (pendingMeeting) and we fall through
|
|
688
|
+
// so the pending verify (or mailbox/heartbeat) still advances rather than being stuck behind it.
|
|
689
|
+
if(parsed.propose_meeting && !meetingState){
|
|
690
|
+
const mr=await startMeeting(String(parsed.propose_meeting),'general',null); await saveAll()
|
|
691
|
+
if(mr && !mr.deferred) return
|
|
692
|
+
}
|
|
658
693
|
await saveAll(); await scheduleNext()
|
|
659
694
|
}
|
|
660
695
|
|
|
@@ -670,6 +705,7 @@ export function apply(ctx) {
|
|
|
670
705
|
running=true; autoDone=false; phase='brainstorm'
|
|
671
706
|
await writeText('Problems/'+problemId+'.md','# 问题|'+problemId+'\n- ID: '+problemId+'\n- 类型: 问题\n- 状态: 求解中\n- 优先级: 1\n- 依赖: []\n\n## 陈述\n'+problemText+'\n')
|
|
672
707
|
residents=new Map(); mailboxes=new Map(); taskboard=[]; decisions=[]; meetings=[]; reports=[]; verifyState=null; meetingState=null; pendingVerify=null; residentSeq=0; artifactCount=0; clearHeartbeat()
|
|
708
|
+
busy=new Set(); wakeKind=new Map(); currentResident=''; pendingMeeting=null; lastSyncMeetingAt=0 // fresh run must NOT inherit stale concurrency/coordination state (busy/wakeKind/currentResident/pendingMeeting) from a previous run on the same reused session
|
|
673
709
|
lastActivityAt=now(); lastProgressAt=now() // fresh stall/activity clock for the new run (else B could fire immediately on a reused session)
|
|
674
710
|
const dirs=Array.isArray(seedDirections)?seedDirections.slice(0,params.residentCount):[]
|
|
675
711
|
for(let i=0;i<params.residentCount;i++){ const r=newResident(dirs[i]||''); await spawnResident(r) }
|
|
@@ -527,7 +527,9 @@ VibeMath/Projects/<project>/
|
|
|
527
527
|
|
|
528
528
|
### A · 自驱动心跳(修复推进力 + 永不永久停死)
|
|
529
529
|
- `heartbeatPrompt` 改为**自驱动**:不是"是否要停止",而是"**请继续解决这个问题**——读他人库、推进子问题/引理/方法、尝试路线;或向团队发消息(input)、提议任务(propose_task);确实已解决/无路可走才提议开会/声明 solved。默认立场是推进而非停在原地。" 心跳回复 schema 增加 `input`、`propose_task`、`contextPct`,使一轮心跳能产生群聊消息/任务从而带动后续工作。
|
|
530
|
+
- **并行填充**(修复"只有 r1、再只有 r2"串行):`scheduleNext` A 分支不再是"一次只唤醒一个最闲常驻",而是收集所有空闲常驻(按空闲时长从大到小)**一次尽量填满 `maxParallel` 并发预算**——每个 `wakeResident` 只 sendMessage 发送(立即返回,等各自 `subagent/end` 异步回归),因此一轮可同时唤醒多个常驻,真正并行推进。失败的唤醒只跳过该常驻、继续填满预算;唤醒失败则重新武装心跳。
|
|
530
531
|
- **唤醒失败重新武装心跳**:`scheduleNext` 心跳分支、`continueMeetingRound`、`continueVerifyRound` 在 `wakeResident` 失败(返回 false)时调用 `armHeartbeat()`,保证**任何一次唤醒失败都不会让小组永久停住**(会稍后重试其它常驻)。
|
|
532
|
+
- **并发邮箱投递**:`deliverNextMailbox` 一次投递所有当前空闲收件人的消息(受 `maxParallel` 上限),避免 `relayToGroup` 广播到多个常驻时被"一次一个"串行化;忙碌收件人保留其消息队列稍后再投(避免饿死其它常驻)。
|
|
531
533
|
|
|
532
534
|
### B · 停滞自动同步会议(分级保活 B)
|
|
533
535
|
- 新增 `lastProgressAt`(会话状态,随产物/会议/验证/任务/群聊/消息更新,`markProgress()`),并持久化到 `State/session.json`。
|
|
@@ -550,6 +552,9 @@ VibeMath/Projects/<project>/
|
|
|
550
552
|
- 同时:**找不到空闲的未发言/未投票常驻时不再静默 `return`,改为 `armHeartbeat()`**(稍后重查),并在发现"没有空闲者"时也武装心跳,杜绝"一直等待一个永不空闲的常驻"造成的隐性挂起。
|
|
551
553
|
- 分级保活 B 增加 `busy.size===0` 守卫:任何常驻正在工作时不触发,避免预抢占在途轮次。
|
|
552
554
|
|
|
555
|
+
### 会议与验证互斥(不抢占)
|
|
556
|
+
- 并行填充常驻后,一个常驻可能"提议验证(propose_verify)"而另一个同时"提议开会(propose_meeting)"。**会议不得抢占验证**:`startMeeting` 在 `verifyState || pendingVerify` 时改为把会议请求**暂存**(`pendingMeeting`,仅记录 agenda/type/target)并返回 `{deferred:true}`;`scheduleNext` 在所有 meeting/verify/pending 都清空后才恢复该暂存会议。这样统一共识验证(全真/全假)作为"求真"环节不会被会议的协调讨论打断,验证做完后再开会开会协调下一步——两者互斥但都不丢失(暂存会议之后补开)。
|
|
557
|
+
|
|
553
558
|
> 效果:一个卡死/坏掉的会议或验证最多阻塞 `activityTimeoutMs`×2 后自动释放,团队重新回到 A/B 分级保活,**不会永久停死**。仍符合"框架只促成、从不指派任务"(放弃只是终止一个无法推进的会议,把控制权交还团队的自组织循环)。
|
|
554
559
|
>
|
|
555
560
|
> 说明:无法对"某常驻真的拒绝/掉线"的情况达成全体一致时,看门狗会把该对象保留为"未定论/带概率",这是哲学上期望的诚实结果。`session.json` 仍不持久化 `meetingState`(进行中的会议不跨进程恢复),B 的停滞看门狗在重启后仍会触发,因此重启也能自愈。
|