dsh-vibe-math 1.3.4 → 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
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
> 🔧 **v1.3.3 审计修复**:非全票验证把平均概率写回源卡(兑现"留库附概率")、方法型验证标 `类型: 方法`(不再误标命题)、同进程 abort→resume 重建常驻、记录命题也触发自动同步会议、唤醒信号改用 `activityTimeoutMs`、`verdictMaxRounds`/`meetingKeepEvery` 可调且展示。详见 §18。
|
|
54
54
|
>
|
|
55
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。
|
|
56
58
|
|
|
57
59
|
---
|
|
58
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.
|
|
4
|
+
"version": "1.3.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "installer.js",
|
|
7
7
|
"exports": {
|
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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,
|
|
@@ -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
|
+
|