自研 local-model-harness 体验记录

格式:日期 | 模型 | 任务一句话 | 结果 | 观察(契约质量/验证是否跑/循环/压缩/误拦)

日期模型任务结果观察
2026-08-07ThinkingCap-27Bbench 首跑 t1(off)PASS 185sgate 拦 2 次;12 回合 13 工具;未调 task_complete 仍过
2026-08-07ThinkingCap-27Bbench 首跑 t2-t5(off)3/4,t3 FAILt3 模型只回 “Key” 零工具调用——thinking=off 下 review 崩溃
2026-08-07ThinkingCap-27Bbench 全量(low)5/5 PASSlow 比 off 快 30-40% 且修复 t3;t3 gate 拦 1 次(越界写入被拦、只读放行)——死锁回归通过

2026-08-07 关键诊断:headless 挂起根因

现象:pi --mode json 无头跑 ThinkingCap 时挂起,turns=0;小模型 Qwen3-0.6B 秒回。

排查链:

  1. curl 直连 8050 一切正常(chat/completions、completions、stream,各种参数均 200)
  2. 代理抓包:pi 请求体完整捕获(105KB、33 工具),用 urllib 原样重放给 oMLX 正常返回——oMLX 无问题
  3. 对比 --print 模式:正常;--mode json + 默认 thinking:挂起
  4. 杀僵尸 pi 进程后重测:默认 thinking 仍挂

根因:defaultThinkingLevel: high + ThinkingCap(推理型模型)→ oMLX 进入 thinking 流(reasoning_content),pi 对 oMLX 的 thinking 流式响应消费挂起(keepalive chunk 后无进展,stopReason=pending)。小模型无 reasoning 所以正常。历史成功会话 019fda4c 恰好是 thinking=off。

二次发现(更致命):thinking=off 虽绕过挂起,但让 ThinkingCap 的 review 类能力崩溃(t3 只回 “Key”)。--thinking low 是最佳档:不挂起 + 保留推理能力 + 更快。bench 驱动已默认 low。

经验:bench 无头跑本地推理模型,thinking 档位是首要变量;代理抓包要先写请求日志再转发(否则挂起时看不到)。

2026-08-07 证据驱动修复:验证节奏死锁(bench t2)

现象:t2(删除 app)errors=27 全量基线,其中 22 次是 task_verify 被拒,死循环。

根因(机制 vs 模型打架):协议要求”修改后运行验证”,但 verify() 机制强制”状态变更后须有成功的只读调用”。模型删完文件直接 task_verify → 机制拒 → 模型不知道要先跑只读检查 → 死循环。

修复链(5 轮迭代,errors 27→0):

  1. 协议补验证节奏说明(状态变更后先跑只读检查再 task_verify)
  2. 只读 bash 判定放宽:&&/|| 链(每段只读则整体只读)、单 & 后台仍拦截
  3. 2>&1/2>/dev/null 归一化;true/: 白名单
  4. verify() 放宽:一次只读调用可支撑多个 condition(仍要求变更后有只读证据)
  5. ! negation 段、; 链(每段只读)、node test.js/npm test 测试验证白名单

结果:t2 errors 27→0;t4 errors 7→1(耗时 289→109s);t5 errors 1;全 5 任务 PASS。

经验:bench 的价值在暴露”机制与真实模型行为”的错位;本地模型验证惯用法(test && echo、! grep、; 链、node test.js)必须被 harness 识别为只读,否则 harness 会主动制造死循环。v0.2.1 已提交。

2026-08-07 quality-monitor:证据否定 vs 防御价值

证据:扫描 bench 历史 277 个 turn_end——0 次空响应;316 个工具调用中 14 个空参数全部是 task_complete(本就无参数,合理)。ThinkingCap 栈不犯 little-coder 检测的空响应/空参数错。

决策:不照搬 little-coder 的 quality-monitor,但仍实现了薄层版本(约 20 行检测逻辑 + telemetry 计数 + turn_end steer 纠正):

  • 理由 1:防御价值——模型劣化或换更小模型时护栏仍在
  • 理由 2:遥测价值——/local-report 增加 quality 计数,可监控
  • 剥离:repetitive_loop 归 LoopGuard(已有),不重复实现

v0.2.2 已提交。教训:bench 让”该不该实现某机制”从拍脑袋变成数据决策——先查历史 transcript 有没有对应失败模式,再决定实现深度。

2026-08-07 v0.2.2 全量基准(修复后基线)

结果:5/5 PASS,errors 38→7(-82%),总耗时 826s(-11%)(ThinkingCap + low)

任务基线 errv0.2.2 err耗时gate
t121118s1
t2270200s0
t311100s1
t474240s2
t511167s1

回归验证通过:

  • t3 gate_blocks=1 → 只读判定放宽后越界写入仍被拦(安全未削弱)
  • t2 errors=0 → 验证死锁彻底修复
  • t4 gate=2 是模型 2 次越界尝试被拦,非回归

全量确认:output-parser(G1)证据继续为零——本轮 5 任务 56 个工具调用全成功解析,无畸形调用。优先级维持:checkpoint(G4)为下一候选。

2026-08-08 read-guard(G3)实现 + checkpoint(G4)跳过决策

G4 checkpoint 跳过:

  • 文件快照解析证据弱:edit 失败全是参数缺失(原子替换不落盘),无”改坏恢复”场景
  • 跨会话恢复受 pi 扩展 API 限制:只有 appendEntry 只写记录,无读回接口,无法 native 实现,与薄层哲学冲突
  • 会话内跨 compaction 已工作:taskLedger 内存对象,session_compact 不重置
  • 长任务超时风险证伪:最新全量最长 240s/900s;t1 曾 900s 是早期 thinking=high 挂起 bug(turns=0),非真实长任务

G3 read-guard 薄层实现(v0.2.3):

  • 证据:历史 1 轮 t4 的 2 次 edit 无 read(err=5),其余轮全 read-first → 失败模式存在但罕见
  • 实现:ReadGuard 跟踪已 read 路径,edit/write 未 read → block + steer;默认关(readGuard.enabled=false)
  • 验证:readGuard on 跑 t4 → PASS、0 拦截(模型天然先读)、无回归
  • 定性:防御机制,与 quality-monitor 同级

机制清单终局:G1/G4/turn-cap 全部证据否定;G3/G2 为防御性(默认关);真正主导的是验证节奏 + 只读判定(errors -82%)。

发表回复