dsh-vibe-math 1.3.9 → 1.4.1

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
@@ -63,6 +63,10 @@
63
63
  > 🔧 **v1.3.8 基于真实测试的诊断修复**:**① 共识验证真正"全体一致"**——`finalizeVerify` 现在要求**全体在册常驻都投了票**才可判"一致",否则辩论或保留为未定论(实测曾出现 2/4 投票却被判"全体一致为真",已修);**② 会议必须全体发言**——`continueMeetingRound` 按"是否已发言"收口,`allSolved`(stop)要求全员发言+全票 true,杜绝缺席成员被带偏;**③ 背景只在首轮讲一次**——`contextBrief` 只在 `brainstormPrompt`(首轮)注入完整版,后续 normal/meeting/verify/CHECKPOINT 用极简当前状态,不再每轮重复长背景(省上下文);**④ 验证目标按提出者精确定位**(`targetOwner` + `findSourceRel` 优先提出者库,避免同名 id 撞车);**⑤ 主代理放权**——persona 明确"让常驻自组织(hands-off)",不注议程/优先级/分工/验证决定,只 read status/report,用户明确要求或明显僵死时才 message/meeting 且只促成不决定。详见 §21。
64
64
  >
65
65
  > 🔧 **v1.3.9 进一步按哲学打磨**:**① 会议议程来源**确认是非 bug(r-1 的 `propose_meeting` 发起);**② 压缩后重申核心规则**——每次压缩触发时在提示开头重申短核心规则(治"压缩遗忘规则"),其余轮仍极简;**③ 验证 `verdict` 明确为 0–1 正确概率**——1=绝对为真/判真、0=绝对为假/判假、0.5=不确定,"全体一致判真(verdict=1)/判假(verdict=0)才算数",未全票留库附平均正确概率(兼容旧字符串);**④ 创建项目不立即启动**——新增 `vibe_v4_configure {project?, problem?, params?}` 只建/配项目与参数(持久化 `State/settings.json`)不唤醒常驻,随后 `vibe_v4_start` 才启动;支持设置**项目名**。详见 §22。
66
+ >
67
+ > 🔧 **v1.4.0 verdict 改为纯概率数值 + 全面审计修复**:**① `verdict` 是纯 0–1 正确概率(程度),不再二分类**——仅当**全体一致给 1(真)或全体一致给 0(假)**才按真/假写入 Verified/,否则只作为概率数值保留、附全组平均正确概率(0.97 不再算"真",属更严格的绝对一致口径);**② 审计修复**:`resume` 补 `loadSettings()`(跨进程不再重置参数)、`configure` 直接写出问题卡、补上缺失的 `/v4 set` 分支、真实 `/compact` 成功后置 `needCompact` 以**重申核心规则**。详见 §23。
68
+ >
69
+ > 🔧 **v1.4.1 自主发明理论 + 模型/工具权限参数 + 压缩重申泄漏修复**:**① 初始提示**告知常驻可(不强迫)**自主构建新的理论框架/工具**——抽象化/一般化出更一般的结构并不断完善、推得定理性质(类比群论/泛函分析的发明),并阐明对原问题的价值;**② 补齐"模型继承 + 工具权限"参数**——`model`/`provider`(空 = 常驻继承主代理的 LLM 路由,此前为声明未用)与 `toolAllow`/`toolDeny`(经 `startContinuable` 的 `toolFilter` 做作用域 `tools.restrict()`,空 = 继承全部工具)真正接入 `spawnResident`;**③ 修复"[核心规则重申]+[CONTEXT COMPACT]"在提示开头重复泄漏**——根因是压缩指令被注入到 meeting/verify 唤醒而其回复从不释放 `needCompact`,标志卡死后每轮重复;现**只对 normal 研究轮注入完整压缩指令**、`needCompact` 只重申一次并立即清位、`postmark` 在**所有分支**统一记账,杜绝泄漏;**④ 会议发言顺序轮换**——不再是 r-1 永远先发言看不到别人,各常驻轮流先发言;**⑤ 解释 HRT 收敛**——三名常驻诚实给出"HRT-4 很可能为假(0.8+,未确立)+ 完整必要筛 + 机制 + 判定方程 + 未决点",无 Verified/、无人 declare solved,run 被外部暂停而非框架强收口,符合"诚实、不编造、框架不强加"哲学。详见 §24。
66
70
 
67
71
  ---
68
72
 
@@ -482,6 +486,23 @@ dsh plugin --profile <你的 profile> add github:ChongCyrus/Vibe-Mathematics
482
486
  | `projectLockTimeoutMs` | 60000 | 项目锁等待超时(同项目同一时刻只允许一个会话调度) |
483
487
  | `methodKeeperPersona` | 空 | 注入方法整理代理提示词开头的人格/要求 |
484
488
 
489
+ ### v4(常驻自组织 · 实验)默认值
490
+
491
+ `vibe_v4_set` 可调(持久化到 `State/settings.json`):
492
+
493
+ | 参数 | 默认 | 说明 |
494
+ |---|---|---|
495
+ | `residentCount` | 4 | 常驻数(可 `vibe_v4_add_member` 增减) |
496
+ | `compactThreshold` | 66 | 常驻上下文占比达此值触发软压缩(自述指令) |
497
+ | `compactAfterRounds` | 8 | 常驻每累计 N 轮(未压缩)触发一次软压缩 |
498
+ | `meetingKeepEvery` | 5 | 每积累 N 个新产物自动触发一次同步会议 |
499
+ | `maxParallel` | 3 | 同时唤醒的常驻上限(框架侧并发闸,非指派) |
500
+ | `activityTimeoutMs` | 120000 | 空闲心跳间隔(超时才触发 CHECKPOINT 唤醒,推动收敛) |
501
+ | `verdictMaxRounds` | 3 | 验证在独立初评后进入辩论的最大轮数 |
502
+ | `provider` / `model` | 空 | **常驻 LLM 路由**(空 = 常驻继承主代理的 provider/model;此前声明未用,v1.4.1 真正接入) |
503
+ | `residentPersona` | 空 | 注入每个常驻提示词开头的人格/要求 |
504
+ | `toolAllow` / `toolDeny` | `[]` | **常驻工具权限**(经 `startContinuable` 的 `toolFilter` 做作用域 `tools.restrict()`;空 = 继承全部工具;⚠️ 空 `allow:[]` 会拒绝一切工具) |
505
+
485
506
  ---
