eve — 架构与原理(总览)
30 秒导读: eve 是 Vercel 出品的「文件系统优先(filesystem-first)」后端 AI agent 框架。你不写一个巨大的配置对象,而是把 agent 的每个部件——指令、工具、技能、频道、定时任务——放 进约定好的文件位置;eve 扫描这个目录树,编译成一个能跑在本地、能服务 HTTP、能跨多轮对话持久运行、且进程崩溃后能从断点恢复的 agent。
1. 这是什么(零基础也能懂)
一句话定义。 eve 是一个用「普通 TypeScript 文件」来搭建持久化 AI agent 的框架(packages/eve/package.json:5,description 写得很直白:"Filesystem-first framework for durable backend AI agents that run anywhere")。
它要解决谁的什么问题。 假设你要做一个能接 Slack、能调工具、能跑好几天、中途还能等人审批的后端 agent。常规做法是把模型配置、工具列表、频道路由全塞进一个大对象,越长越难读、越难改。eve 反过来:每个关注点都有一个固定的"家"——指令在一个文件、工具在一个文件夹、频道在另一个文件夹。光读目录树,你就大致知道这个 agent 能干什么。
一个典型 eve agent 长这样(摘自 README.md:24):
my-agent/
└── agent/
├── agent.ts # 可选:选模型、配运行时
├── instructions.md # 必填:always-on 系统提示
├── tools/ # 可选:模型能调的、带类型的函数
│ └── get_weather.ts
├── skills/ # 可选:按需加载的长流程
│ └── plan_a_trip.md
├── channels/ # 可选:消息入口(HTTP / Slack / Discord)
│ └── slack.ts
└── schedules/ # 可选:周期性 cron 任务
└── weekly_recap.ts
用起来什么样(最小示例)。 一条命令起一个新项目(README.md:43):
npx eve@latest init my-agent
它会建目录、装依赖、初始化 Git,并打开一个交互式终端 UI。
写一个工具,只需在 agent/tools/ 下放一个文件——文件路径就是工具名,不需要任何注册表(README.md:71):
// agent/tools/get_weather.ts —— 文件名 get_weather 就是工具名
import { defineTool } from "eve/tools";
import { z } from "zod";
export default defineTool({
description: "Return mock weather data for a city.",
inputSchema: z.object({ city: z.string().min(1) }),
async execute({ city }) {
return { city, condition: "Sunny", temperatureF: 72 };
},
});
选模型,在 agent/agent.ts 里一行(README.md:87):
import { defineAgent } from "eve";
export default defineAgent({
model: "anthropic/claude-sonnet-4.6",
});
这就是一个能跑的 agent 了。defineTool / defineAgent 这些只是给文件套了类型壳子,真正的"组装"由框架在后台完成。
一句话直觉。 把 eve 想成 agent 界的「约定优于配置」框架——就像 Next.js 把 pages/ 目录下的文件自动变成路由,eve 把 agent/ 目录下的文件自动变成一个 agent 的能力。目录结构就是这个 agent 的接口。
2. 顶层全景(它大概怎么转)
要看懂 eve,先抓住一条主线:目录约定 → discover(发现)→ compile(编译)→ runtime(运行)。前两步在构建/启动时跑一次,把你写的文件变成一份"清单(manifest)";第三步在每条消息到来时跑,驱动模型循环干活 。
怎么读这张图
从上到下分三段:约定层(你写的文件)→ 编译层(框架一次性把文件读成 manifest)→ 运行层(每条消息触发的持久化执行)。运行层内部又是一条 channel → turn workflow → harness 循环 → sandbox 的链路。
┌──────────────────────────── 你写的文件(约定层) ────────────────────────────┐
│ agent/instructions.md agent/tools/*.ts agent/skills/* agent/channels/* │
│ agent/agent.ts agent/schedules/* agent/connections/* agent/subagents/* │
└───────────────────────────────────┬───────────────────────────────────────────┘
│ 构建 / 启动时跑一次
┌────────────▼────────────┐
│ ① discover(发现) │ 扫目录,认出每个"槽位"
│ src/discover/ │ (哪些是工具/频道/技能…)
└────────────┬────────────┘
┌────────────▼────────────┐
│ ② compile(编译) │ 规范化成一份 manifest
│ src/compiler/ │ (模型、工具描述、路由…)
└────────────┬────────────┘
│
▼ ── 运行时(每条消息触发) ──
消息到来 ┌─────────────────────────┐
(HTTP/Slack/cron) ───▶ │ ③ channel(前门) │ 验签、定身份、转成统一消息
│ src/channel/ │
└────────────┬────────────┘
┌───────────▼─────────────┐
│ ④ turn workflow │ 把"这一轮"包成持久化工作流
│ src/execution/ │ 逐 step 落 checkpoint,崩溃可恢复
└───────────┬─────────────┘
┌───────────▼─────────────┐
│ ⑤ harness(默认循环) │ 喂指令/工具/历史给模型,
│ src/harness/ │ 跑 tool-loop,做上下文压缩
└───────────┬────── ───────┘
│ bash / read_file / write_file …
┌───────────▼─────────────┐
│ ⑥ sandbox(隔离工作区) │ 模型的 shell/文件操作落在这里
│ src/sandbox/ │ /workspace 隔离,无 secrets
└──────────────────────────┘
主线走一遍(高层,不进代码)
- 写文件。 你在
agent/下按约定摆好指令、工具、频道等。 - discover + compile(一次性)。
discoverAgent(src/discover/discover-agent.ts:59)扫描目录认出每个槽位,compileAgent(src/compiler/compile-agent.ts:62)把它们规范化成一份运行时清单(模型选择、工具描述、频道路由等)。 - 消息进来。 不管来自 Web、终端还是 Slack,走的是同一条流程(
docs/introduction.mdx:65):channel 把平台输入转成统一消息。 - 包成一轮持久化工作流。 每一轮(turn)作为一个 durable workflow 运行——
turnWorkflow(src/execution/turn-workflow.ts:32)。 - harness 驱动模型。 默认 harness 把指令、技能、工具、历史喂给模型,跑 tool-loop——
createToolLoopHarness(src/harness/tool-loop.ts:347),按需调工具和子 agent。 - shell/文件落到 sandbox。 模型的
bash/read_file/write_file操作被代理进隔离的/workspace,碰不到process.env和密钥。 - 保存 + 流式返回。 会话被持久化、事件被流式推送,结果以平台期望的形式送回。
关键收获:channel→turn→harness→sandbox 这条链对每个平台都一样。你的天气工具不需要知道问题是从浏览器还是 Slack 来的(docs/introduction.mdx:67)。
3. 部件一句话职责
下面这张表是 eve 的"零件清单"。左边是你在 agent/ 下能写的槽位,右边是它干什么、用哪个 define* 助手、源码在哪。
| 部件 | 干什么 | 你写的文件 / 助手 | 关键源码符号 |
|---|---|---|---|
| agent 配置 | 选模型、配 compaction、配运行时(Workflow world 等) | agent/agent.ts · defineAgent | defineAgent(src/public/definitions/agent.ts:37) |
| instructions | always-on 系统提示,定 agent 的稳定身份 | agent/instructions.md 或 .ts · defineInstructions | defineInstructions(src/public/definitions/instructions.ts:34) |
| tools | 模型能调的、带 zod 类型的函数;执行在 app 运行时 | agent/tools/<name>.ts · defineTool | defineTool(src/public/definitions/tool.ts:193) |
| skills | 按需加载的长流程,平时不进 prompt,匹配到才注入 | agent/skills/*.md 或 */SKILL.md · defineSkill | defineSkill(src/public/definitions/skill.ts:29) |
| channels | agent 的"前门":入站验签、定身份、转成统一消息 | agent/channels/*.ts · defineChannel | defineChannel(src/public/definitions/defineChannel.ts:210) |
| schedules | 周期性 / 定时任务(cron) | agent/schedules/*.ts · defineSchedule | defineSchedule(src/public/definitions/schedule.ts:104) |
| connections | 接外部 MCP / OpenAPI 服务,把它们的工具引进来 | agent/connections/*.ts | eve/connections 导出(package.json:114) |
| subagents | 专精子 agent,根 agent 可委派,各自独立上下文 | agent/subagents/* | src/execution/subagent-*.ts |
| state | 会话级持久化状态,跨 step 边界存活 | defineState | defineState(src/public/definitions/state.ts:43) |
| dynamic | 按调用者(团 队/租户/计划)在运行时解析指令或技能 | defineDynamic | defineDynamic(src/public/definitions/tool.ts:258) |
| sandbox | 模型 shell/文件操作的隔离工作区 | agent/sandbox/* · eve/sandbox | eve/sandbox 导出(package.json:129) |
补一句容易混的点:tool 和 sandbox 的信任级别不同。工具的 execute 跑在受信的 app 运行时(能读 process.env、调 Stripe);而模型的 shell 命令跑在隔离的 sandbox(没有密钥、没有回到 app 的路)。这条边界是 eve 安全模型的核心(docs/concepts/security-model.md:10),详见 05 章。
4. 本组阅读地图
这一组文档把 eve 由浅入深拆成五章。建议按顺序读:1→2 打通"文件怎么变成运行的 agent、为什么不丢状态";3→4 深入默认行为和上下文控制;5 收尾在边界和安全。
| 顺序 | 章节 | 讲什么 | 什么时候该读 |
|---|---|---|---|
| 1 | 01-filesystem-discovery.md | 文件系统即接口:discover 怎么扫目录认槽位,compile 怎么规范化成 manifest | 想搞懂"为什么放个文件就能加工具" |
| 2 | 02-execution-durability.md | session / turn / step 三级模型,Workflow SDK 怎么让会话崩溃可恢复、可暂停可续 | 想搞懂"为什么进程挂了对话不丢" |
| 3 | 03-harness-tool-loop.md | 默认 harness:agent 主循环、内置工具集、上下文压缩(compaction) | 想搞懂"开箱即用的那套循环和工具" |
| 4 | 04-context-skills-subagents.md | 上下文控制:always-on instructions、按需 skills、隔离 subagents | 想搞懂"怎么控制模型每轮看到什么" |
| 5 | 05-channels-connections-security.md | channels 前门、connections 凭证、app/sandbox 双信任域、失败即关闭 | 想搞懂"边界在哪、安全怎么兜底" |
巧妙之处提要(精华预告)
这些是读完整组后能带走的设计点,先列在这里建立期待:
- 路径即身份(filesystem-first)。 工具名 = 文件名,移动/重命名文件,它的身份跟着走;没有要同步的注册表(
docs/introduction.mdx:61)。这是 01 章的主题。 - turn = 一个持久化工作流。 每轮对话被包成 durable workflow,在每个 step 边界落 checkpoint。崩溃/超时/重新部署后从最后一个完成的 step 续跑,已完成的 step 不重跑(
docs/concepts/execution-model-and-durability.md:43)。这是 02 章的主题。 - "停下来等"也是持久的(parked work)。 等人审批、等 OAuth、等子 agent——工作流挂起且不占算力,输入到了再原地续(
docs/concepts/execution-model-and-durability.md:51)。 - compaction 不只压历史,还保护框架自己的工具状态。 压缩会重置 read-before-write 跟踪、并重新注入 todo 列表,让模型跨摘要不丢任务(
docs/concepts/default-harness.md:21)。这是 03 章的主题。 - 内置工具靠"同名覆盖 / sentinel 禁用"来改。 在
agent/tools/放一个同 slug 文件就覆盖内置;导出disableTool()就移除——文件名写错会在构建时报错而不是悄悄失败(docs/concepts/default-harness.md:80)。 - skills 默认不进 prompt。 框架只广告技能描述并挂一个
load_skill工具,匹配到才把技能 markdown 注入当轮(docs/concepts/context-control.md:36)。这是 04 章的主题。 - 双信任域 + 失败即关闭。 密钥只在 app 运行时,sandbox 永远看不到;路由默认拒绝匿名流量,放开匿名要显式
none()(docs/concepts/security-model.md:76)。这是 05 章的主题。 - frontmatter 是数据,不是代码。 skill/schedule 文件的 YAML frontmatter 被严格当数据;会
eval()的代码引擎(---js)被禁用,这类 fence 直接抛错(docs/concepts/security-model.md:73)。
5. 边界与本章范围
本章只讲 Layer 0(这是什么)+ Layer 1(顶层全景)+ 阅读地图,不深入任何子系统 的实现细节。具体机制——发现/编译的算法、Workflow 的 step 语义、tool-loop 的内循环、压缩的触发、channel 验签——分别落在 01–05 各章。
eve 当前处于 beta(README.md:124),API 和行为可能变;本章所有引用 as-of sourceCommit: ed8a935。
6. 代码地图(跨 src 顶层目录的导航索引)
eve 源码在 packages/eve/src/ 下,按"发现 → 编译 → 运行"的生命周期组织。下表是顶层目录的跳转表,带真实符号名(行号会随上游漂移,符号名更抗漂移,可直接 grep)。
| 主题 | 文件路径(相对 packages/eve/src/) | 关键符号 |
|---|---|---|
| 对外入口(re-export) | index.ts → public/index.ts | export * from "#public/index.js" |
| agent 配置助手 | public/definitions/agent.ts | defineAgent |
| 工具定义助手 | public/definitions/tool.ts | defineTool · defineDynamic |
| 指令 / 技能 / 定时 / 频道 / 状态助手 | public/definitions/ | defineInstructions · defineSkill · defineSchedule · defineChannel · defineState |
| ① 发现:扫目录认槽位 | discover/ | discoverAgent(discover/discover-agent.ts:59) · filesystem.ts · slots.ts · manifest.ts |
| ② 编译:规范化成 manifest | compiler/ | compileAgent(compiler/compile-agent.ts:62) · normalize-*.ts |
| ③ 频道:入站前门、路由、验签 | channel/ | routes.ts · adapter.ts · schedule.ts · send.ts |
| ④ 持久化执行:turn 工作流 / session | execution/ | turnWorkflow(execution/turn-workflow.ts:32) · session.ts · workflow-entry.ts · subagent-*.ts |
| ⑤ harness:主循环、内置工具、压缩 | harness/ | createToolLoopHarness(harness/tool-loop.ts:347) · compaction.ts · advertised-tools.ts |
| 内置工具实现(编译进来的) | runtime/framework-tools/ | bash.ts · glob.ts · grep.ts · ask-question.ts · connection-search-dynamic.ts |
| ⑥ sandbox:隔离工作区后端 | sandbox/ | (后端适配:docker / just-bash / microsandbox / vercel) |
| 运行时解析图(把 manifest 连成可执行图) | runtime/ | resolve-agent.ts · resolve-channel.ts · resolve-tool.ts · graph.ts |
CLI(eve init / dev / deploy) | cli/ | cli/commands/init.ts · cli/run.ts · cli/dev/ |
| 客户端 / 前端框架绑定 | client/ · react/ · vue/ · svelte/ | 对应 package.json 的 ./client ./react 等导出 |
| 评测 | evals/ | package.json 的 ./evals 导出 |
想直接从源码入手:先读
discover/discover-agent.ts看"文件怎么被认出来",再读compiler/compile-agent.ts看"怎么变成 manifest",最后读execution/turn-workflow.ts+harness/tool-loop.ts看"一轮对话怎么跑"。这三处串起来就是 eve 的主干。各子系统细节见 01–05 章。