跳到主要内容

数据截至 (上游 commit ee230f304a1a)

Letta Code — 架构与原理

30 秒导读: Letta Code 是一个"终端里的编码 agent",但它和 Claude Code / Codex CLI 最大的不同是:对话历史、记忆、身份不在本地,而在 Letta 服务端;本地这份程序只是一副"手脚"(harness),负责跑工具、管权限、渲染界面。因此同一个 agent 可以今天在你笔记本的终端里干活、晚上被 Slack 消息叫醒、凌晨被 cron 唤醒去整理记忆。


1. 这是什么(零基础也能懂)

一句话定义: Letta Code 是一个有状态 agent 的本地执行外壳——模型和记忆托管在服务端,工具执行留在你的机器上。

它解决什么问题。 普通编码 CLI 的"记忆"就是这一次会话的上下文窗口:关掉终端,agent 就失忆了;换台机器,更是从零开始。Letta Code 把这件事反过来——agent 的人格、对用户的了解、学到的技能都存在服务端,本地只是它当前"附身"的那台机器。

给谁用。 想要一个长期共事、会越用越懂你的 agent 的人;以及想让 agent 常驻(定时跑、被消息唤醒)的人。

它能做什么:

  • 在终端里读写代码、跑 shell、开子 agent(和常见编码 agent 一样)
  • 让 agent 自己改自己的记忆:记忆是一个 git 仓库,每次写入都是一次 commit
  • 让 agent 自己装技能、自己配权限、甚至自己改 harness 代码(mods)
  • 同一个 agent 从 CLI、桌面 app、浏览器、Slack / Telegram / Discord 进入
  • 常驻:把任意机器变成"远程环境"(letta server),或用 cron 让它按时自己干活

用起来什么样。 装完之后就是一条命令,交互式 TUI 与一次性 headless 两种形态(命令清单见 src/index.ts:161,printHelp):

npm install -g @letta-ai/letta-code

letta # 交互 TUI,续上这个项目的上一段对话
letta -p "把 auth 模块的测试补全" # headless:一次性提示,无 TTY 界面
letta server --env-name "work-laptop" # 把这台机器变成 agent 可用的远程环境

一句话直觉。 把它想成远程操作系统 + 本地终端的关系:agent 的"内存和硬盘"(上下文、记忆、身份)在服务端,你的机器只是插上去的键盘和显示器——键盘还带一个安全锁(权限层),不是所有按键都直接生效。


2. 顶层全景(它大概怎么转)

2.1 一次回合的五步

先看主线。怎么读这张图:从上到下是时间,②③⑤ 三步循环,直到模型不再要工具为止。

你的机器(harness) Letta 服务端(agent 状态)
──────────────────── ─────────────────────────
① 组装请求
messages + client_tools ─────────────► ② 模型推理
+ client_skills │
│ 模型要调 Bash / Edit …
④ 本地执行 │
权限判定 → 沙箱 → 真跑 ◄──────────────── ③ approval_request 流回来
│ (stop_reason=requires_approval)
└──► ⑤ 回填结果 {type:"approval", …} ───► 续跑,回到 ②

关键在第 ③ 步:服务端不自己执行本地工具,它把"我要调这个工具"当成一个"审批请求"流回给你。于是"要不要批准"和"谁来执行"变成同一件事的两面——这正是同一个 agent 能被不同前端轮流接管的原因:谁连着,谁就负责批准和执行。

发请求的地方在 src/agent/message.ts:350(sendMessageStream),请求体里同时带上本地工具表和技能表(src/agent/message.ts:334-335,client_skills / client_tools);解析流、攒出 approval 的地方在 src/cli/helpers/stream-processor.ts:139;执行与回填在 src/agent/approval-execution.ts:372(executeApprovalBatch)。

2.2 分层结构

第二张图是静态结构。怎么读:上层可以调下层,下层不准反向调用——这条规则由 CI 脚本强制(scripts/check-layer-boundaries.js:29,RULES)。

入口层 cli/ (Ink TUI) headless.ts websocket/ (listener) channels/ cron/
└──────────┴──────────────┬──────────┴───────────┘
领域层 agent/ (回合、审批、记忆、子 agent)

能力层 tools/ ──── permissions/ ──── sandbox/

存取层 backend/ (Backend 接口:api | local)

2.3 部件职责一览

