Maka 核心技术解读:本地优先 Agent 工作台的 Runtime 内核、权限门控与崩溃恢复架构
2026/9/18 3:59:42 网站建设 项目流程

Maka 核心技术解读:本地优先 Agent 工作台的 Runtime 内核、权限门控与崩溃恢复架构

【免费下载链接】makaApache Maka (Incubating) is a high-performance agent workspace that keeps a complete record of everything it did.项目地址: https://gitcode.com/GitHub_Trending/mak/maka

本文基于 docs/archive/maka-core-tech-walkthrough.md(2026-06-25 生成、2026-07-13 归档的技术通读笔记)展开,并结合当前仓库源码、配置与测试用例进行验证与补充。面向工程师,帮助你理解 Maka 的 runtime 内核分层、四档权限模型、ledger 持久化恢复以及 Electron 集成边界,并掌握"崩溃可恢复、权限可控、诊断不影响主路径"这三条核心设计如何在代码中落地。

整体定位

Maka 是一个本地优先(local-first)的 Electron 桌面 AI agent 工作台。它的技术追求可以概括为一句话:让用户在自己的电脑上跑一个可观察、可控、可恢复的 agent——所有数据本地落盘,敏感值按各自边界保存在本地,工具调用走权限策略,单次对话崩溃后能从 ledger 恢复。

当前官方 README 将其定位为 "a high-performance agent workspace that keeps a complete record of everything it did"(高性能 agent 工作区,完整记录它所做的一切),这与下面要讲的 ledger + projection 架构完全对应:"完整记录"正是靠 run 事件日志(ledger)实现的

仓库分层

monorepo 使用 npm workspaces,分层清晰:

定位说明
packages/core纯契约层零运行时依赖,只定义类型、纯函数与策略表(如 packages/core/src/permission.ts)
packages/storage文件持久化层run ledger、原子写、session 消息 JSONL
packages/runtime内核实现最核心的部分,SessionManager / AgentRun / backend / ToolRuntime
packages/ui共享渲染组件React 组件与 stories
apps/desktopElectron 壳子把 runtime 装进窗口,提供 IPC、preload、OAuth、bot、gateway

核心设计原则:契约与实现分离。runtime 内核再拆成可独立理解的边界(session → run → backend → model/tool),公共 API 保持稳定,内部可演化。例如契约层的permission.ts只声明类型与纯策略,而真正的运行时状态(requestId、parked Promise)由 runtime 层管理。

Runtime 内核架构

这是整个项目最核心的部分。分层关系如下:

SessionManager ← 对外公共 API(桌面 / bot / gateway 都走它) -> AgentRun ← 单次 turn 的生命周期 + 启动恢复 -> AiSdkBackend ← 流式 + 工具循环引擎 -> ModelAdapter ← provider stream / usage / error 归一化 -> ToolRuntime ← 工具输入校验 / 权限 / watchdog / abort / telemetry -> RunTrace ← best-effort 诊断 trace,写失败不影响对话 -> AgentRunStore ← durable run ledger

关键代码入口(已按当前仓库核实):

模块文件
SessionManagerpackages/runtime/src/session-manager.ts(export class SessionManager,位于 905 行附近)
AgentRunpackages/runtime/src/agent-run.ts(export class AgentRun,242 行)
AiSdkBackend.send()流式泵packages/runtime/src/ai-sdk-backend.ts
ToolRuntime权限门控packages/runtime/src/tool-runtime.ts(export class ToolRuntime,477 行)
ModelAdapterprovider 适配packages/runtime/src/model-adapter.ts(export class ModelAdapter,145 行)
RunTrace诊断packages/runtime/src/run-trace.ts(export class RunTrace,107 行)

SessionManager:公共 runtime API

SessionManager是对外(桌面 IPC、bot adapter、open-gateway 三类入口)暴露的唯一 runtime 门面,职责包括:

  • session CRUD
  • backend registry 编排
  • active run 查找
  • 恢复入口(recoverInterruptedSessions(),参见 packages/runtime/src/session-manager.ts 及测试 packages/runtime/src/tests/runtime-continuation-crash.test.ts)

真正干活的 turn 生命周期被委派给AgentRun

AgentRun:单 turn 的 durable 编排