486
507
 
487
508
  ## 📝 断点续跑 & 人工干预(两大硬性需求)
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.9",
4
+ "version": "1.4.1",
5
5
  "type": "module",
6
6
  "main": "installer.js",
7
7
  "exports": {
@@ -36,6 +36,13 @@
36
36
  team genuinely discusses/debates), convenes and records meetings, exposes a shared task
37
37
  board, and stops the run only when the whole team agrees the problem is solved.
38
38
 
39
+ At brainstorm, residents are told they MAY (but are never forced to) autonomously build a
40
+ NEW general theory/framework/tool — by abstracting/generalising a structure (like inventing
41
+ group theory to solve polynomial equations, or building functional analysis as a general
42
+ framework). If they do, they must state its value to the original problem and may refine /
43
+ generalise it over time; such artifacts go in their Methods/<resident>/ library. This is an
44
+ encouragement, not an assignment.
45
+
39
46
  **YOUR ROLE — LET THEM SELF-ORGANIZE (hands-off):** you are NOT a moderator/coordinator.
40
47
  Do NOT inject agendas, priorities, division-of-labor, or verification decisions, and do
41
48
  NOT direct the residents' work. After `vibe_v4_start`, stay passive: read `vibe_v4_status`
@@ -54,7 +61,7 @@
54
61
  Main controls (recommended flow: configure FIRST, then start):
55
62
  - vibe_v4_configure {project?, problem?, params?} — create/configure the project (name, problem, params) WITHOUT starting a run; set everything here first.
56
63
  - vibe_v4_start {problem?, residentCount?, seedDirections?} — begin the run (spawn residents, brainstorm). If problem was configured, omit it.
57
- - vibe_v4_set {residentCount, compactThreshold, compactAfterRounds, meetingKeepEvery, maxParallel, activityTimeoutMs, verdictMaxRounds} — tune params (persisted to the settings file).
64
+ - vibe_v4_set {residentCount, compactThreshold, compactAfterRounds, meetingKeepEvery, maxParallel, activityTimeoutMs, verdictMaxRounds, provider, model, residentPersona, toolAllow, toolDeny} — tune params (persisted to the settings file). provider/model override the residents' LLM route (empty = they inherit YOUR model/provider); toolAllow/toolDeny are per-resident tool permissions (empty = they inherit all tools).
58
65
  - vibe_v4_resume / vibe_v4_pause / vibe_v4_abort / vibe_v4_status / vibe_v4_report.
59
66
  - vibe_v4_message {to|all, content} — inject a message to a resident (human/assistant intervention).
60
67
  - vibe_v4_meeting {agenda} — force a meeting.
@@ -32,7 +32,13 @@ export function apply(ctx) {
32
32
  residentCount: 4, compactThreshold: 66, compactAfterRounds: 8,
33
33
  maxParallel: 3, activityTimeoutMs: 120000, verdictMaxRounds: 3,
34
34
  meetingKeepEvery: 5, // 每积累 N 个新产物自动触发一次同步会议
35
+ // model/provider inheritance: '' = the resident inherits the parent (main assistant)
36
+ // route (provider + model). Set them to override the resident's LLM backend/model.
35
37
  provider: '', model: '', residentPersona: '',
38
+ // tool permissions: an allow/deny list of tool names applied via startContinuable's
39
+ // toolFilter (scoped tools.restrict() in the child). Empty = inherit all tools.
40
+ // CAUTION: only set one of these; an empty allow:[] would deny EVERY tool.
41
+ toolAllow: [], toolDeny: [],
36
42
  }
37
43
  let params = Object.assign({}, DEFAULT_PARAMS)
38
44
  let running = false, autoDone = false, phase = 'idle'