部件干什么在哪个文件
Backend 抽象屏蔽"agent 状态存在云端还是本地"的差异,以 capabilities 声明支持哪些能力src/backend/backend.ts:161(Backend)、:150(BackendCapabilities)
回合驱动发消息、消费 SSE 流、攒 approval、判定停止原因src/cli/helpers/stream.ts:90(drainStream)
审批执行批量执行本地工具,把结果打包回填src/agent/approval-execution.ts:372(executeApprovalBatch)
工具层定义工具 schema + 实现,按模型族装配不同工具集src/tools/define-tool.ts:21(defineTool)、src/tools/manager.ts:371(ANTHROPIC_DEFAULT_TOOLS)
权限层纯规则判定 allow / deny / ask,不依赖 UIsrc/permissions/checker.ts:157(checkPermission)
沙箱层把 shell 命令包一层 seatbelt(macOS)或 bwrap(Linux)src/sandbox/wrap.ts:21(wrapLauncher)
记忆层memory blocks + git 化的记忆文件系统(MemFS)src/agent/memory.ts:16(MEMORY_BLOCK_LABELS)、src/agent/memory-git.ts:1348(commitMemoryWrite)
自我扩展技能加载、子 agent 派生、mods 改写 harnesssrc/agent/client-skills.ts:499src/agent/subagents/manager.ts:803src/mods/mod-engine.ts:1716
常驻入口WebSocket 监听器、消息渠道网关、定时调度器src/websocket/listener/lifecycle.ts:648src/channels/gateway-core.ts:208src/cron/scheduler.ts:606

2.4 主线走一遍(不进代码)

  1. 启动。 解析命令行,先决定后端模式(云端 API 还是实验性本地后端,src/backend/backend-mode.ts:24,resolveBackendMode),再决定进 TUI、headless 还是某个子命令(src/cli/subcommands/router.ts:34)。
  2. 组装。 扫描本地技能、按当前模型族装配工具集、拉取记忆仓库,拼成第一条请求。
  3. 推理。 服务端跑模型,SSE 流式回推理文本、助手消息、审批请求。
  4. 批准。 每个待执行工具先过权限规则;命中 allow 直接跑,命中 ask 弹对话框,命中 deny 直接拒。
  5. 执行。 工具在本地跑(shell 类还要过沙箱),结果被截断到安全长度。
  6. 回填。 结果打包成一条 approval 消息发回服务端,回到第 3 步,直到模型给出最终回答。
  7. 收尾。 回合结束后把这一回合产生的记忆改动 push 回记忆仓库;够条件时触发"做梦"去整理记忆。

3. 阅读地图

建议按顺序读——每章都假设你读过前一章的概念。

顺序章节讲什么什么时候读
0Letta Code — 架构与原理全景、主线、代码地图(本页)先读
1一次回合是怎么跑的:后端抽象与 approval 环路Backend 接口、SSE 流处理、requires_approval 停止、断流恢复与租约想懂"它到底怎么转"就必须读
2本地工具层:定义、按模型族换装、执行与截断defineTool、四套工具集与命名映射、串行/并行执行、返回值截断想加工具、或想懂"换模型为什么工具名变了"
3批准这件事:权限规则、shell 分析与沙箱四种权限模式、规则匹配、shell 命令拆解、seatbelt / bwrap 渲染关心安全边界、或要自定义自动批准规则
4记忆系统:memory blocks、git 化的 MemFS 与做梦记忆块、记忆仓库的 clone/pull/commit/push、worktree 里的反思本项目最有价值的一章
5自我扩展:技能、子 agent 与改写 harness 的 mods三级技能作用域、子 agent 子进程模型、mods 运行时与"可恢复优于兼容"哲学想让 agent 自己变强
6多入口与常驻:CLI、headless、远程环境、消息渠道与定时TUI / headless / listener / channels / cron / teleport想部署常驻 agent

4. 巧妙之处(可以带走的技术)

4.1 把"工具调用"建模成"审批请求"

妙在哪: 一个协议同时解决了三个问题——权限确认、远程执行、多前端接管。服务端不需要知道你的机器上有什么;它只是说"我想调 Bash(git status)",然后等一个结果回来。谁在线谁就是执行者,agent 本身不用改。

代码里这体现为一种特殊的停止原因:流以 requires_approval 结束,harness 处理完再发起下一轮(src/cli/app/use-conversation-loop.ts:1724)。对照:服务端自带的工具(如 web 搜索)走的是普通 tool_call_message,不进这条路(src/cli/helpers/stream-processor.ts:136-138 的注释明确了这条分界)。

4.2 工具表是"请求级"的,所以能按模型族换装

妙在哪: 因为工具清单每次请求都随消息上报(client_tools),换模型时不需要在服务端重建 agent——直接换一套本地工具集就行。