理解恢复机制的关键。一次sendMessage会创建一个AgentRun,它负责:

  1. 生成 runId,写run_created事件到 ledger;
  2. 追加用户消息、写初始 turn 状态;
  3. 锁定连接快照(connectionLocked),防止 turn 跑到一半连接被改;
  4. 构建 prior runtime context(从历史 run 的RuntimeEvent投影成模型历史);
  5. 驱动 backend 流事件,投影 session 状态;
  6. 写 turn 完成 / 失败 / abort / permission-wait 状态;
  7. finalize()收尾:注销 active run、更新 header、写run_completed / run_failed / run_cancelled

execute()是 async generator,核心结构(packages/runtime/src/agent-run.ts):

async *execute(): AsyncIterable<SessionEvent> { try { const begin = await this.begin(); for await (const ev of begin.backend.send(begin.backendInput)) { await this.recordSessionEvent(ev); yield ev; } } catch (error) { await this.recordFailure(error); throw error; } finally { await this.finalize(); } }

每个 backend 事件都会被recordSessionEvent投影成 session 状态变更(running / blocked / aborted),并记录到 run ledger。即使进程崩溃,重启时也能从 ledger 重建。AgentRun还定义了AgentRunDurability = 'best_effort' | 'required'(packages/runtime/src/agent-run.ts 159 行),区分"尽力持久化"与"必须持久化"两种耐用性级别。

AiSdkBackend:流式 + 多步工具循环

agent loop 的引擎,用 Vercel AI SDK 的streamText配合stopWhen: stepCountIs(N)驱动多步工具调用循环——循环本身交给 AI SDK,但所有"自己的机器"(权限、持久化、materializer、watchdog)都保留在 Maka 这边。

send()的当前实现(packages/runtime/src/ai-sdk-backend.ts):

async *send(input: BackendSendInput): AsyncIterable<SessionEvent> { const turn = this.openTurnScope(input); try { yield* turn.run(); } finally { this.activeTurns.delete(turn); await turn.close(); } }

相比归档笔记中"单 turn 单后台泵"的描述,当前实现已演化为per-turn scope 模型:每次send()打开一个AiSdkTurnopenTurnScope),该 turn 拥有自己的 abort controller、自己的ToolRuntime、自己的 watchdog 与 run trace。这带来两个重要特性:

  • 并发 turn 互不串扰stop()以广播方式停止所有 active turn,但每个 turn 以"自身"身份收尾,不再共享一个currentTurnId导致重叠 turn 被错误标记;
  • 清理顺序保证:teardown 对每个 scope 全部 settle 后才抛错——因为endTurn是唯一能 reject 停在askUserQuestion上的工具的东西,abort signal 不会唤醒 registry,若中途 bail 会导致兄弟 turn 永远 parked(代码注释对此有详细说明)。

stop()支持mode: 'immediate' | 'after_step'两种停止策略,且会先abortHistoryCompact()中止手动历史压缩。

backend 层的精巧点还包括:

  • StreamWatchdog:流式连接有 connect timeout 和 idle timeout,超时会 abort controller 并推 error 事件,防止挂死;工具执行期间 watchdog 会被暂停(见下文 ToolRuntime)。
  • step cap grace:当finishReason === 'tool-calls'(撞了 step 上限)且没有 assistant 文本时,注入一条确定性提示,告诉用户已达本轮工具上限、可发"继续"。
  • prepareStep 动态工具加载:同 turn 内可以逐步激活更多工具(deferred load),active 工具集按 step 重算;snapshotToolAvailability()(packages/runtime/src/ai-sdk-backend.ts)负责冻结 host tools 并注入 memory trigger 工具,且保留MEMORY_REMEMBER_TOOL_NAME/MEMORY_EXTRACT_TOOL_NAME两个保留名,防止用户工具冲突。

ToolRuntime:权限门控 seam

整个安全模型的执行点。每个send()createToolRuntime(...)创建一个与 turn 身份绑定的ToolRuntime(packages/runtime/src/ai-sdk-backend.ts),其 scope 闭包保证"工具在 step 之后很久才 settle 时,仍能解析到本 turn 的 watchdog、trace 和 run"。工具调用经过的链:

  1. loop-gate:同一个 tool + 同一组 args 连续失败 N 次,直接 block,防止 agent 死循环。block 本身不记 outcome,streak 停在阈值,后续相同调用继续被拦。
  2. 权限评估三态
    • allow→ 跑真实实现,写ToolResult
    • block→ 合成isError: true的工具结果返回给模型,不执行真实逻辑;
    • prompt→ 推PermissionRequestEvent给 UI,await 一个 parked Promise,等用户决定。allow 则跑,deny 则合成"用户拒绝"。
  3. 工具执行期间暂停 stream watchdog(避免长任务被判超时),finally里恢复。
  4. abort signal 透传进工具,stop 按钮能中断。
  5. 记录 telemetry、artifact candidate、错误分类。

