LobsterAI 硬核机制拆解:沙箱实现 + 记忆机制设计
你要的不是趋势,而是代码。下面只讲两件事:沙箱怎么跑起来,以及记忆怎么被提取/评审/落库。
一、沙箱机制(代码级调用链)
1) 执行入口与模式分流(coworkRunner.ts)
runClaudeCode()先做路径与附件检查(尤其附件是否在 cwd 内)。- 若已有活跃 VM(
sandboxProcess + ipcBridge),走continueSandboxTurn()复用同一 VM。 executionMode=local直接runClaudeCodeLocal()。- 否则检查
ensureSandboxReady();sandbox 强制模式失败会报错,auto 模式可能回退 local。
关键点:这是“策略层”,不是直接 spawn VM。
2) VM 运行时准备(coworkSandboxRuntime.ts)
- 确定运行资源路径:
app.getPath('userData')/cowork/sandbox。 - 下载并校验 QEMU runtime + qcow2 镜像(支持架构分流 x64/arm64)。
- 状态模型:
runtimeReady/imageReady/downloading/progress/error。
const runtimeDir = path.join(baseDir, 'runtime', version)
const imagePath = path.join(imageDir, `linux-${process.arch}.qcow2`)3) QEMU 启动参数拼装(coworkVmRunner.ts)
- 统一内核参数:
-m 4096 -smp 2 -snapshot。 - 磁盘:
-drive file=...qcow2,if=virtio,format=qcow2。 - 网络:
-netdev user+virtio-net。 - 加速器按平台选:mac=hvf, win=whpx, linux=kvm(可覆写)。
跨平台 IPC 差异(重点)
| 平台 | 共享机制 | 代码路径 |
|---|---|---|
| macOS / Linux | 9p virtfs 挂载(ipc/work/skills) | -virtfs local,path=...,mount_tag=... |
| Windows | 不用 9p,改用 virtio-serial + TCP bridge | VirtioSerialBridge + findFreePort() |
这就是为什么 Windows 路径有额外“pushFile”逻辑:它没有 9p,技能文件要通过 serial 分块同步进 VM。
4) VM 内外请求协议(文件模式 + serial 模式)
ensureCoworkSandboxDirs(sessionId)创建requests/responses/streams目录。- 主进程构造请求 JSON(prompt/cwd/mounts/env),写到请求通道。
- 主进程监听 VM 输出的 JSON 行:
sdk_event、permission_request、host_tool_request。 - 权限请求回到宿主后,写回响应文件或 serial 回包。
if (messageType === 'permission_request') {
emit('permissionRequest', sessionId, request)
// 用户确认后 writeSandboxPermissionResponse(...)
}5) 可恢复与降级策略
waitForVmReady(..., 60000)等待 VM 就绪,超时会附带 serial.log 诊断。- 加速器失败会识别
HV_DENIED/WHPX关键字并降级。 - sandbox 强制模式失败:终止;auto 模式失败:可回退 local 并提示。
架构取舍:保证“能跑”优先(auto fallback),但会引入“安全语义变动”风险(从隔离执行回到本机执行)。
二、记忆机制(从对话到 user_memories)
1) 数据结构(SQLite)
- 核心表:
user_memories(记忆本体)、user_memory_sources(来源追踪)。 - 索引:状态更新时间、fingerprint 去重索引。
- 状态流:
created / stale / deleted。
2) 提取器(coworkMemoryExtractor.ts)
这是规则引擎,不靠玄学:
- 显式提取:匹配“记住/保存到记忆/forget this”等命令型表达。
- 隐式提取:按句切分后识别个人画像、偏好、长期事实。
- 硬过滤:问题句、寒暄句、过程命令、临时新闻等直接拒绝。
if (SMALL_TALK_RE.test(trimmed)) return false
if (isQuestionLikeMemoryText(trimmed)) return false
if (PROCEDURAL_CANDIDATE_RE.test(trimmed)) return false核心思想:宁可漏记,不乱记。
3) 评审器(coworkMemoryJudge.ts)
- 每条候选记忆会过
judgeMemoryCandidate()。 - 可开关 LLM 评审(
memoryLlmJudgeEnabled),并统计llmReviewed/judgeRejected。 - 防止“提取器误判”直接写库。
4) 写入路径(coworkStore.applyTurnMemoryUpdates())
- 提取:
extractTurnMemoryChanges(...) - 评审:
judgeMemoryCandidate(...) - 写库:
createOrReviveUserMemory(...) - 挂来源:用户消息 + 助手消息 source 关联
- 回收:
markOrphanImplicitMemoriesStale()
const write = this.createOrReviveUserMemory({ text, confidence, isExplicit, source })
if (!change.isExplicit && assistantMessageId) {
this.addMemorySource(write.memory.id, { role:'assistant', ... })
}5) 删除语义不是“精确匹配”
删除走 scoreDeleteMatch(),会从候选记忆里算最相近目标再删除。这样能处理“近似表达删除”,但也带来误删边界,需要 guard。
三、你最该在本地打断点的函数
CoworkRunner.runClaudeCodeCoworkRunner.runClaudeCodeInSandboxspawnCoworkSandboxVmVirtioSerialBridge.connect / pushFileCoworkRunner.waitForVmReadyextractTurnMemoryChangesjudgeMemoryCandidateCoworkStore.applyTurnMemoryUpdates
四、一句硬结论
LobsterAI 的难点不是“调模型”,而是“把 VM 执行协议和记忆生命周期做成稳定系统”。这两块做稳了,它就不是 Demo,而是可长期运行的 Agent Runtime。