解读 Memos 的 AGENTS.md:一份为 AI 编码代理而写的可执行工程手册
AGENTS.md 是 Memos 仓库中面向 AI 编码代理的"操作说明书":它不是一篇泛泛的架构介绍,而是把技术栈快照、工作守则、可复制的命令集、代码地图、变更路由表和验证策略压缩在一个文件里。读完本文,你将掌握 Memos 前后端、Proto、数据库迁移各子系统的标准开发/验证命令,知道每类改动应该触碰哪些文件、跑哪些测试,并理解这些约定如何在 CI 中真正被执行——这也是你编写或维护自己仓库 AGENTS.md 时可直接套用的范式。
设计哲学:短、具体、命令必须真的能跑
文档开篇就立下了两条元规则(AGENTS.md):
- 保持文件短小、具体,并且每一条都绑定到"在这个仓库里确实能跑通的命令";
- 如果文件中的某个事实与源码或 CI 配置冲突,以源文件为准,并回头更新这份指南。
第二条规则本身就值得注意——它让 AGENTS.md 成了一个"自我修正"的文档。当前仓库里就有实例:文档快照中写 "TypeScript 6",而 web/package.json 的 devDependencies 实际是 typescript: ^7.0.2。按文档自己的规则,应以源文件为准。这种"文档可能过时、源码是唯一真相"的立场,正是 AI 代理在仓库中工作时最需要的防幻觉机制。
项目快照:技术栈与生成产物的边界
AGENTS.md 用一小节(AGENTS.md)给出了 Memos 的技术栈全貌,每一条都能在仓库中找到证据:
| 声明 | 证据 |
|---|---|
| Go 1.27.0 | go.mod 第 3 行 go 1.27.0 |
| Echo v5 | go.mod 依赖 github.com/labstack/echo/v5 v5.3.1 |
| Connect RPC / gRPC-Gateway / Protobuf | go.mod 中 connectrpc.com/connect、github.com/grpc-ecosystem/grpc-gateway/v2、google.golang.org/protobuf |
| React 19、Vite 8、Tailwind CSS v4、React Query v5 | web/package.json 中 react ^19.2.6、vite ^8.0.11、tailwindcss ^4.2.4、@tanstack/react-query ^5.100.9 |
| SQLite / MySQL / PostgreSQL | 目录 store/db/sqlite、store/db/mysql、store/db/postgres |
快照中最有实操价值的一句是生成产物的定位:
- Go 与 OpenAPI 生成代码在
proto/gen/; - TypeScript 生成代码在
web/src/types/proto/。
proto/buf.gen.yaml 印证了这一点:buf generate 一次会调用五个远程插件(protocolbuffers/go、grpc/go、connectrpc/go、grpc-ecosystem/gateway、google-gnostic-openapi),全部输出到 gen/;而 bufbuild/es 插件则把 target 指向 ../web/src/types/proto。也就是说,改一次 .proto 文件,Go 和 TypeScript 两侧的生成代码必须成对出现在提交里——这正是后文"Database And Proto Rules"中"必须包含 Go/OpenAPI 与 TypeScript 两侧生成产物"这一条的底层原因。
工作守则:七条约束划定代理的改动边界
AGENTS.md 的 Working Rules 是全文中"负面清单"最集中的部分,七条规则可以归为三类:
- 改动前理解上下文:编辑前先读相关代码;优先复用仓库内已有模式,而不是引入新抽象。
- 控制 diff 范围:不做全仓库清理、不做依赖刷新(dependency churn)、不重写生成文件——除非任务本身要求。这条规则直接决定了代理产出 diff 的可审查性。
- 高风险操作先询问:新增重量级依赖、修改认证/token 行为、改动 Docker/发布流程之前,必须先问。
其中两条规则有明确的"机制级"支撑:
- "不要手改生成代码,改
.proto后跑buf generate" —— 因为 buf.gen.yaml 里启用了managed: enabled(managed mode),文件头部的go_package等选项由 buf 统一管理,手改几乎必然在下一次生成时被打掉; - "改 schema 时给所有数据库驱动加迁移" —— 因为仓库为三个驱动各自维护一套增量迁移目录(
store/migration/{sqlite,mysql,postgres}/),漏掉任何一套都会让对应驱动的升级路径断裂。
标准命令集:三组命令全部与 CI 对齐
AGENTS.md 的 Commands 一节按后端、前端、Protocol Buffers 三组给出命令,且默认从仓库根目录执行(除非命令以 cd 开头)。完整命令如下,可直接复制使用:
# Backend
go run ./cmd/memos --port 8081 # Start backend dev server
go test ./... # Run all Go tests
go test -v ./store/... # Store tests, including DB drivers via TestContainers
go test -v -race ./server/... # Server tests with race detector
go test -v -race ./internal/... # Internal package tests with race detector
go test -v -run TestFoo ./pkg/... # Run matching Go tests
go mod tidy -go=1.27.0 # Match CI tidy check
golangci-lint run # Go lint, config: .golangci.yaml
golangci-lint run --fix # Auto-fix lint, including goimports
# Frontend
cd web && pnpm install # Install dependencies
cd web && pnpm dev # Dev server on :3001, proxying API to :8081
cd web && pnpm lint # Type check + Biome lint
cd web && pnpm test # Vitest unit tests
cd web && pnpm build # Production build
cd web && pnpm release # Build SPA into server/router/frontend/dist
# Protocol Buffers
cd proto && buf generate # Regenerate Go + TypeScript + OpenAPI
cd proto && buf lint # Lint proto files
cd proto && buf format -w # Format proto files
逐条对照仓库后,有几个细节值得展开:
go mod tidy -go=1.27.0 为什么要带版本参数。后端 CI 的第一步"静态检查"就是跑 go mod tidy -go=1.27.0 然后 git diff --exit-code(.github/workflows/backend-tests.yml)——本地如果不带 -go=1.27.0 参数 tidy,产生的 go.mod/go.sum 差异会被 CI 直接判为失败。这是"本地命令必须模拟 CI"的典型例子。
pnpm release 与 pnpm build 的区别。web/package.json 里 build 就是 vite build,而 release 是 vite build --mode release --outDir=../server/router/frontend/dist --emptyOutDir——它把 SPA 产物直接打进后端的静态资源目录,scripts/Dockerfile 的注释也明确写了"请先构建前端,这样静态文件才可用"。所以"只改前端想验证完整容器"时应跑 release,"只想看 Vite 构建是否通过"时跑 build 即可。
store 测试为什么特别。注释说"including DB drivers via TestContainers",go.mod 中确实引入了 testcontainers-go 及其 mysql/postgres/minio 模块;后端 CI 对 store 组还特意设置了 DRIVER 环境变量逻辑(store 组不设置 DRIVER,即跑全部驱动;其余组用 sqlite),见 backend-tests.yml。
代码地图:20 行表格定位整个仓库
AGENTS.md 用一张路径→职责的表把仓库核心区域全部点出。为便于检索,这里按"入口 → 服务层 → 存储层 → API 定义 → 前端"分层重新组织,全部条目与原文档一致:
| 路径 | 职责 |
|---|---|
cmd/memos/main.go |
Cobra/Viper CLI 装配与服务启动 |
server/server.go |
Echo HTTP 服务器、路由装配与优雅停机 |
server/auth/ |
JWT access token、refresh token、PAT 处理 |
server/router/api/v1/ |
Connect/gRPC-Gateway 服务、ACL 配置、SSE hub |
server/router/frontend/ |
静态 SPA 服务 |
server/router/fileserver/ |
原生 HTTP 文件服务、缩略图、range 请求 |
server/runner/ |
Memo payload 重建 |
store/ |
Store 门面、缓存、迁移、驱动接口 |
store/db/{sqlite,mysql,postgres}/ |
各数据库驱动的专属实现与 SQL |
proto/api/v1/ |
对外 API 服务定义 |
proto/store/ |
内部存储 Proto 消息 |
internal/ |
应用私有包:scheduler、cron、email、CEL filter、markdown、idp、S3 |
web/src/connect.ts |
Connect RPC 客户端、auth 拦截器、access token 刷新 |
web/src/auth-state.ts |
token 存储与 BroadcastChannel 跨标签页同步 |
web/src/hooks/ |
服务端状态的 React Query hooks |
web/src/contexts/ |
客户端/UI 状态的 React context |
web/src/components/ |
Radix/Tailwind UI 组件与业务组件 |
web/src/themes/ |
基于 OKLch 色彩 token 的 CSS 主题 |
以代码地图的第一行为例做实证:cmd/memos/main.go 中确实能看到 cobra.Command 根命令 + viper.SetDefault("port", 8081) 的装配逻辑——这就是文档里 go run ./cmd/memos --port 8081 默认端口 8081 的来源,也与前端开发服务器代理到 :8081 的说明相互咬合。
变更路由表:每类改动"改哪里 + 验什么"
AGENTS.md 的 Change Routing 是全文对 AI 代理最有指导意义的表格:它把"你要改什么"映射到"该动哪些文件"和"用什么命令自证"。完整继承如下:
| 改动类型 | 应更新 | 验证命令 |
|---|---|---|
| Go 服务或路由行为 | server/ 下对应 service 代码、包邻近测试 |
go test -v -race ./server/... |
| Store 或迁移行为 | store/、三个 DB 驱动的迁移、LATEST.sql |
go test -v ./store/... |
| 内部包逻辑 | 对应 internal/ 包的测试 |
go test -v -race ./internal/... |
| 前端行为 | web/src/ 下组件/hooks/contexts |
cd web && pnpm lint && pnpm test |
| 前端生产构建产物 | Vite 配置或影响发布的 UI | cd web && pnpm build 或 pnpm release |
| Proto API | .proto 源文件加生成产物 |
cd proto && buf generate && buf lint |
| 公开的免鉴权路由 | server/router/api/v1/acl_config.go |
针对性 server 测试或手工路由检查 |
表中"Go 服务或路由行为"一行的注释与 CI 相互印证:backend-tests.yml 里 server 组的注释写着 ./server/test 会通过 server.NewServer 真实启动服务器并冒烟它挂载的所有 router——所以 server 组测试本身就是一次"真实服务器启动"级别的验证。
而"公开免鉴权路由必须登记到 acl_config.go"这一行,经确认该文件确实存在于 server/router/api/v1/acl_config.go。这意味着 Memos 的 ACL 不是散落各处的 e.GET(...),而是集中声明——对代理来说这是一条"改路由不登记 = 权限模型不一致"的硬约束。
Go 代码规范:由 golangci-lint 强制执行的约定
AGENTS.md 的五条 Go 规范几乎都能在 .golangci.yaml 里找到对应的执行器:
- 错误包装用
errors.Wrap(err, "context")(github.com/pkg/errors),不用fmt.Errorf。这条不是风格偏好,而是被forbidigolinter 硬编码禁止的——.golangci.yaml 中forbid模式'fmt\.Errorf(# Please use errors\.Wrap\|Wrapf\|Errorf instead)?'会直接让 lint 失败。同理ioutil.ReadDir也被禁,要求改用os.ReadDir。 - 服务错误用
status.Errorf(codes.X, "message")返回,保持 gRPC/Connect 语义一致。 - import 分组:标准库 → 第三方 →
github.com/usememos/memos。执行器是 goimports 格式化器,其local-prefixes精确配置为github.com/usememos/memos,golangci-lint run --fix会自动执行。 - 导出标识符加 doc 注释,godot 强制导出注释标点。.golangci.yaml 确实启用了
godotlinter。 - 避免包级可变状态,除非所在包本身已采用该模式——这是一条纯约定,靠 code review 与代理自律执行。
前端代码规范:Biome 配置即契约
AGENTS.md 的前端六条规范,与 web/biome.json 逐项对应:
@/绝对导入:由 tsconfig/Vite 的别名机制支撑,pnpm lint中的tsc --noEmit会验证其有效性(package.json 的 lint 脚本是tsc --noEmit --skipLibCheck && biome check src tests);- 2 空格缩进、双引号、分号、140 列宽:biome.json 中
indentWidth: 2、lineWidth: 140,JavaScript formatter 段(biome.json)中quoteStyle: "double"、semicolons: "always"; - 服务端数据放
web/src/hooks/的 React Query hooks,纯 UI 状态放 context 或组件 state:这是数据架构约定,web/src/hooks/目录里 30 余个use*Queries/use*文件即是该模式的实体; - Tailwind v4 工具类 +
cn()合并 + CVA 变体:package.json 依赖中有clsx、tailwind-merge、class-variance-authority; - 优先复用 Radix 基元与现有组件(该仓库 UI 层实际同时依赖
@base-ui/react,从 package.json 依赖看代理应以现有组件代码为准); web/src/types/proto/下的生成 TypeScript 不手改、也不参与 Biome 重写:biome.json 的files.includes明确排除了!src/types/proto,lint 的includes同样排除!src/types/proto/**——所以本地跑pnpm lint不会碰生成代码,与 AGENTS.md 的禁令在工具层面完全闭环。
数据库与 Proto 规则:三条不可妥协的等价性约束
AGENTS.md 给出四条规则:
- schema 变更 = 三份迁移 + 三份
LATEST.sql。仓库结构印证了"三份":store/migration/sqlite、store/migration/mysql、store/migration/postgres 各自按版本目录(0.17 … 0.31)存放增量 SQL,且每个目录下都有LATEST.sql(全新安装的完整 schema)。 - 全新安装 SQL 与增量迁移必须保持等价——即"从空库跑 LATEST.sql"和"从旧版一步步跑增量"必须收敛到同一 schema。
- Proto 字段变更默认必须保持兼容,除非任务显式允许破坏性变更。
- 改 proto 后重新生成,且提交必须同时包含 Go/OpenAPI 与 TypeScript 两侧产物——呼应 buf.gen.yaml 中六个插件一次生成两类输出的事实。
对维护者的实际含义:一次 schema 改动的最小完整提交 = 新迁移文件 × 3 + LATEST.sql × 3 + store 层代码(如需要)+ go test -v ./store/... 通过(store 测试会经 TestContainers 对真实 MySQL/PostgreSQL 跑迁移)。
验证策略:迭代时窄验证,收工前按路由面验证
AGENTS.md 的 Verification Policy 给出了四条节奏规则:
- 迭代过程中跑最窄的相关检查(比如只改一个 store 驱动就只跑对应包测试);
- 收尾前,按 Change Routing 表中"被改动面"对应的命令完整跑一遍;
- 纯文档改动
git diff --check即可,除非文档里包含可运行示例; - 某个必需检查在本地跑不了时,必须报告原因和确切命令——这条专为 AI 代理设计,防止"悄悄跳过验证"。
CI 参考:文档声明的每条 CI 事实都可复核
AGENTS.md 的 CI Reference 列出的版本与流水线事实,逐一与仓库对账:
| 文档声明 | 仓库证据 |
|---|---|
后端 CI:Go 1.27.0、go mod tidy -go=1.27.0、golangci-lint v2.13.1、测试组 store/server/internal/other |
backend-tests.yml GO_VERSION: "1.27.0";第 43 行 version: v2.13.1;第 51-52 行 matrix test-group: [store, server, internal, other] |
前端 CI:Node 24、pnpm 11.0.1、pnpm lint/pnpm test/pnpm build |
frontend-tests.yml NODE_VERSION: "24"、PNPM_VERSION: "11.0.1",lint job 与 build job 分别执行对应脚本 |
Proto CI:buf lint 与 buf format 检查 |
proto-linter.yml:buf-lint-action 输入 proto,并显式 buf format -d 判空失败 |
Docker:scripts/Dockerfile、Alpine 3.21、非 root 用户、端口 5230 |
Dockerfile:FROM alpine:3.21、创建 nonroot 用户(uid 10001)、ENV MEMOS_PORT="5230" 与 EXPOSE 5230 |
文档还提到 Docker 是"multi-arch amd64/arm64/arm/v7"。Dockerfile 中使用 --platform=$BUILDPLATFORM 与 ARG TARGETOS TARGETARCH(Dockerfile),是标准的构建平台/目标平台分离写法,为多架构发布提供了基础设施;具体架构列表以发布流水线(.github/workflows/release.yml)为准。
结语:AGENTS.md 为什么值得被当作模板研究
把 AGENTS.md 拆开看,它其实回答了 AI 代理在陌生仓库中会问的四类问题,且每类问题都绑定了可机器验证的出口:
- "这个项目是什么、用什么技术" → Project Snapshot,且声明了生成代码边界,避免代理误改生成文件;
- "我该怎么动手" → Working Rules + Change Routing,用"改动类型 → 文件 → 验证命令"三元组把自由度压缩到可审查范围;
- "什么命令才算验证通过" → Commands + Verification Policy,与 CI 工作流逐条对齐,本地即 CI;
- "写代码要遵守什么风格" → Go/Frontend Conventions,且大部分规则有 lint 工具兜底(forbidigo 禁
fmt.Errorf、godot 查注释标点、goimports 管分组、Biome 管格式与引号)。
对阅读 Memos 源码的开发者而言,这份文件同样是最快的仓库导航图;对要给自己的仓库编写 AGENTS.md 的团队而言,它的"命令与 CI 对齐 + 事实冲突时以源文件为准 + 跑不了的验证必须显式报告"三条原则,是比任何章节标题都更值得抄走的部分。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00