此外当前 ToolRuntime 还接入了sandbox boundary 请求AwaitRegistry<SandboxBoundarySettlement>),支持沙箱边界升级的异步等待与respondToSandboxBoundary()路由。

ModelAdapter:provider 归一化

把 provider / AI SDK 的细节挡在外面:stream chunk 类型、provider setup、usage 归一化、provider error mapping。

  • startStream统一调streamText
  • handleStreamChunk把各种 chunk 翻译成 Maka 的SessionEvent(text delta、thinking delta、complete 等);
  • ModelAdapter还维护OpenAiChatReasoningTransportState,处理 OpenAI chat 推理通道的状态(packages/runtime/src/model-adapter.ts)。

这样未来加 provider 不需要重复权限 / 工具 / run / session 逻辑。

RunTrace:best-effort 诊断

记录 turn 的里程碑事件(RunTracePhase,见 packages/runtime/src/run-trace.ts 79 行附近):turn started、model resolved / stream started / completed / failed、tool started / completed / failed、permission requested / decided、usage recorded、abort requested。

关键约束:trace 写失败绝不影响用户对话。错误消息被截断到 2048 字符(REDACTED_ERROR_MESSAGE_MAX_CHARS),且它与 session 消息 JSONL 是分开的两套持久化。

权限系统

这是 Maka 区别于普通 chat demo 的核心安全设计,定义在 packages/core/src/permission.ts。注意:当前仓库中的权限模型相比归档笔记已有所演化,下面以当前源码为准说明。

权限模式(PermissionMode)

当前PERMISSION_MODES = ['explore', 'ask', 'bypass'](packages/core/src/permission.ts)。归档笔记中提到的execute模式已被退役RETIRED_PERMISSION_MODESexecute折叠为ask——因为它从未有独立行为,编译结果与ask相同、显示为ask、产生相同的执行边界。为了兼容历史记录,decodePersistedPermissionMode()允许旧记录中的execute读回为ask,但新输入与线上值使用严格的isPermissionMode()检查。

模式语义
explore只读探索:读工具放行,写与危险操作被 block,网络读取需要 prompt
ask默认交互模式:读放行,写/危险/网络/浏览器均需用户确认
bypass全自动:全部 allow,仅用户显式选择时进入

工具类别(ToolCategory)

TOOL_CATEGORIES共 14 类(比归档笔记的 12 类多了computer_useclient_capability两类):

read, web_read, file_write, fs_destructive, shell_safe, shell_unsafe, git_destructive, network_send, privileged, browser, computer_use, client_capability, custom_tool, subagent

类别名沿用 Claude SDK 术语,Pi adapter 必须把 Pi 原生工具名翻译成这些类别后再进入 runtime(见 packages/core/src/permission.ts 注释)。

BUILTIN_TOOL_CATEGORY把常见工具名映射到类别(Read/search_files/Grep/GlobreadWebFetch/WebSearchweb_readWrite/Edit/apply_patch/patchfile_writeBash/WriteStdinshell_unsafe)。

mode × category 策略矩阵

契约层用PolicyDecision = 'allow' | 'prompt' | 'block'表示决策三态。虽然当前源码中策略矩阵的实现已迁移到运行时(runtime 层根据readExecutionBoundary/readPermissionMode计算,而不是 core 里一份静态PERMISSION_POLICY表),归档笔记总结的行为模型仍然有效,可帮助理解各模式的意图:

模式危险操作网络浏览器
exploreallowblockblockweb_read=promptblock
askallowpromptpromptpromptprompt
bypassallowallowallowallowallow

核心规则依然成立:不可逆操作(fs_destructivegit_destructiveprivilegedbrowser)在任何非 bypass 模式下都强制 prompt——浏览器操作可能发帖/下单,视为不可逆。bypass全 allow,仅用户显式选择时进入。

Shell 命令分类:从"安全降级"到"fail-closed"

这是当前源码相对归档笔记最重要的演化。归档笔记描述categorizeBash()可根据命令前缀动态降级/升级(ls/pwdshell_safermfs_destructivegit push --forcegit_destructive)。当前源码明确移除了shell_safe的产出路径

