dsh-vibe-math 2.0.7 → 2.0.9
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
|
|
|
@@ -97,7 +97,7 @@ dsh plugin --profile <你的 profile> add github:ChongCyrus/Vibe-Mathematics
|
|
|
97
97
|
|
|
98
98
|
- **形态依赖**:三个 preset 依赖 DSH 的标准 **agent-preset 机制**(`~/.dsh/.agent-presets/<id>/` + preset picker)与 **bundle patch 机制**(`cordis.patch.yml` 注入安装器)。
|
|
99
99
|
- **宿主插件行**:`agent.cordis.yml` 引用宿主提供的 `@deepseek-ai/dsh-*` 插件行(persona、agent-instructions、tool-bash/pwsh、tool-fs/fs-search、tool-jobs、skill-filesystem、tool-skill、tool-goal、plan-mode、compaction、subagent/workflow、ask-user、todo、web 等,约 21 个唯一包名)。宿主缺行会导致 preset 挂载失败(会话启动时报错)。
|
|
100
|
-
- **宿主服务 API**:预设插件消费 `subagents`(startContinuable / followup
|
|
100
|
+
- **宿主服务 API**:预设插件消费 `subagents`(startContinuable / **sendMessage**(续做/唤醒;`followup` 仅为 `Agent` 对象方法、**不是** `subagents` 服务方法)/ interrupt)、`agents`(roots)、`tools`(register)、`commands`(register)、`fs`(resolve/stat/readText/writeText/listDir)、可选 `subprocess` / `sandboxPolicy`。这些 API 形状随 DSH 版本演进;本项目**已充分测试并确认适配 `dsh-v0.1.2-rc.1`**(`package.json` 的 `dsh.testedVersion`;`minVersion` 为 `0.1.2-rc.1`)。**注意:DSH 0.1.2 起 `subagents.startContinuable` 的 `agentOptions` / `toolFilter` 需要宿主 provider 声明对应 capability**(spawn / fork 进程内 provider 均支持,v4 指定常驻模型/路由与工具权限依赖于此)。
|
|
101
101
|
- **DSH STORE 兼容声明**:`package.json` 的 `dsh.compatibility.dshReleases` 对每个完整 DSH 版本逐项声明 `compatible` / `incompatible` / `unknown`(当前 `0.1.2-alpha.4`、`0.1.2-alpha.5`、`0.1.2-rc.1` 均标 `compatible`);`engines.node` 为 `^22.19.0 || >=24.0.0`(与 DSH 0.1.2 相同)。
|
|
102
102
|
- **运行时自检(能力 + 版本双检)**:安装器(bundle 插件)每次启动时:**① 尽力探测 DSH 版本**(读 `@deepseek-ai/dsh/package.json` 或 `DSH_VERSION` 环境变量;DSH 未通过公开 service/context 暴露版本,故为尽力而为,探测不到就跳过)。若探测到且该版本未被 `dshReleases` 声明为 `compatible`,会给出明确"DSH 版本不匹配,请使用 `dsh-v0.1.2-rc.1`(或 `0.1.2-alpha.4/alpha.5`)"提示;**② 再对上述服务与关键 API 做能力自检**(这是真正的挂载门槛,含 `fs.resolve` 返回形状检测与 subagent `agentOptions`/`toolFilter` capability 检测),不满足时打 warning。preset 挂载失败时先看 DSH 日志里的自检 warning。
|
|
103
103
|
- **升级路径**:DSH 升级后无需重装本包;升级本包用 `dsh plugin update dsh-vibe-math`,重启 DSH 后安装器会自动把 preset 更新到新版本(见上文「安装」说明)。
|
package/installer.js
CHANGED
|
@@ -113,7 +113,9 @@ async function checkHostCapabilities(ctx, logger) {
|
|
|
113
113
|
// 2) capability self-check (the authoritative mounting gate; also covers hosts whose version
|
|
114
114
|
// could not be read). subagents / agents / tools / commands / fs shapes + v4 capabilities.
|
|
115
115
|
const checks = [
|
|
116
|
-
|
|
116
|
+
// subagents 服务的续做/唤醒方法是 sendMessage(sender, targetId, content, {signal});
|
|
117
|
+
// followup 不是 subagents 服务的方法(它只是 Agent 对象方法)。同时探测两者,能用一个即可。
|
|
118
|
+
['subagents', ['startContinuable', 'interrupt']],
|
|
117
119
|
['agents', ['roots']],
|
|
118
120
|
['tools', ['register']],
|
|
119
121
|
['commands', ['register']],
|
|
@@ -128,6 +130,10 @@ async function checkHostCapabilities(ctx, logger) {
|
|
|
128
130
|
for (let j = 0; j < methods.length; j++) {
|
|
129
131
|
if (typeof s[methods[j]] !== 'function') problems.push(svc + '.' + methods[j] + ' 不可用(宿主版本可能过旧)')
|
|
130
132
|
}
|
|
133
|
+
// subagents continuation (wake) API: sendMessage (modern) OR followup (legacy) must exist.
|
|
134
|
+
if (svc === 'subagents' && typeof s.sendMessage !== 'function' && typeof s.followup !== 'function') {
|
|
135
|
+
problems.push('subagents 缺少续做/唤醒方法(需 sendMessage 或 followup 至少其一)')
|
|
136
|
+
}
|
|
131
137
|
}
|
|
132
138
|
// fs API shape: DSH 0.1.1 起 resolve 返回 {targetKey, displayPath} 对象(旧版返回字符串路径)
|
|
133
139
|
try {
|
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.9",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -444,7 +444,19 @@ export function apply(ctx) {
|
|
|
444
444
|
scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1
|
|
445
445
|
await saveAll(); return started.childId
|
|
446
446
|
}
|
|
447
|
-
|
|
447
|
+
// DSH continuable-wake API is subagents.sendMessage(sender, targetId, content, {signal}); subagents.followup
|
|
448
|
+
// does NOT exist on the subagents service (it is only Agent.followup). Calling the missing method threw
|
|
449
|
+
// TypeError and made every wake fail silently. Prefer sendMessage, fall back to a legacy followup.
|
|
450
|
+
async function followupChild(childId, promptText) {
|
|
451
|
+
const blocks = [textBlock(promptText)]
|
|
452
|
+
try {
|
|
453
|
+
if (typeof subagents.sendMessage === 'function') await subagents.sendMessage(rootAgent, childId, blocks, { signal: makeSignal(30000) })
|
|
454
|
+
else if (typeof subagents.followup === 'function') await subagents.followup(rootAgent, childId, blocks, { source: { kind: 'user' }, signal: makeSignal(30000) })
|
|
455
|
+
else throw new Error('no subagent continuation API')
|
|
456
|
+
} catch (e) { console.error('vibe-math-v2: wake ' + childId + ' failed: ' + String((e && e.message) || e)); throw e }
|
|
457
|
+
scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1
|
|
458
|
+
await saveAll()
|
|
459
|
+
}
|
|
448
460
|
async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
|
|
449
461
|
|
|
450
462
|
// ================= prompts =================
|
|
@@ -888,7 +888,19 @@ export function apply(ctx) {
|
|
|
888
888
|
await saveAll()
|
|
889
889
|
return started.childId
|
|
890
890
|
}
|
|
891
|
-
|
|
891
|
+
// DSH continuable-wake API is subagents.sendMessage(sender, targetId, content, {signal}); subagents.followup
|
|
892
|
+
// does NOT exist on the subagents service (it is only Agent.followup). Calling the missing method threw
|
|
893
|
+
// TypeError and made every wake fail silently. Prefer sendMessage, fall back to a legacy followup.
|
|
894
|
+
async function followupChild(childId, promptText) {
|
|
895
|
+
const blocks = [textBlock(promptText)]
|
|
896
|
+
try {
|
|
897
|
+
if (typeof subagents.sendMessage === 'function') await subagents.sendMessage(rootAgent, childId, blocks, { signal: makeSignal(30000) })
|
|
898
|
+
else if (typeof subagents.followup === 'function') await subagents.followup(rootAgent, childId, blocks, { source: { kind: 'user' }, signal: makeSignal(30000) })
|
|
899
|
+
else throw new Error('no subagent continuation API')
|
|
900
|
+
} catch (e) { console.error('vibe-math-v3: wake ' + childId + ' failed: ' + String((e && e.message) || e)); throw e }
|
|
901
|
+
scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1
|
|
902
|
+
await saveAll()
|
|
903
|
+
}
|
|
892
904
|
async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
|
|
893
905
|
|
|
894
906
|
// ================= prompts =================
|
|
@@ -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
|
|
@@ -280,7 +280,21 @@ export function apply(ctx) {
|
|
|
280
280
|
prompt = coreRulesBrief() + '\n' + prompt
|
|
281
281
|
r.needCompact = false
|
|
282
282
|
}
|
|
283
|
-
|
|
283
|
+
// The DSH continuable-wake API is subagents.sendMessage(sender, targetId, content, {signal}),
|
|
284
|
+
// NOT subagents.followup (which is only Agent.followup, and does NOT exist on the subagents
|
|
285
|
+
// service). Using a non-existent method threw TypeError and made EVERY wake fail silently →
|
|
286
|
+
// the group went idle forever. Prefer sendMessage; fall back to a legacy followup if a host
|
|
287
|
+
// still exposes it (older deployments), so this works across versions.
|
|
288
|
+
try {
|
|
289
|
+
if(typeof subagents.sendMessage==='function'){
|
|
290
|
+
await subagents.sendMessage(rootAgent, r.childId, [textBlock(prompt)], {signal: makeSignal(params.activityTimeoutMs||60000)})
|
|
291
|
+
} else if(typeof subagents.followup==='function'){
|
|
292
|
+
await subagents.followup(rootAgent, r.childId, [textBlock(prompt)], {source:{kind:'user'},signal:makeSignal(params.activityTimeoutMs||60000)})
|
|
293
|
+
} else {
|
|
294
|
+
throw new Error('no subagent continuation API (need sendMessage or followup)')
|
|
295
|
+
}
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
284
298
|
catch(e){ console.error('vibe-v4 wake '+r.rId+' failed: '+String((e&&e.message)||e)); busy.delete(r.rId); return false }
|
|
285
299
|
}
|
|
286
300
|
function byChild(childId){ for(const [,r] of residents){ if(r.childId===childId) return r } return undefined }
|
|
@@ -353,6 +367,10 @@ export function apply(ctx) {
|
|
|
353
367
|
// ---- meeting ----
|
|
354
368
|
async function startMeeting(agenda,type,targetId){
|
|
355
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'} }
|
|
356
374
|
clearHeartbeat()
|
|
357
375
|
const ids=Array.from(residents.keys())
|
|
358
376
|
// Rotate the per-meeting speaking order so the SAME resident isn't always the "first speaker
|
|
@@ -539,6 +557,9 @@ export function apply(ctx) {
|
|
|
539
557
|
if(meetingState){ await continueMeetingRound(); return }
|
|
540
558
|
if(verifyState){ await continueVerifyRound(); return }
|
|
541
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 }
|
|
542
563
|
// mailbox delivery
|
|
543
564
|
const delivered=await deliverNextMailbox(); if(delivered) return
|
|
544
565
|
// maxParallel: don't start a new wake when the in-flight cap is reached
|
|
@@ -556,21 +577,34 @@ export function apply(ctx) {
|
|
|
556
577
|
return
|
|
557
578
|
}
|
|
558
579
|
}
|
|
559
|
-
// A) heartbeat / coordination: wake
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
//
|
|
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).
|
|
563
586
|
clearHeartbeat()
|
|
564
|
-
let target=null, oldest=-1
|
|
565
|
-
for(const [,r] of residents){ if(busy.has(r.rId)) continue; const idle=now()-r.lastActiveAt; if(idle>oldest){ oldest=idle; target=r } }
|
|
566
587
|
const atOs=Number(params.activityTimeoutMs)||120000
|
|
567
|
-
|
|
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
|
|
568
601
|
let ok=false
|
|
569
|
-
try { ok = await wakeResident(
|
|
602
|
+
try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){ ok=false }
|
|
603
|
+
if(ok) started++
|
|
570
604
|
await saveAll()
|
|
571
|
-
if(!ok)
|
|
572
|
-
return
|
|
605
|
+
if(!ok) continue // a failed wake must NOT stop the fill; try the next idle resident
|
|
573
606
|
}
|
|
607
|
+
if(started>0) { return } // at least one started; their onResidentEnd re-drives scheduleNext
|
|
574
608
|
// everyone is busy or not idle-enough: arm a heartbeat to re-check later (no infinite spin)
|
|
575
609
|
armHeartbeat()
|
|
576
610
|
}
|
|
@@ -583,19 +617,36 @@ export function apply(ctx) {
|
|
|
583
617
|
await writeText('Shared/meetings/brainstorm.md', lines.join('\n'))
|
|
584
618
|
}
|
|
585
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
|
|
586
625
|
for(const [to,msgs] of mailboxes){
|
|
587
626
|
if(msgs.length===0) continue
|
|
588
|
-
const
|
|
589
|
-
if(
|
|
590
|
-
|
|
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
|
|
591
635
|
}
|
|
592
|
-
return
|
|
636
|
+
return delivered
|
|
593
637
|
}
|
|
594
638
|
|
|
595
639
|
// ---- resident end handler ----
|
|
596
640
|
async function onResidentEnd(childId, info){
|
|
597
641
|
const r=byChild(childId); if(!r) return
|
|
598
642
|
busy.delete(r.rId)
|
|
643
|
+
// Any resident turn that COMPLETED is real activity for the stall clock (B). Residents frequently
|
|
644
|
+
// write their libraries via direct fs (not the record* tools), so relying only on
|
|
645
|
+
// bumpArtifacts/markProgress would leave lastProgressAt stale and B would fire against an active
|
|
646
|
+
// team. We count only a clean 'completed' turn: an error/max-tokens/refusal did NOT meaningfully
|
|
647
|
+
// advance the work, so it must NOT mask a truly stalled group (B can then convene a recovery
|
|
648
|
+
// meeting). A completed turn also refreshes the meeting/verify deadlock clock through lastInputAt.
|
|
649
|
+
if(info && info.stopReason==='completed') markProgress()
|
|
599
650
|
realCompact(r).catch(()=>{}) // best-effort real DSH /compact of this resident while idle
|
|
600
651
|
const output=blocksToText(info&&info.lastAssistantMessage)
|
|
601
652
|
const parsed=parseReply(output)
|
|
@@ -632,8 +683,13 @@ export function apply(ctx) {
|
|
|
632
683
|
if(parsed.propose_task) await proposeTask(parsed.propose_task, parsed.task_desc||'', r.rId)
|
|
633
684
|
if(parsed.claim_task) await claimTask(parsed.claim_task, r.rId)
|
|
634
685
|
if(parsed.task_done) await taskDone(parsed.task_done, r.rId)
|
|
635
|
-
// a resident may self-trigger a meeting (resident-driven coordination, closest to the philosophy)
|
|
636
|
-
|
|
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
|
+
}
|
|
637
693
|
await saveAll(); await scheduleNext()
|
|
638
694
|
}
|
|
639
695
|
|
|
@@ -88,7 +88,7 @@ flowchart TD
|
|
|
88
88
|
### 4.1 本质
|
|
89
89
|
- 每个常驻 = 一个 DSH **continuable 子代理**:`subagents.startContinuable({ parent: 会话根代理, request, agentOptions, toolFilter })`。
|
|
90
90
|
- **持久上下文**(continuable):跨多次唤醒记住前文,直到被 `/compact` 压缩。
|
|
91
|
-
- **唤醒** = `subagents.
|
|
91
|
+
- **唤醒** = `subagents.sendMessage(root, <childId>, [textBlock(prompt)], {signal})`(DSH 的 `subagents` 服务没有 `followup` 方法——`followup` 只是 `Agent` 对象的方法;`sendMessage` 才是服务暴露的续做 API。旧代码 `subagents.followup(...)` 在真实运行时抛 `TypeError`,导致每次唤醒都失败、小组整体停摆);一轮收益 = 该常驻提交一次完整思考(经 `ctx.on('subagent/end')` 回归,`stateOf`=settled 时 `watchSettlement` 每次 dispose 都会重新触发一次 `/end`)。
|
|
92
92
|
- **工具**:`tools.register` 对会话内所有代理可用(处理器按 `exec.agent` 路由),故常驻可直接用 `fs` + `vibe_v4_*` 工具(写自己的库、读他人的库、发消息、提会议、记命题、发起验证)。
|
|
93
93
|
|
|
94
94
|
### 4.2 身份与上下文
|
|
@@ -117,7 +117,7 @@ VibeMath/Projects/<project>/
|
|
|
117
117
|
| 能力 | 机制 | 关键点 |
|
|
118
118
|
|---|---|---|
|
|
119
119
|
| **常驻生命周期** | start 时 brainstorm→spawn N(各带初始方向);add/remove;resume 重建 | 常驻上下文断点后需 re-spawn 并用其 `progress.md` 重种化 |
|
|
120
|
-
| **消息总线** | `vibe_v4_send_message(to, content)` → 入目标邮件箱 → 若空闲则 `
|
|
120
|
+
| **消息总线** | `vibe_v4_send_message(to, content)` → 入目标邮件箱 → 若空闲则 `sendMessage` 唤醒 | 常驻间不直接互调,全靠框架 relay(模拟收件箱);支持 `broadcast(all)` |
|
|
121
121
|
| **会议** | `vibe_v4_meeting(agenda)` → 向全体发会议 prompt → 收齐发言 → 写 `Shared/meetings/<id>.md` → 广播结论 | 会议用于分工/方向/任务分配/提出验证/表决"是否已解决" |
|
|
122
122
|
| **任务板** | 常驻在会议/留言提议任务 → 框架记 `Shared/taskboard.md`;认领后被唤醒 | 框架只搬运,不决定谁做什么 |
|
|
123
123
|
| **产物沉淀** | `vibe_v4_publish_progress` / `record_proposition` / `record_method` / `record_subproblem` | **必填**:价值程度 / 动机用途计划 / 自身概率估计(框架校验,缺则提示) |
|
|
@@ -254,7 +254,7 @@ VibeMath/Projects/<project>/
|
|
|
254
254
|
| DSH 能力 | 在本架构中的用途 |
|
|
255
255
|
|---|---|
|
|
256
256
|
| `subagents.startContinuable({parent, request, agentOptions, toolFilter})` | 产生常驻(continuable,持久上下文) |
|
|
257
|
-
| `subagents.
|
|
257
|
+
| `subagents.sendMessage(root, childId, blocks, {signal})` | 唤醒常驻做一轮 / 投递一条留言 / 开会向某常驻收集发言(**服务暴露的续做 API**;旧 `subagents.followup` 不存在) |
|
|
258
258
|
| `ctx.on('subagent/end')` | 一常驻完成一轮(收其回复,续驱动) |
|
|
259
259
|
| `subagents.interrupt / list` | 中断 / 枚举常驻 |
|
|
260
260
|
| `tools.register`(会话内所有代理可用,`exec.agent` 路由) | 常驻可直接调用 `vibe_v4_*` 与 `fs` |
|
|
@@ -521,12 +521,15 @@ VibeMath/Projects/<project>/
|
|
|
521
521
|
|
|
522
522
|
针对一次真实 run("跑完就停")的诊断:三名常驻第一轮就把问题归约到**同一个硬核引理**并一致给出 `solved=false`——这是**自然的难点停滞**,而非 bug。但框架的活性机制太弱且会"死":
|
|
523
523
|
- 空闲后唯一驱动是心跳(`activityTimeoutMs` 唤醒一个常驻、CHECKPOINT 提示词偏向"是否要停止"),**缺乏推进力**;
|
|
524
|
-
- **唤醒失败会永久停死**(`scheduleNext` 在心跳分支 `await wakeResident(...)` 后无条件 `return`,而 `wakeResident` 捕获异常后返回 false 但**不重新武装心跳**)→
|
|
524
|
+
- **唤醒失败会永久停死**(`scheduleNext` 在心跳分支 `await wakeResident(...)` 后无条件 `return`,而 `wakeResident` 捕获异常后返回 false 但**不重新武装心跳**)→ 一旦某次唤醒抛异常,心跳不再武装,小组**永不再被唤醒**;
|
|
525
|
+
- **唤醒 API 用错**(历史根因):代码曾调 `subagents.followup(...)`,但 DSH 的 `subagents` 服务**没有** `followup`(那只是 `Agent` 对象的方法),真实运行时每次唤醒抛 `TypeError: is not a function` → 被 catch 后所有后续唤醒全部失败。**已改为** `subagents.sendMessage(root, childId, blocks, {signal})`(服务暴露的续做 API),并保留 `followup` 作为旧宿主兜底。
|
|
525
526
|
- `session.json` 不持久化 `meetingState`(进行中会议只在进程内存),进程重启或会议唤醒失败都可能让会议悬停。
|
|
526
527
|
|
|
527
528
|
### A · 自驱动心跳(修复推进力 + 永不永久停死)
|
|
528
529
|
- `heartbeatPrompt` 改为**自驱动**:不是"是否要停止",而是"**请继续解决这个问题**——读他人库、推进子问题/引理/方法、尝试路线;或向团队发消息(input)、提议任务(propose_task);确实已解决/无路可走才提议开会/声明 solved。默认立场是推进而非停在原地。" 心跳回复 schema 增加 `input`、`propose_task`、`contextPct`,使一轮心跳能产生群聊消息/任务从而带动后续工作。
|
|
530
|
+
- **并行填充**(修复"只有 r1、再只有 r2"串行):`scheduleNext` A 分支不再是"一次只唤醒一个最闲常驻",而是收集所有空闲常驻(按空闲时长从大到小)**一次尽量填满 `maxParallel` 并发预算**——每个 `wakeResident` 只 sendMessage 发送(立即返回,等各自 `subagent/end` 异步回归),因此一轮可同时唤醒多个常驻,真正并行推进。失败的唤醒只跳过该常驻、继续填满预算;唤醒失败则重新武装心跳。
|
|
529
531
|
- **唤醒失败重新武装心跳**:`scheduleNext` 心跳分支、`continueMeetingRound`、`continueVerifyRound` 在 `wakeResident` 失败(返回 false)时调用 `armHeartbeat()`,保证**任何一次唤醒失败都不会让小组永久停住**(会稍后重试其它常驻)。
|
|
532
|
+
- **并发邮箱投递**:`deliverNextMailbox` 一次投递所有当前空闲收件人的消息(受 `maxParallel` 上限),避免 `relayToGroup` 广播到多个常驻时被"一次一个"串行化;忙碌收件人保留其消息队列稍后再投(避免饿死其它常驻)。
|
|
530
533
|
|
|
531
534
|
### B · 停滞自动同步会议(分级保活 B)
|
|
532
535
|
- 新增 `lastProgressAt`(会话状态,随产物/会议/验证/任务/群聊/消息更新,`markProgress()`),并持久化到 `State/session.json`。
|
|
@@ -549,6 +552,9 @@ VibeMath/Projects/<project>/
|
|
|
549
552
|
- 同时:**找不到空闲的未发言/未投票常驻时不再静默 `return`,改为 `armHeartbeat()`**(稍后重查),并在发现"没有空闲者"时也武装心跳,杜绝"一直等待一个永不空闲的常驻"造成的隐性挂起。
|
|
550
553
|
- 分级保活 B 增加 `busy.size===0` 守卫:任何常驻正在工作时不触发,避免预抢占在途轮次。
|
|
551
554
|
|
|
555
|
+
### 会议与验证互斥(不抢占)
|
|
556
|
+
- 并行填充常驻后,一个常驻可能"提议验证(propose_verify)"而另一个同时"提议开会(propose_meeting)"。**会议不得抢占验证**:`startMeeting` 在 `verifyState || pendingVerify` 时改为把会议请求**暂存**(`pendingMeeting`,仅记录 agenda/type/target)并返回 `{deferred:true}`;`scheduleNext` 在所有 meeting/verify/pending 都清空后才恢复该暂存会议。这样统一共识验证(全真/全假)作为"求真"环节不会被会议的协调讨论打断,验证做完后再开会开会协调下一步——两者互斥但都不丢失(暂存会议之后补开)。
|
|
557
|
+
|
|
552
558
|
> 效果:一个卡死/坏掉的会议或验证最多阻塞 `activityTimeoutMs`×2 后自动释放,团队重新回到 A/B 分级保活,**不会永久停死**。仍符合"框架只促成、从不指派任务"(放弃只是终止一个无法推进的会议,把控制权交还团队的自组织循环)。
|
|
553
559
|
>
|
|
554
560
|
> 说明:无法对"某常驻真的拒绝/掉线"的情况达成全体一致时,看门狗会把该对象保留为"未定论/带概率",这是哲学上期望的诚实结果。`session.json` 仍不持久化 `meetingState`(进行中的会议不跨进程恢复),B 的停滞看门狗在重启后仍会触发,因此重启也能自愈。
|