项目为此准备了四套并存的工具集:Anthropic 风格(Bash/Edit/Read)、Codex 风格(exec_command/apply_patch)、Gemini 风格(run_shell_command/replace),以及两套 PascalCase 变体(src/tools/manager.ts:371:393:404:422:441)。模型族由 handle 前缀判定(src/tools/manager.ts:1591,isOpenAIModel)。

顺带的收益:仓库里还存着三家原版系统提示的快照(src/agent/prompts/source_claude.md 等),用于对齐基准——同一个 harness 可以穿上别人的衣服跑对比实验

4.3 记忆是一个 git 仓库,不是一张 KV 表

妙在哪: 记忆写入自动获得版本、diff、回滚、审计、跨机同步——全是 git 免费给的。agent 每次改记忆都必须写 reason,然后落成一次 commit(src/tools/impl/memory.ts:21MemoryCommandsrc/agent/memory-git.ts:1348,commitMemoryWrite)。

记忆仓库落在 ~/.letta/agents/<agentId>/memory(src/agent/memory-filesystem.ts:27,MEMORY_FS_ROOT),启动时 clone/pull、回合结束后 push(src/agent/memory-git.ts:1619/:1700/:1802)。用户还能把它指向自己的 GitHub 仓库。

4.4 "做梦"是在 git worktree 里改记忆,再合并

妙在哪: 让 agent 反思、重写自己的记忆是危险动作——写坏了就等于人格损伤。这里的做法是:开一个 git worktree,让一个专职子 agent 在分支上改,改完再决定合不合(src/cli/helpers/reflection-launcher.ts:6-15,createReflectionMemoryWorktree / finalizeReflectionMemoryWorktree)。

于是"自我改造"变成了一次可 review、可丢弃的分支操作。触发条件可配:按步数、或按上下文压缩事件(src/reflection-settings.ts:1,ReflectionTrigger)。

4.5 子 agent 就是一个 headless 子进程

妙在哪: 不发明新的进程内并发模型,直接 spawn 一个自己:letta -p <prompt> --output-format stream-json --permission-mode unrestricted(src/agent/subagents/manager.ts:286-289:481)。

好处很实在——子 agent 天然隔离、天然可并行、天然复用整套工具与权限代码。父进程把自己的允许规则并进子进程的允许清单,子 agent 才不会卡在审批对话框上(src/agent/subagents/manager.ts:292-298)。内置类型放在 src/agent/subagents/builtin/(general-purposerecallreflectionforkhistory-analyzer 等)。

4.6 并发接管靠"租约",不靠标志位

妙在哪: 一个 agent 可能同时被终端、Slack、cron 拉扯。listener 的做法是给每个回合发一张租约(TurnLease),规定:任何 await 之后,想改状态先问 isCurrent(lease),过期租约一个事件都不准发(src/websocket/listener/turn-lifecycle.ts:10:197)。

状态机只有四态且互斥:idle / command / active / cancelling;禁止为这些值再加平行标志位。这是把"并发正确性"写成了可执行的团队规约。

4.7 沙箱渲染是纯函数

妙在哪: wrapLauncher(launcher, policy, {backend}) 只做一件事——把 ["/bin/zsh","-c",cmd] 渲染成 sandbox-exec … -- /bin/zsh -c cmdbwrap … -- …,不做可用性探测、不做副作用(src/sandbox/wrap.ts:21)。后端选择是调用方的事(src/sandbox/availability.ts)。

结果:沙箱策略可以纯靠快照测试验证,而不需要真的起一个沙箱。

4.8 mods 的哲学:可恢复优于兼容

妙在哪: mods 是改写 harness 本身的本地代码(注册工具、命令、事件、模型提供商、UI 面板,见 src/mods/mod-engine.ts:132,LettaModApi)。项目明确拒绝为它承诺 API 稳定性,理由是——mod 的作者是 agent 自己:与其维护兼容矩阵,不如保证失败时诊断清晰、能安全模式启动、能被 agent 自己读代码修好(src/mods/README.md,"Core thesis"一节)。


5. 代码地图(导航索引)

按主题跳源码。符号名比行号抗漂移,建议用符号 grep。

5.1 回合与后端

主题文件路径符号名
后端接口与能力声明src/backend/backend.tsBackendBackendCapabilitiesAPIBackend
后端单例与模式切换src/backend/backend.tsgetBackendconfigureBackendMode
后端模式解析src/backend/backend-mode.tsresolveBackendModeBackendMode
本地后端(自己调模型)src/backend/local/local-backend.tsLocalBackend
发消息 / 组装请求体src/agent/message.tssendMessageStreambuildConversationMessagesCreateRequestBody
消费 SSE 流src/cli/helpers/stream.tsdrainStreamdrainStreamWithResume
攒审批请求src/cli/helpers/stream-processor.tsStreamProcessorApprovalRequest
执行审批批次src/agent/approval-execution.tsexecuteApprovalBatchexecuteAutoAllowedTools
断线恢复 / 挂起审批src/agent/check-approval.tsgetResumeDataFromBackendprepareMessageHistory
交互式主循环src/cli/app/use-conversation-loop.tsuseConversationLoop
回合租约状态机src/websocket/listener/turn-lifecycle.tsTurnLifecycleTurnLease