There is no SAFE_SHELL_PREFIXES allowlist: a shell command cannot be proven safe from its string... Eight review rounds of enumerating dangerous shapes proved the futility of the inverse (deciding a Turing-complete shell's runtime effect from a static string is undecidable). SocategorizeBashnever returnsshell_safe; read-only needs go through typed tools (Read/Glob/Grep — fixed argv, no shell), and every shell command is at leastshell_unsafe→ prompt.

也就是说:从静态字符串证明 shell 命令安全是不可判定的echo "$(rm x)"、PowerShell 的$(...)、反引号、iex都能在参数里藏执行),因此不再允许任何 shell 命令自动放行——只读需求走固定 argv 的 typed tools(Read/Glob/Grep),其余 shell 命令至少shell_unsafe→ prompt。类别划分仍然保留,但作用从"安全边界"降级为"让确认原因(REASON)更准确"(delete vs elevate vs generic),漏配只是措辞问题而非绕过漏洞。

相应的分类器以纯函数 + 正则表实现(packages/core/src/permission.ts):

  • PRIVILEGED_SHELL_PREFIXES(sudo、su、chmod、chown、mount、kill、systemctl、shutdown、reboot 等)与PRIVILEGED_SHELL_PATTERNS(PowerShell/cmd 等价物:stop-process-verb runas、service 控制、icacls/takeown等,大小写不敏感);
  • FS_DESTRUCTIVE_PATTERNSddtruncateshredmkfsfind -deleterm家族、remove-item/clear-content等),锚定在每个语句段的开头commandSegments|;&\n(){}切分),而不是整个命令开头;
  • PIPE_DESTRUCTIVE_PATTERNS| xargs rm/shred/truncate/dd| sh/bash/zsh);
  • DESTRUCTIVE_GIT_PATTERNSgit reset --hardgit push --force/-fgit branch -Dgit clean -fd?git checkout .git rebase -i)。

切分策略刻意"quote-naive"(引号感知切分会更糟):$( )与反引号在双引号内也会展开,所以 naive 切分永不丢内容、只会切碎,多出的边界只增加扫描候选,不会隐藏危险片段。

纯函数原则

preToolUse()给定输入必然返回相同结果,不生成 UUID(requestId 由 runtime 层的权限引擎生成),便于测试。

parked Promise 机制

权限引擎为每个 outstanding 权限请求维护一个 parked Promise,UI 的决定通过respondToSandboxBoundary()/respondToUserQuestion()(packages/runtime/src/ai-sdk-backend.ts)resolve 回等待中的 adapter。turn 结束时未回答的请求会被 reject 成user_stop。如前述,endTurn是唯一能 reject 停在askUserQuestion上的工具的地方,abort signal 不会唤醒 registry——这保证了 stop 时不会留下永久悬挂的 parked 请求。

持久化与恢复

文件布局

工作数据放 ElectronuserData下的 workspace 目录:

<Electron userData>/workspaces/default/ llm-connections.json credentials.json settings.json sessions/<sessionId>/ messages.jsonl runs/<runId>/ run.json ← run header(原子写) events.jsonl ← append-only run 事件

AgentRun ledger

存储层在 packages/storage/src/agent-run-store.ts,对应测试见 packages/storage/src/tests/atomic-file-write.test.ts。核心保障:

  • run.json原子写(writeAtomicFile先写临时文件再 rename,测试验证"写出精确字节且不残留临时文件");
  • events.jsonlappend-only;
  • 同 run 写串行化(per sessionId+runId 的 Promise 链),避免并发写竞争;
  • ID 校验SAFE_ID_PATTERN,防路径穿越;
  • 读事件时容忍损坏行(event_corrupt)和未终止尾部。

这套 ledger 和用户可见的 session 消息 JSONL 分开,诊断 / 恢复状态不污染对话历史。此外还配套了agent-run-inspect.ts/execution-inspect.ts(core 层)提供 run 审计文档(schemaVersion、source health、projection summary、compaction checkpoint 等)。

启动恢复

recoverInterruptedSessions()优先用 run ledger:扫描持久化的 run header 和事件,分类非终态 run(created/running/waiting_permission),修复后收敛 session / turn 投影。恢复入口的严格版本recoverInterruptedSessionsStrict()在 packages/runtime/src/tests/runtime-ledger-repair.test.ts 中有覆盖(对 pending invocation 进行 run ledger 修复断言)。