@@ -59,6 +65,19 @@ export function apply(ctx) {
59
65
  function logActivity(event,detail){ activityLog.push({at:now(),event,detail:String(detail||'')}); if(activityLog.length>activityLogCap) activityLog.shift() }
60
66
  function logDecision(kind,detail){ decisions.push({at:now(),kind,detail:String(detail||'')}) }
61
67
  function pickProvider(){ try { const n=subagents.list?subagents.list():[]; if(n.indexOf('spawn')!==-1) return 'spawn'; if(n.indexOf('fork')!==-1) return 'fork' } catch(e){} return 'spawn' }
68
+ // Per-resident model/provider inheritance: when params.provider / params.model are set,
69
+ // the resident uses that exact route; when left '' the resident inherits the parent's
70
+ // (main assistant) route — the documented DSH default (resolveChildAgentOptions merges
71
+ // requested over parent). No override is applied for empty values.
72
+ function residentAgentOptions(){ const ao={}; if(params.provider) ao.provider=params.provider; if(params.model) ao.model=params.model; return ao }
73
+ // Tool permission (scoped toolFilter). Only emit a filter when allow or deny has entries;
74
+ // an empty object is rejected by DSH ("must declare allow and/or deny").
75
+ function residentToolFilter(){
76
+ const allow=Array.isArray(params.toolAllow)?params.toolAllow.filter(x=>String(x).trim()):[]
77
+ const deny=Array.isArray(params.toolDeny)?params.toolDeny.filter(x=>String(x).trim()):[]
78
+ if(allow.length===0 && deny.length===0) return undefined
79
+ const f={}; if(allow.length) f.allow=allow; if(deny.length) f.deny=deny; return f
80
+ }
62
81
  function makeSignal(ms){ return AbortSignal.timeout(ms||30000) }
63
82
  function workspaceRoot(){ try { if(rootAgent&&rootAgent.session&&rootAgent.session.header&&rootAgent.session.header.cwd) return rootAgent.session.header.cwd } catch(e){} if(sandboxPolicy&&sandboxPolicy.workspaceRoot) return sandboxPolicy.workspaceRoot; return '.' }
64
83
  function vibeRoot(){ return (workspaceRoot()+'/VibeMath').replace(/\\/g,'/') }
@@ -150,6 +169,12 @@ export function apply(ctx) {
150
169
  s.push('### 可用工具')
151
170
  s.push(toolList())
152
171
  s.push('')
172
+ if(level==='full'){
173
+ s.push('### 可自主发明理论/工具(鼓励,但不强迫)')
174
+ s.push('请注意:你可以(但**不强迫**,完全视实际需要而定)尝试自主构建新的理论框架或工具——例如对某种系统做抽象化、一般化,抽离/推广出更一般的结构或理论框架;然后不断完善这个理论框架,在该框架下推得各种定理、性质、结论,以利于该框架下问题的解决。这就像为解决方程问题发明了群论、为分析需要建立了泛函分析框架——它比单纯解决当前问题更有学术价值,因为你直接得到了一类更普遍的方法/理论体系。')
175
+ s.push('若你发明了这样的理论/工具,请**阐明它对原问题的用处、价值**;后续可根据需要不断**完善、一般化、推广**它。把这类成果记入你的 Methods/<你>/ 库。')
176
+ s.push('')
177
+ }
153
178
  s.push('### 规则')
154
179
  s.push('- 只有 Verified/(或卡片标"已验证·真/假")算已确立;其余都是你的实验性工作,请区分"猜想/已知"。')
155
180
  s.push('- 验证必须**全组一致**(全真或全假);你只信全票结果。未全票的对象留在库里带概率。')
@@ -194,11 +219,11 @@ export function apply(ctx) {
194
219
  +'{"input":"<your real contribution to this discussion>","propose_task":"<task title or null>","task_desc":"...","claim_task":"<task id or null>","propose_verify":"<id or null>","voteSolved":true}'
195
220
  }
196
221
  function verifyPrompt(r, vs){
197
- const others=Object.entries(vs.verdicts).map(([k,v])=>'- '+k+': '+(v.verdict==='TRUE'?'判真':v.verdict==='FALSE'?'判假':'不确定')+' (正确概率 '+String(v.prob!=null?Number(v.prob).toFixed(2):0.5)+') '+v.reason).join('\n')
222
+ const others=Object.entries(vs.verdicts).map(([k,v])=>'- '+k+': 正确概率 '+String(v.prob!=null?Number(v.prob).toFixed(2):0.5)+' '+v.reason).join('\n')
198
223
  return (params.residentPersona?params.residentPersona+'\n':'')
199
224
  +'Resident '+r.rId+' — 团队验证。 The group is verifying object '+vs.targetId+'('+vs.targetType+',提出者 '+vs.targetOwner+')。\n'
200
- +'请给出你对「该对象为真」的正确概率 `verdict`,取值范围 **0–1**:**1 = 绝对为真(判真)**, **0 = 绝对为假(判假)**, **0.5 = 完全不确定**, 介于其间按倾向。\n'
201
- +'只有**全体常驻一致判真(正确概率 verdict 为 1)或一致判假(正确概率 verdict 为 0)**才算数;否则该对象留库并附上全组平均正确概率(不写入 Verified/)。\n'
225
+ +'请给出你对「该对象为真」的**正确概率 `verdict`**,仅一个 0–1 数值:**1 = 绝对为真,0 = 绝对为假,0.5 = 完全不确定,其余为介于其间的程度**(不要给 TRUE/FALSE,就给一个数值)。\n'
226
+ +'判定规则:仅当**全体常驻一致给 1(都认为是真)或一致给 0(都认为是假)**,才按「真/假」写入 Verified/;否则**只作为概率数值(一种程度)保留在库中**,附全组平均正确概率,不写成真/假。\n'
202
227
  +'请给出你**诚实独立的判断**'
203
228
  +(vs.stage==='debate'?',并参考他人意见:\n':'。\n')
204
229
  +(vs.stage==='debate'&&others?('### 他人意见(已转发给你)\n'+others+'\n'):'')
@@ -210,7 +235,8 @@ export function apply(ctx) {
210
235
  let residentSeq = 0
211
236
  function newResident(dir){ const rId='r-'+(++residentSeq); return {rId,childId:'',direction:dir||'',status:'brainstorm',rounds:0,roundsSinceCompact:0,lastActiveAt:now(),insight:'',contextPct:0,contextSeed:'',needCompact:false} }
212
237
  async function spawnResident(r){
213
- const started=await subagents.startContinuable({provider:pickProvider(),label:r.rId,request:{prompt:[textBlock(brainstormPrompt(r))],parent:rootAgent,agentOptions:{}},signal:makeSignal(params.activityTimeoutMs||60000)})
238
+ const ao=residentAgentOptions(); const tf=residentToolFilter()
239
+ const started=await subagents.startContinuable({provider:pickProvider(),label:r.rId,request:{prompt:[textBlock(brainstormPrompt(r))],parent:rootAgent,agentOptions:ao,...(tf?{toolFilter:tf}:{})},signal:makeSignal(params.activityTimeoutMs||60000)})
214
240
  r.childId=started.childId; r.status='brainstorm'; r.lastActiveAt=now()
215
241
  childOwner.set(started.childId,sessionId); busy.add(r.rId); wakeKind.set(r.rId,'normal'); currentResident=r.rId
216
242
  residents.set(r.rId,r); await saveAll(); logActivity('spawn',r.rId+' ('+(r.direction||'brainstorm')+')')
@@ -220,16 +246,28 @@ export function apply(ctx) {
220
246
  clearHeartbeat()
221
247
  busy.add(r.rId); wakeKind.set(r.rId,kind||'normal'); currentResident=r.rId
222
248
  r.lastActiveAt=now(); r.rounds+=1; r.roundsSinceCompact+=1
223
- // context / /compact: if the resident reports high context (or reached the round proxy),
224
- // prepend a compact directive it condenses its working state to a self-summary that the
225
- // framework uses as the next context seed (equivalent to /compact's "consolidate & forget").
249
+ // Context compaction has TWO distinct needs. Confusing them is the bug that made
250
+ // '[核心规则重申]+[CONTEXT COMPACT]' repeat at the start of nearly every prompt:
251
+ // (a) r.needCompact (set by a REAL /compact) => the resident's rules may be blurred, so
252
+ // re-anchor the short core rules on the next wake of ANY kind, then CLEAR the flag.
253
+ // (Short recap only; no self-summary directive — the real compact already condensed.)
254
+ // (b) soft-compact trigger (contextPct>=threshold OR roundsSinceCompact>=afterRounds) =>
255
+ // the resident's context genuinely grew; ask it to self-summary. ONLY on a normal
256
+ // research round (kind==='normal'): a meeting/verify reply has no contextPct/compacted
257
+ // fields, so a directive injected there is never acknowledged and would repeat forever.
226
258
  let prompt = promptText
227
- if(r.needCompact || (Number(r.contextPct)>=Number(params.compactThreshold)) || (r.roundsSinceCompact>=Number(params.compactAfterRounds))){
259
+ const isNormal = (kind||'normal')==='normal'
260
+ const wantSoft = isNormal && (Number(r.contextPct)>=Number(params.compactThreshold) || Number(r.roundsSinceCompact)>=Number(params.compactAfterRounds))
261
+ const wantReanchor = r.needCompact
262
+ if(wantSoft){
228
263
  prompt = coreRulesBrief() + '\n' +
229
264
  '[CONTEXT COMPACT — your conversation is at/near the limit. Do NOT re-derive history.\n' +
230
265
  'Condense your current working state into ONE tight self-summary (findings so far, active direction, key artifacts you recorded, next concrete steps, open questions), then answer this round in the normal JSON format as usual.\n' +
231
266
  'Set "contextPct": 15 (your post-compact usage) and "compacted": true in the reply so the framework records the condensed seed.]\n\n' + promptText
232
267
  r.needCompact = true
268
+ } else if(wantReanchor){
269
+ prompt = coreRulesBrief() + '\n' + prompt
270
+ r.needCompact = false
233
271
  }
234
272
  try { await subagents.followup(rootAgent,r.childId,[textBlock(prompt)],{source:{kind:'user'},signal:makeSignal(params.activityTimeoutMs||60000)}); return true }
235
273
  catch(e){ console.error('vibe-v4 wake '+r.rId+' failed: '+String((e&&e.message)||e)); busy.delete(r.rId); return false }
@@ -261,6 +299,18 @@ export function apply(ctx) {
261
299
  async function saveTaskboard(){ await writeJson('State/taskboard.json',taskboard); await writeTaskboard() }
262
300
  function listTasks(){ return taskboard.filter(t=>t.status!=='done') }
263
301
  async function reportContext(rId,pct){ const r=residents.get(rId); if(r){ r.contextPct=clPct(pct); if(Number(pct)<30) r.needCompact=false; } return {ok:true} }
302
+ // Apply context/compact bookkeeping from a resident's reply, so the flag can clear even when
303
+ // the reply came through a meeting/verify branch (defensive) as well as the normal branch.
304
+ function postmark(r, parsed){
305
+ if(typeof parsed.contextPct==='number') r.contextPct=clPct(parsed.contextPct)
306
+ if(parsed.compacted===true || (r.needCompact && parsed.summary)){
307
+ r.contextSeed=String(parsed.summary||r.contextSeed||'')
308
+ r.contextPct=Math.min(r.contextPct||15,25)
309
+ r.roundsSinceCompact=0
310
+ r.needCompact=false
311
+ logActivity('compact', r.rId+' consolidated context')
312
+ }
313
+ }
264
314
 
265
315
  // ---- messaging ----
266
316
  async function postMessage(from,to,content){
@@ -293,15 +343,21 @@ export function apply(ctx) {
293
343
  async function startMeeting(agenda,type,targetId){
294
344
  if(meetingState) return {ok:false,message:'meeting already in progress'}
295
345
  clearHeartbeat()
296
- meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[]}
346
+ const ids=Array.from(residents.keys())
347
+ // Rotate the per-meeting speaking order so the SAME resident isn't always the "first speaker
348
+ // who sees no one else's contribution"; a real discussion lets each member lead sometimes.
349
+ const rot=Math.floor(Math.random()*Math.max(1,ids.length))
350
+ const order=ids.slice(rot).concat(ids.slice(0,rot))
351
+ meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[],order}
297
352
  logActivity('meeting','start: '+agenda); await saveAll(); await scheduleNext(); return {ok:true,id:meetingState.id}
298
353
  }
299
354
  async function continueMeetingRound(){
300
355
  if(!meetingState) return
301
356
  const ids=Array.from(residents.keys()); const allSpoke=ids.every(id=>meetingState.inputs[id]!==undefined)
302
357
  if(allSpoke){ await finalizeMeeting(); return }
303
- // only wake IDLE un-spoken residents; in-flight ones re-trigger this on end.
304
- const id=ids.find(x=>meetingState.inputs[x]===undefined && !busy.has(x)); if(!id) return
358
+ // only wake IDLE un-spoken residents (rotated order); in-flight ones re-trigger this on end.
359
+ const order=meetingState.order||ids
360
+ const id=order.find(x=>meetingState.inputs[x]===undefined && !busy.has(x)); if(!id) return
305
361
  const r=residents.get(id)
306
362
  await wakeResident(r, meetingPrompt(r,meetingState), 'meeting'); await saveAll()
307
363
  }
@@ -345,8 +401,9 @@ export function apply(ctx) {
345
401
  const vs=verifyState; const expected=Array.from(residents.keys()).length
346
402
  const allVoted = expected>0 && Object.keys(vs.verdicts).length>=expected
347
403
  const vals=Object.values(vs.verdicts)
348
- const allTrue = allVoted && vals.every(x=>x.verdict==='TRUE')
349
- const allFalse = allVoted && vals.every(x=>x.verdict==='FALSE')
404
+ // verdict is a PURE 0-1 probability; only ALL=1 (true) or ALL=0 (false) is a binary verdict.
405
+ const allTrue = allVoted && vals.every(x=>Number(x.prob)===1)
406
+ const allFalse = allVoted && vals.every(x=>Number(x.prob)===0)
350
407
  if(allTrue||allFalse){ await closeVerify(vs,allTrue); return }
351
408
  if(vs.round+1<params.verdictMaxRounds){ vs.stage='debate'; vs.round+=1; vs.asked=[]; logActivity('verify',vs.targetId+' round '+vs.round+' → debate'); await saveAll(); await scheduleNext(); return }
352
409
  const avg=vals.length? vals.reduce((a,x)=>a+(x.prob!=null?x.prob:0.5),0)/vals.length : 0.5
@@ -363,7 +420,7 @@ export function apply(ctx) {
363
420
  }
364
421
  async function writeDebateDoc(vs,done,val){
365
422
  const lines=['# 验证辩论|'+vs.targetId+'('+vs.targetType+')|'+fmtTime(),'',(done?('**结论**:'+(val===1?'全体一致为真':'全体一致为假')):('**未达成全体一致**,平均概率 '+val.toFixed(2))),'','## 各常驻意见']
366
- for(const [k,v] of Object.entries(vs.verdicts)){ lines.push('### '+k+'|'+(v.verdict==='TRUE'?'判真':v.verdict==='FALSE'?'判假':'不确定')+'|正确概率 '+(v.prob!=null?Number(v.prob).toFixed(2):'0.50')); lines.push(v.reason||''); lines.push('') }
423
+ for(const [k,v] of Object.entries(vs.verdicts)){ lines.push('### '+k+'|正确概率 '+(v.prob!=null?Number(v.prob).toFixed(2):'0.50')); lines.push(v.reason||''); lines.push('') }
367
424
  await writeText('Shared/debates/'+vs.targetId+'.md', lines.join('\n'))
368
425
  }
369
426
  async function writeVerifiedCard(vs,isTrue){
@@ -432,7 +489,9 @@ export function apply(ctx) {
432
489
  const signal = makeSignal(params.activityTimeoutMs||60000)
433
490
  const result = await compaction.compactIfNeeded(agent, 'pressure', signal)
434
491
  if(result && (result.shadowedSeqs||[]).length>0){
435
- r.roundsSinceCompact=0; r.needCompact=false; r.contextPct=Math.min(r.contextPct||15,25)
492
+ // the resident's real session was compacted → its context is now a summary.
493
+ // Flag needCompact so the NEXT wake re-anchors the core rules (they may have been blurred).
494
+ r.roundsSinceCompact=0; r.needCompact=true; r.contextPct=Math.min(r.contextPct||15,25)
436
495
  logActivity('compact', r.rId+' real /compact (shadowed '+result.shadowedSeqs.length+' items, ~'+String(result.shadowedTokenCount||0)+' tokens)')
437
496
  }
438
497
  } catch(e){ /* real compaction unavailable/failed; the soft directive already covers it */ }
@@ -487,6 +546,7 @@ export function apply(ctx) {
487
546
  realCompact(r).catch(()=>{}) // best-effort real DSH /compact of this resident while idle
488
547
  const output=blocksToText(info&&info.lastAssistantMessage)
489
548
  const parsed=parseReply(output)
549
+ postmark(r, parsed) // context/compact bookkeeping, regardless of wake kind (clears any leak)
490
550
  const kind=wakeKind.get(r.rId)||'normal'
491
551
  if(kind==='meeting' && meetingState){
492
552
  meetingState.inputs[r.rId]={input:parsed.input||parsed.summary||'',voteSolved:typeof parsed.voteSolved==='boolean'?parsed.voteSolved:null,propose_verify:parsed.propose_verify||null,propose_task:parsed.propose_task||null,task_desc:parsed.task_desc||'',claim_task:parsed.claim_task||null}
@@ -502,16 +562,13 @@ export function apply(ctx) {
502
562
  else if(/^TRUE$/i.test(String(v.verdict))){ p=1 }
503
563
  else if(/^FALSE$/i.test(String(v.verdict))){ p=0 }
504
564
  else { p=clamp01(Number(v.confidence)) }
505
- const verdict= p>0.5?'TRUE': p<0.5?'FALSE':'UNCERTAIN'
506
- verifyState.verdicts[r.rId]={verdict,prob:p,confidence:p,reason:String(v.reason||parsed.summary||'')}
565
+ // verdict is a PURE 0-1 probability (a degree); no binary TRUE/FALSE classification.
566
+ verifyState.verdicts[r.rId]={prob:p,confidence:p,reason:String(v.reason||parsed.summary||'')}
507
567
  await saveAll(); await continueVerifyRound(); return
508
568
  }
509
569
  // normal turn
510
570
  if(r.status==='brainstorm'){ r.insight=parsed.summary||output; r.status='active' }
511
571
  if(typeof parsed.solved==='boolean') reports.push({rId:r.rId,solved:parsed.solved,summary:parsed.summary||'',at:now()})
512
- // context / compact: record the condensed seed + post-compact usage, clear the flag
513
- if(typeof parsed.contextPct==='number'){ r.contextPct=clPct(parsed.contextPct) }
514
- if(parsed.compacted===true || (r.needCompact && parsed.summary)){ r.contextSeed=String(parsed.summary||''); r.contextPct=Math.min(r.contextPct||15,25); r.roundsSinceCompact=0; r.needCompact=false; logActivity('compact',r.rId+' consolidated context') }
515
572
  if(parsed.propose_verify) pendingVerify={targetId:parsed.propose_verify,targetType:guessTargetType(parsed.propose_verify),proposer:r.rId,at:now()}
516
573
  // group-conversation relay: the resident may choose to speak to the whole team (input) —
517
574
  // forward it to the others so this is a real discussion group, not private monologues.
@@ -542,7 +599,7 @@ export function apply(ctx) {
542
599
  await saveAll(); return {ok:true,message:'v4 started: '+params.residentCount+' resident(s) brainstorming',project:currentProject}
543
600
  }
544
601
  async function resume(){
545
- currentProject=await readCurrentProject(); await ensureDirs(); await loadAll()
602
+ currentProject=await readCurrentProject(); await ensureDirs(); await loadAll(); await loadSettings()
546
603
  if(phase==='idle' && !running && residents.size===0) return {ok:false,message:'nothing to resume'}
547
604
  // If the persisted State came from a DIFFERENT process (crash/restart), the saved
548
605
  // childIds are stale; clear them so residents re-spawn (their libraries persist on
@@ -556,14 +613,22 @@ export function apply(ctx) {
556
613
  }
557
614
  function status(){ return { ok:true, running, phase, autoDone, project:currentProject, residentCount:residents.size,
558
615
  residents:listResidents(), busy:[...busy], taskboard:taskboard.length,
559
- params:['residentCount','compactAfterRounds','compactThreshold','maxParallel','activityTimeoutMs','meetingKeepEvery','verdictMaxRounds'].map(k=>k+'='+params[k]).join(', ') } }
616
+ params:['residentCount','compactAfterRounds','compactThreshold','maxParallel','activityTimeoutMs','meetingKeepEvery','verdictMaxRounds','provider','model','residentPersona','toolAllow','toolDeny'].map(k=>k+'='+(Array.isArray(params[k])?params[k].join(','):params[k])).join(', ') } }
560
617
  function report(){ return { ok:true, running, phase, autoDone, project:currentProject, problem:problemText,
561
618
  residents:listResidents(), taskboard:taskboard.filter(t=>t.status!=='done'),
562
619
  verify: verifyState?{target:verifyState.targetId,stage:verifyState.stage}:null, meetings:meetings.length,
563
620
  recentActivity: activityLog.slice(-8) } }
564
621
  async function addMember(direction){ const r=newResident(direction||''); await spawnResident(r); return {ok:true,id:r.rId,direction:r.direction} }
565
622
  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} }
566
- function setParams(upd){ for(const k of Object.keys(upd||{})){ if(k in params) params[k]=upd[k] } saveSettings().catch(()=>{}); return {ok:true} }
623
+ // Normalize one parameter value to its intended type so a string from /v4 set or configure
624
+ // becomes the right number/array. Keeps settings.json clean regardless of how it was set.
625
+ function normalizeParam(k, v){
626
+ const INT_KEYS=['residentCount','compactThreshold','compactAfterRounds','maxParallel','activityTimeoutMs','verdictMaxRounds','meetingKeepEvery']
627
+ if(INT_KEYS.includes(k)){ const n=Number(v); return Number.isFinite(n)?n:v }
628
+ if(k==='toolAllow'||k==='toolDeny'){ if(Array.isArray(v)) return v.map(x=>String(x).trim()).filter(Boolean); if(typeof v==='string') return v.split(',').map(x=>x.trim()).filter(Boolean); return [] }
629
+ return v
630
+ }
631
+ function setParams(upd){ for(const k of Object.keys(upd||{})){ if(k in params) params[k]=normalizeParam(k, upd[k]) } saveSettings().catch(()=>{}); return {ok:true} }
567
632
  // ---- create / configure (no auto-start) + settings-file persistence ----
568
633
  async function loadSettings(){ const s=await readJson('State/settings.json'); if(s&&typeof s==='object'){ for(const k of Object.keys(s)){ if(k in params) params[k]=s[k] } } }
569
634
  async function saveSettings(){ await writeJson('State/settings.json', params) }
@@ -572,8 +637,11 @@ export function apply(ctx) {
572
637
  async function configure(cfg){
573
638
  if(cfg && cfg.project && String(cfg.project).trim()) currentProject=String(cfg.project).trim()
574
639
  if(cfg && cfg.problem) problemText=String(cfg.problem)
575
- if(cfg && cfg.params && typeof cfg.params==='object') for(const k of Object.keys(cfg.params)) if(k in params) params[k]=cfg.params[k]
576
- await writeCurrentProject(); await ensureDirs(); await saveSettings(); await saveAll()
640
+ if(cfg && cfg.params && typeof cfg.params==='object') setParams(cfg.params)
641
+ await writeCurrentProject(); await ensureDirs(); await saveSettings()
642
+ // create the problem card so the project is complete BEFORE the run starts
643
+ if(problemText){ const pid=slugify(problemText.slice(0,40))||'problem'; await writeText('Problems/'+pid+'.md','# 问题|'+pid+'\n- ID: '+pid+'\n- 类型: 问题\n- 状态: 求解中\n- 优先级: 1\n- 依赖: []\n\n## 陈述\n'+problemText+'\n') }
644
+ await saveAll()
577
645
  return {ok:true,project:currentProject,problem:problemText?problemText.slice(0,60):'',params:Object.keys(params).map(k=>k+'='+params[k]).join(', ')}
578
646
  }
579
647
  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'} }
@@ -618,7 +686,10 @@ export function apply(ctx) {
618
686
  registerTool('vibe_v4_list_members','List residents.',objParams({}),(s)=>({ok:true,residents:s.listResidents()}))
619
687
  registerTool('vibe_v4_add_member','Add a resident.',objParams({direction:{type:'string'}}),(s,a)=>s.addMember(a.direction))
620
688
  registerTool('vibe_v4_remove_member','Close a resident.',objParams({id:{type:'string'}},['id']),(s,a)=>s.removeMember(a.id))
621
- registerTool('vibe_v4_set','Set V4 parameters.',objParams({residentCount:{type:'integer'},compactAfterRounds:{type:'integer'},compactThreshold:{type:'integer'},meetingKeepEvery:{type:'integer'},maxParallel:{type:'integer'},activityTimeoutMs:{type:'integer'},verdictMaxRounds:{type:'integer'}}),(s,a)=>{ s.setParams(a); return {ok:true} })
689
+ // model/provider inheritance: set model/provider to override the residents' LLM route (''=inherit
690
+ // the main assistant's route). toolAllow/toolDeny are per-resident tool permissions (scoped
691
+ // restrict). residentPersona prepends a persona line to every resident prompt.
692
+ registerTool('vibe_v4_set','Set V4 parameters. model/provider override resident LLM route (empty=inherit main); toolAllow/toolDeny restrict resident tools (arrays of tool names); residentPersona adds a persona line.',objParams({residentCount:{type:'integer'},compactAfterRounds:{type:'integer'},compactThreshold:{type:'integer'},meetingKeepEvery:{type:'integer'},maxParallel:{type:'integer'},activityTimeoutMs:{type:'integer'},verdictMaxRounds:{type:'integer'},provider:{type:'string'},model:{type:'string'},residentPersona:{type:'string'},toolAllow:{type:'array',items:{type:'string'}},toolDeny:{type:'array',items:{type:'string'}}}),(s,a)=>{ s.setParams(a); return {ok:true} })
622
693
  // resident-facing tools: route to the CALLING resident (exec.agent.id === childId);
623
694
  // fall back to the last-woken resident when called by the host/assistant.
624
695
  registerTool('vibe_v4_send_message','(resident) Send a message to another resident.',objParams({to:{type:'string'},content:{type:'string'}},['to','content']),(s,a,x)=>s.postMessage(s.residentIdOf(x),a.to,a.content))
@@ -656,6 +727,7 @@ export function apply(ctx) {
656
727
  else if(cmd==='members') r={ok:true,residents:s.listResidents()}
657
728
  else if(cmd==='add') r=await s.addMember(rest.join(' '))
658
729
  else if(cmd==='remove') r=await s.removeMember(rest[0]||'')
730
+ else if(cmd==='set'){ const upd={}; for(const tok of rest){ const eq=tok.indexOf('='); if(eq>0){ const k=tok.slice(0,eq); const rv=tok.slice(eq+1); const n=Number(rv); upd[k]=Number.isFinite(n)?n:rv } } r=s.setParams(upd) }
659
731
  else r={ok:false,usage:'configure|start|resume|pause|abort|status|report|message|meeting|members|add|remove|set'}
660
732
  return {kind:'success',text:JSON.stringify(r,null,2)}
661
733
  },
@@ -462,5 +462,59 @@ VibeMath/Projects/<project>/
462
462
  ### 4. 创建项目不再立即启动(先配置后启动)+ settings 文件 + 项目名
463
463
  新增 **`vibe_v4_configure {project?, problem?, params?}`**:**只创建/配置项目(名称、问题、参数),不唤醒任何常驻**;参数持久化到 `State/settings.json`。之后 **`vibe_v4_start {problem?, residentCount?, seedDirections?}`** 才真正启动(若已配置问题可省略)。这样"先设好参数再启动",不再一创建就着急跑。`/v4 configure` 子命令 + 主代理 persona 的 `Main controls` 已同步(`vibe_v4_set` 也持久化到 settings 文件);支持设置**项目名**(`configure.project`)。
464
464
 
465
+ ---
466
+
467
+ ## 23. verdict 改为纯概率数值 + 全面审计修复(v1.4.0)
468
+
469
+ ### 1. `verdict` 是**纯 0–1 概率数值**(不再二分类)
470
+ 按用户要求,`verdict` 现在**只是一个 0–1 的正确概率**(程度),框架**不再把它分段映射成 TRUE/FALSE/不确定**。判定规则:
471
+ - **仅当全体常驻一致给 `1`(都认为是真)** → 按"真"写入 Verified/;
472
+ - **仅当全体常驻一致给 `0`(都认为是假)** → 按"假"写入 Verified/;
473
+ - **否则**:只作为**概率数值(一种程度)保留在库中**,附全组平均正确概率(`- 概率:` 写该平均值),**不写成真/假**。
474
+
475
+ 相应地:验证提示改为"请给出对该对象为真的正确概率 verdict(一个 0–1 数值,不要给 TRUE/FALSE)";解析/存储/辩论录/源卡回写全用**数值概率**。向后兼容旧 `"TRUE"/"FALSE"`(解析为 1/0)。**注意**:这也意味着"0.97(很高但非 1)"不再算"真",会保留为概率 0.97——这是更严格的"绝对一致"口径。
476
+
477
+ ### 2. 全面审计修复
478
+ - **`resume` 补上 `loadSettings()`**:否则跨进程 resume 后参数会退回默认(settings 不加载)。
479
+ - **`configure` 现在直接写出问题卡**(`Problems/<id>.md`),使"创建项目"在启动前就完整;`start` 仍会幂等重写。
480
+ - **补上缺失的 `/v4 set` 分支**:此前 usage 列了 `set` 但命令处理器没实现,`/v4 set` 会掉到 usage;现支持 `key=value` 解析并 `setParams`(也持久化到 settings)。
481
+ - **真实 `/compact` 后重申规则**:`realCompact` 成功压缩常驻真实会话后,置 `needCompact=true`,使下一次唤醒**重申核心规则**(与软压缩一致),避免真实压缩后常驻淡忘规则。
482
+
483
+ ---
484
+
485
+ ## 24. 自主发明理论 / 模型与工具权限 / 压缩重申泄漏修复(v1.4.1)
486
+
487
+ 针对一次真实 3 常驻 run(HRT 4 猜想)的深入审计 + 用户三条新增要求:
488
+
489
+ ### 1. 初始提示告知"可自主构建新的理论框架/工具"(v1.4.1-①)
490
+ 在 `contextBrief(r,'full')`(**仅首轮 brainstorm**,符合"背景只讲一次")新增一节 **"可自主发明理论/工具(鼓励,但不强迫)"**:
491
+ - 常驻可(但**不强迫**、完全视实际需要)**自主尝试构建新的理论框架或工具**——对某种系统做**抽象化、一般化**,抽离/推广出更一般的结构或理论框架;然后**不断完善**它,在该框架下推得各种**定理、性质、结论**,以利于该框架下问题的解决。
492
+ - 类比:为解决方程问题发明了**群论**、为分析需要建立了**泛函分析**框架——这比单纯解决当前问题更有价值,因为直接得到了一类**更普遍的方法/理论体系**。
493
+ - 若发明了这样的理论/工具,请**阐明它对原问题的用处、价值**;后续可**不断完善、一般化、推广**它,并把这类成果记入 `Methods/<你>/` 库。
494
+ - 这是**鼓励,不是指派**;不写固定模板、不强制"每轮必须发明"。
495
+
496
+ ### 2. 补齐"模型继承关系 + 工具权限"参数(v1.4.1-②)
497
+ 此前 `provider`/`model` 参数在 `DEFAULT_PARAMS` 里**声明但从未被使用**(死参数),且**没有任何工具权限参数**。现补齐并真正落地:
498
+ - **模型继承**:默认**常驻继承主代理(main assistant)的 provider/model 路由**(DSH `resolveChildAgentOptions` 把请求项并到父路由之上)。通过 `params.provider`/`params.model` 可**覆盖**常驻的 LLM 后端/模型(空=继承)。
499
+ - **工具权限**:新增 `params.toolAllow` / `params.toolDeny`,经 `startContinuable` 的 `toolFilter` 做**作用域 `tools.restrict()`**(被点名的工具从常驻提示里消失且拒绝执行)。默认两者为空 → **常驻继承全部工具**(含 fs 与各 `vibe_v4_*`);只有显式配置才收权。⚠️ 注意"deny-all 陷阱":**空 `allow:[]` 会拒绝一切工具**,因此只有当 allow 或 deny 至少有一项时才会发出 filter。
500
+ - **接线**:`spawnResident` 现把 `residentAgentOptions()`(provider/model)与 `residentToolFilter()`(toolAllow/toolDeny)传入 `startContinuable`;`setParams` 做类型归一(整数 / 逗号分隔的数组);`vibe_v4_set` schema 与 `status()` 参数串也已加这些新参数。
501
+
502
+ ### 3. 修复"[核心规则重申]+[CONTEXT COMPACT]"在提示开头重复泄漏(v1.4.1-③)
503
+ 这是真实 run 的核心 bug。**根因**(从 `State/residents.json` 证实):r-1 的 `needCompact:true` 常年不释放、`roundsSinceCompact:10`(>8)——框架在**所有**唤醒(含 meeting/verify)里都根据"`needCompact || contextPct>=阈值 || rounds>=afterRounds`"注入压缩指令;而 meeting/verify 分支收到回复后**提前 return,从不处理 `contextPct/compacted/needCompact`**,于是 `needCompact` 卡死为 true,**每个后续提示都在开头重复"核心规则重申 + CONTEXT COMPACT"**。修复:
504
+ - **只对 normal 研究轮注入完整压缩指令**;meeting/verify/CHECKPOINT 不再注入(它们的回复没有 `compacted/contextPct` 字段,注入了也永远无法被确认 → 无限重复)。
505
+ - **`needCompact`(真实 `/compact` 后)只重申一次短规则并立即清位**(下次任一唤醒即可,清位后不再重复),不再要求再做一次自述。
506
+ - 新增 `postmark(r, parsed)`,在 `onResidentEnd` **所有分支**(含 meeting/verify)开头统一处理 context/compact 记账,杜绝任何情况下的标志泄漏。
507
+ - 这样"规则重申/压缩指令"只出现在**真正发生了压缩的那次唤醒**(软压缩每 `compactAfterRounds` 轮一次、真实 `/compact` 后一次),不再"每轮都重复"。
508
+
509
+ ### 4. 会议发言顺序轮换(v1.4.1-④,改善"第一位发言者看不到别人")
510
+ 真实 run 里 r-1 几乎总是在会议里 **第一个发言**(因 `residents` 按插入序 r-1,r-2,r-3,`continueMeetingRound` 取第一个未发言者),于是它本次会议内**看不到** r-2/r-3 的后续发言。现改为**每次会议随机轮换发言顺序**(`meetingState.order`),让不同常驻轮流先发言,讨论更公平、各成员都能看到别人。
511
+
512
+ ### 5. 关于 HRT run 收敛与"第7轮后看不到别人消息"的解释(审计结论,非代码 bug)
513
+ - **收敛合理且符合哲学**:三名常驻经约 18/24/29 轮与 6 次会议,**独立且一致**地给出诚实结论——"HRT-4 一般形式很可能为假(0.8+,未确立)+ 完整必要筛 + 统一机制 + 判定方程(♯) + 明确标注未决点(显式反例=外部无全文阻塞、generic 证明=HRT 核心困难未证)";**无任何 Verified/ 对象**;**无人 declare solved**(每会 `voteSolved` 均非全 true)。框架没有强行收口(`autoDone=false`),run 是被**外部暂停**(`running:false, phase:active`)。这正是哲学要求的诚实:常驻不编造"已解决",框架也不强加结论。
514
+ - **"第7轮后看不到别人消息/群聊"合理且有两点成因**:① run 后期以**会议为主**(r-1 的后续唤醒几乎都是 Meeting 提示,而非"第 N 轮"研究轮);② **`[群聊]` 群聊转发在会议主导期被积压**——`scheduleNext` 让会议/验证优先于邮箱投递,会议期间 `[群聊]` 只排队不投递,r-1 邮箱里积压了大量未投递的群聊消息(`State/mailboxes.json` 可见)。会议提示里虽会把"他人已发言"转给常驻,但 r-1 作为最常的先发言者,本次会议内看不到后续发言。这是**自组织在"深度协作+会议主导"下的自然表现**;已通过 §24.4 的发言顺序轮换改善,`[群聊]` 积压会在恢复 normal 阶段被正常投递。
515
+
516
+ > 测试:`selfdrive-v4.mjs` 21/21;`e2e-v4-fixes.test.mjs` 36/36(新增 T10 模型/工具权限接线、T11 默认继承、T12 自主发明理论提示、T13 压缩指令不泄漏进 meetings);v3 E2E 100/100、v2 regression 14/14、v2 business 24/24、multisession 25/25 全绿。
517
+
518
+
465
519
 
466
520