5.2 工具与权限

主题文件路径符号名
工具定义原语src/tools/define-tool.tsdefineToolToolAssets
工具注册表 / 工具集装配src/tools/manager.tsANTHROPIC_DEFAULT_TOOLSOPENAI_DEFAULT_TOOLSGEMINI_DEFAULT_TOOLSgetClientToolsFromRegistry
模型族判定src/tools/manager.tsisOpenAIModelisGeminiModel
工具实现src/tools/impl/bash.tsedit.tsread.tsmemory.tsskill.tstask.ts
工具实现约定src/tools/README.md—(签名与 AbortSignal 约定)
权限判定主入口src/permissions/checker.tscheckPermissioncheckPermissionWithHooks
权限模式src/permissions/mode.tsPermissionModemigratePermissionMode
规则推荐("总是允许"存什么)src/permissions/analyzer.tsanalyzeApprovalContext
shell 命令拆解src/permissions/shell-analysis.tssplitShellSegmentsparseShellAnalysis
只读 shell 判定src/permissions/read-only-shell.tsisReadOnlyShellCommand
沙箱策略与渲染src/sandbox/policy.tssrc/sandbox/wrap.tsbuildFsSandboxPolicywrapLauncher
seatbelt / bwrap 参数src/sandbox/seatbelt.tssrc/sandbox/bwrap.tsbuildSeatbeltProfilebuildBwrapArgs
钩子(hooks)src/hooks/index.tssrc/hooks/types.tsrunPreToolUseHooksHookEvent

5.3 记忆与自我扩展

主题文件路径符号名
记忆块默认值src/agent/memory.tsMEMORY_BLOCK_LABELSparseMdxFrontmatter
记忆块内容src/agent/prompts/*.mdxpersona.mdxhuman.mdxmemory_filesystem.mdx
记忆文件系统路径src/agent/memory-filesystem.tsMEMORY_FS_ROOTgetMemoryFilesystemRoot
记忆 git 操作src/agent/memory-git.tscloneMemoryRepopullMemorycommitMemoryWritepushMemorysyncPendingMemoryCommitsAfterTurn
记忆写入工具src/tools/impl/memory.tsMemoryCommandMemoryArgs
做梦 / 反思src/cli/helpers/reflection-launcher.tssrc/agent/memory-worktree.tscreateReflectionMemoryWorktreefinalizeReflectionMemoryWorktree
反思配置src/reflection-settings.tssrc/backend/api/reflection.tsReflectionTriggerretrieveCloudReflectionConfig
技能发现与注入src/agent/client-skills.tsdiscoverClientSideSkillsbuildClientSkillsPayload
内置技能src/skills/builtin/creating-skillscreating-modsself-configuration
子 agent 派生src/agent/subagents/manager.tsspawnSubagent
子 agent 定义src/agent/subagents/builtin/*.mdgeneral-purposerecallreflectionfork
mods 运行时src/mods/mod-engine.tscreateModEngineLettaModApiModEngine
mods 设计准则src/mods/README.md—("Core thesis" / "Design checklist")
系统提醒目录src/reminders/catalog.tsSHARED_REMINDER_CATALOG

5.4 入口与常驻

主题文件路径符号名
主入口与命令帮助src/index.tsprintHelp
子命令路由src/cli/subcommands/router.tsrunSubcommandsubcommandNeedsEarlyBackendMode
headless 模式src/headless.tshandleHeadlessCommand
WebSocket 监听器src/websocket/listener/lifecycle.tsstartListenerClient
listener 状态规约src/websocket/listener/AGENTS.md—(租约规则、终态规则)
letta serversrc/cli/subcommands/server.tsrunServerSubcommand
消息渠道网关src/channels/gateway-core.tsChannelGateway
渠道插件协议src/channels/README.mdchannelPluginchannel.json
定时任务src/cron/scheduler.tssrc/cron/cron-file.tsstartSchedulershouldFireTaskCronTask
分层约束(CI 强制)scripts/check-layer-boundaries.jsRULES

引用均 as-of ee230f304a1a9415d949420c0835ff8583df26f7。行号可能随上游漂移,符号名通常还在——定位时优先 grep 符号。