覆盖的恢复场景:

  • stale 的created/runningrun;
  • 停在run_started/model_stream_started的 run;
  • tool_started后的残留;
  • permission_requested后的等待;
  • 缺终态事件的model_stream_completed
  • 损坏事件行。

没有 run ledger 的老 session 走旧的 message + turn-state 恢复路径。相关回归测试还包括 packages/runtime/src/tests/runtime-continuation-crash.test.ts、packages/runtime/src/tests/sandbox-boundary-restart-recovery.test.ts 与 packages/runtime/src/tests/runtime-handoff.test.ts。

Electron 集成层

进程边界

进程文件职责
mainapps/desktop/src/main/main.ts装配SessionManager、注册 IPC、管理窗口、OAuth、bot、gateway
preloadapps/desktop/src/preload/preload.tscontextBridge暴露window.maka.*白名单 API
rendererapps/desktop/src/renderer/React UI + Settings

main 进程装配runtime = new SessionManager({...})并启动 gateway 服务;IPC 用ipcMain.handle注册大量通道(memory、artifacts、settings、connection、session 等)。

密钥安全边界

  • Provider/API key、bot token、proxy password、gateway token 等写入本机凭据文件,依赖 OS 账号边界和文件权限;subscription OAuth token 使用独立的系统安全存储;
  • Renderer 永远拿不到明文密钥,Settings 只显示 masked 状态和测试结果。

多入口

除了桌面 UI,runtime 还服务两类外部入口:

  • Bot adapter(packages/runtime/src/bots/):Telegram、飞书、企业微信、微信 iLink、Discord、钉钉、QQ 等,统一对接 SessionManager;
  • Open Gateway:本地 HTTP / SSE API,用 token 保护,让外部读取会话状态 / 事件 / 能力 / 健康摘要。

数据流:一次对话的完整路径

  1. 用户在 renderer 输入消息 →window.maka.session.send→ IPC →SessionManager.sendMessage
  2. 创建AgentRun,写run_created到 ledger,append 用户消息到messages.jsonl
  3. 从历史 run 的 RuntimeEvent 投影模型上下文;
  4. AiSdkBackend.send():打开 turn scope → 构建权限包裹的工具 →streamText(stepCountIs(N))→ 后台泵 fullStream;
  5. 每个流 chunk 经ModelAdapter.handleStreamChunk归一化成SessionEvent→ 推 queue → 前台 yield → IPC 流到 renderer;
  6. 模型调用工具 → 权限门控链 → loop-gate → 权限评估三态(allow / block / prompt)→ 执行、合成错误或等待用户决定;
  7. assistant 文本落messages.jsonl,usage 记 telemetry,trace 写 run ledger;
  8. finalize()收尾,写run_completed,更新 session header。

技术亮点小结

  • ledger + projection 模式:run 事件是 source of truth,session 状态是投影,崩溃可重建——整个恢复能力的根基;
  • 纯函数策略 + runtime 包装:权限决策表是纯函数好测试,requestId / parking 状态由 runtime 管,职责分明;
  • 权限门控作为工具 execute 的 seam:不动 AI SDK 的循环,在 execute 回调里插入 allow / block / prompt,最小侵入;
  • fail-closed 的 shell 分类:承认"静态判定 shell 安全性不可判定",取消shell_safe自动放行,只读走 typed tools,类别只用于精确确认原因;
  • best-effort trace 不影响主路径:诊断信息尽力写,失败静默,对话绝不受拖累;
  • 同 run 写串行化 + 原子写:文件持久化的并发安全靠 Promise 链和 rename 保证;
  • per-turn scope 模型:每次 send 绑定独立的 watchdog / ToolRuntime / trace,并发 turn 互不串扰,stop 与 teardown 顺序有严格保证;
  • 安全分层:模式 × 类别矩阵 + 命令分类器 + 凭据文件权限 / subscription token 安全存储 + preload 白名单,多层防御。

延伸阅读:本笔记已归档(2026-07-13),当前 backend 体系的最新指引以仓库根目录 ARCHITECTURE.md 及其架构章节、源码和测试为准。

【免费下载链接】makaApache Maka (Incubating) is a high-performance agent workspace that keeps a complete record of everything it did.项目地址: https://gitcode.com/GitHub_Trending/mak/maka

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询