开发者指南
这份指南带你把本地仓库跑起来,找到你的改动该落在哪段代码里,并为它选对要跑的检查。仓库规则先看 AGENTS.md,pull request 流程见 CONTRIBUTING.md。想先了解产品和部署架构,可以看 README。
准备本地仓库
后端命令都在仓库根目录下运行。先装好 Python 3.12 和 uv,然后创建环境:
make setup-dev
make check
make setup-dev 会创建 .venv,再用 uv sync 安装项目和开发依赖。默认用 Python 3.12,和 CI 一致;项目同时支持 3.12 和 3.13。在这个仓库里跑 Python 工具时,用 uv run。包发现已经把 apps/backend/ 包含进去了,不需要为每个包单独设置 PYTHONPATH。
如果你要改前端,请用和 CI 一样的 Node.js 22,并安装锁定版本的依赖:
cd apps/frontend
npm ci
如果要把登录、迁移、主机注册和真实的 Agent CLI 放在一起实际跑一遍,就用完整的本地 Docker 环境。在仓库根目录运行:
./deploy/local/dev.sh up
./deploy/local/dev.sh smoke
本地环境指南里讲了 Docker 的前置条件、端口、日志、停止和恢复,以及怎样同时跑多份互不干扰的仓库副本。
找到改动该落在哪里
后端在 apps/backend/ 下分成三个可安装的包。你要改的行为该由谁负责,就改哪个包:
包 |
负责的内容 |
|---|---|
控制平面:HTTP API、准入与调度、持久化、网关适配器以及发布。 |
|
主机端执行:认领任务、保留沙箱、负责工作区和会话的 sandbox manager,以及 Agent CLI 适配器。 |
|
协议版本、能力名称、共用的数据结构和错误、性能剖析采集,以及共用的 Git 和归档算法。 |
控制平面内部:HTTP 处理函数在 api/,可复用的业务流程在 application/,持久化接口在 ports/,接口对应的数据库实现在 storage/。后台维护和状态调和放在 workers/,资源的创建和生命周期管理放在 bootstrap/。最后由 app.py 把主 API 和单独的 MCP 专用监听器组装起来。代码结构指南更细地梳理了 schema 领域、运行时适配器、共用的性能剖析、前端 API 客户端和任务详情界面。
常见的改动可以从下表中的文件入手。表里的后端路径都相对于 apps/backend/。
改动 |
先看这里 |
|---|---|
任务或对话的 HTTP 行为 |
|
请求或响应校验 |
|
新增一处创建任务的调用 |
|
工作区文件、终端或 Git 视图 |
|
查询或持久化字段 |
对应的 |
计算资源生命周期 |
|
主机认领任务,或 worker 的 HTTP 调用 |
|
保留容器的数据类型或生命周期 |
数据类型在 |
CLI 行为或版本兼容性 |
|
浏览器界面或 BFF 路由 |
|
改动要放在负责这项行为的模块里。一段流程需要单独测试时,再把它抽出来;一条规则被多个入口共用时,再把规则抽出来。
守住依赖边界
不同的部署镜像里装的包不一样,所以一个导入在本地仓库里没问题,到了生产环境照样可能失败。请遵守下面这些边界:
HTTP 适配器调用应用流程,以及流程需要的存储能力。应用流程和 worker 不能导入路由模块。有些应用流程用到了 FastAPI 的
HTTPException,所以别想当然地认为这个包和框架无关,以实际的接口为准。ports/只定义能力,不导入能力的实现。Depends(get_storage)返回组装好的Storage:使用这个接口面对应的窄协议,调用store.audit、store.host_pool这样的能力。Repository 方法都归属于这些能力。主机端不能导入控制平面,共用包也不能导入这两个实现包中的任何一个。共用的 Git 子进程和归档 I/O 放在
cloud_agent_shared/git/。除此之外,共用代码里只有操作性能剖析采集器可以导入进程、网络或后台线程相关的模块。导入边界测试里列出了每一项豁免,以及它覆盖哪些模块。控制平面不能导入主机端的实现。运行时的发现信息放在
cloud_agent_shared/runtime_catalog.py,主机适配器和控制平面都从这里读取。导入边界测试会从两个方向检查这条服务边界。访问网关只能走 HTTP。请使用
gateway_contracts/里的契约;不要导入serving.*,不要直接访问网关的数据库,也不要使用网关的API_KEY_SECRET或JWT_SECRET_KEY。本服务有自己的AGENT_CONTROL_TOKEN_SECRET和AGENT_SESSION_SECRET。
跨组件的接线由 bootstrap 负责。MCP 会话的生命周期在 mcp/runtime.py,立即发送(send-now)交接任务的生命周期在 application/send_now.py。bootstrap、应用流程、worker 和领域模块都不能导入 HTTP 路由模块;领域规则和 port 不能导入存储实现或 HTTP 框架;storage 以外的代码不能导入 storage/repositories/。共用的策略应该放在它所属的领域,或者负责它的服务里。
改动沙箱网络、凭据范围或隔离方式时,还要对照 AGENTS.md 里的安全边界做评审。MCP 工具调用只能走 MCP 专用监听器,长期凭据不能进沙箱。推理费用仍然受网关的每用户配额约束,没有按任务设的美元预算。
选择要跑的检查
开发后端时,先跑覆盖你所改行为的那几个测试。开 pull request 之前,再把完整的后端必跑检查跑一遍:
uv run pytest -q tests/servers/test_agent_jobs_api.py
make check
命令 |
检查内容 |
|---|---|
|
Ruff 格式化,并自动修复 lint 问题;会改动文件。 |
|
Ruff 格式检查、Ruff lint(不自动修复)和 pydocstyle。 |
|
对三个后端包全部跑 mypy。 |
|
跑 pytest,不含 |
|
依次运行 |
|
依赖 PostgreSQL 的 |
|
除 |
|
把整个文档站(所有语言)构建到 |
|
先跑 |
在本地跑 CI
这个可移植的启动脚本会把后端、数据库和前端的检查都跑一遍,格式化和 lint 只检查、不改文件:
make ci
# Or select a tier:
make ci-backend
make ci-database
make ci-frontend
跑后端或数据库检查前,先装好 uv 和 Python 3.12。前端检查需要 Node.js 22 和 npm,数据库检查需要 Docker 守护进程在运行。启动脚本会用 uv sync --locked 和 npm ci 同步锁定的依赖,并使用本机已装的运行时。和 CI 一样,uv.lock 与 pyproject.toml 对不上时它会失败,这时运行 uv lock 并提交结果。脚本可以在任何目录下直接调用:在仓库里用 ./scripts/ci-local.sh [all|backend|database|frontend],在别处用它的绝对路径。默认的层级是 all。
后端检查包括 Ruff 格式和 lint 检查、pydocstyle、mypy,以及用四个 worker 和 --dist loadfile 跑的 pytest(不含 external 和 dbtest)。数据库层级会在一个随机的回环端口上新起一个 PostgreSQL 15 容器,给四个 worker 各建一个数据库,退出或被中断时删除容器。只要有测试被跳过,或者测试总数不到 100 个,这一层就不算通过。前端检查包括 lint、格式检查、类型检查、测试,以及 server-only 和运行时站点配置这两项构建守卫。
每次运行都会把日志存进一个新建的 .local-ci/<tier>.XXXXXX 目录,这个目录已被 git 忽略。想换输出位置,就设置 CI_LOG_DIR。本地检查不包括外部回归测试、滚动升级、Gitleaks、Docker 发布镜像构建和 Kata 资格验证,这些仍由托管 CI 负责。
每个 pull request,不管来自 fork 还是本仓库的分支,都在用完即弃的 GitHub 托管虚拟机上运行:ARM64 产物用 ubuntu-24.04-arm,其余都用 ubuntu-24.04。仓库是公开的,这些 runner 不占分钟配额。推送到 dev 和 main、以及经过授权的手动触发,仍然使用自托管的 runner 池,发布和同步也在这些池上跑。PR 拿到的只有一个只读令牌,checkout 不会把它保存下来,镜像任务从镜像仓库匿名拉取,所有 PR 任务都拿不到 secret。发布不和其中任何一个共用构建器:每个发布单元都在自己的 RUNNER_TEMP 下新建构建器,任务结束就删掉。发布唯一会复用的层来自它自己的镜像仓库缓存,也就是产物包里的 buildcache-<artifact>-<ISO week> 标签,只有发布工作流会写它。这个标签每周换一次名,所以每周的第一次发布都是冷构建。真实 Kata 主机的资格验证不会在任何 PR 里跑,只在受信任的 dev 事件上运行。
pull request 只读取 Actions 缓存,不写入。每次推送到 dev 后,可选的 pr-caches.yml 工作流会在一台托管虚拟机上填好 uv 和 npm 缓存,并导出沙箱镜像的构建层,存在 dev 作用域下,所有合入 dev 的 pull request 都能恢复使用。它和 CI 分开运行:发布不用等它,它失败了也不影响 CI 的结果。真实 CLI 的 MCP 用例共 15 个:pull request 中,Kilo 的三个各自单独作为一个 MCP conformance (kilo, …) 任务运行,其余十二个放在沙箱镜像任务里;受信任的事件则把 15 个都放在沙箱镜像任务里跑。
pull request 还会把默认测试层级、PostgreSQL 层级和前端单元测试各拆到两台托管虚拟机上,显示为 Tests (1/2) 这样的名字。pytest 由 scripts/ci_shard.py 按整个文件分配到各个分片,vitest 则用 --shard 自己拆分。受信任的事件每个层级只跑一个分片。想在本地跑某一个分片,把它分到的文件传给 pytest:
uv run pytest -m "not external and not dbtest" \
$(uv run python scripts/ci_shard.py --shard 1/2 -- -m "not external and not dbtest")
剩下需要管理员完成的工作记录在 GitHub issue #464:配置下面说的 runner 组限制,并用一个不受信任的 fork 验证。
因为仓库是公开的,fork 发来的 pull request 也能运行工作流。所以要配置 GitHub 的 runner 组,让持久化的 runner 池只能被 refs/heads/dev 和 refs/heads/main 上受信任的工作流引用使用,其中包括发布和手动同步工作流;PR 的 merge/head 引用和其他工作流文件一律拒绝。这些池目前注册在仓库级别,仓库级别没有这种限制,所以得先把它们挪到组织级的 runner 组里。在此之前,所有外部贡献者的 fork 工作流都必须经过批准,而不只是第一次贡献的人。验证方法是:在一个不受信任的 fork 里修改工作流,让它申请持久化 runner,结果必须是申请不到。fork 可以随意改自己的工作流 YAML,所以仓库里写的 runner 表达式本身拦不住它。如果组织无法按工作流引用限制 runner 组,就把这些持久化 runner 从仓库可用的组里移除,把受信任的 CI 和发布放到另一个受限的仓库里跑,或者换成不带凭据、只跑一个任务就销毁的 runner。光靠 fork 的批准设置是不够的:批准一次测试运行,绝不能顺带给出发布主机或发布凭据的访问权限。这些都是 GitHub 的管理员设置,仓库里的测试既配置不了,也验证不了。
默认的后端测试层级在 CI 和 make test 中都用四个 worker,同一个测试文件始终在同一个 worker 上。需要时可以改 worker 数量;设为零就关闭并行,方便调试:
make check PYTEST_WORKERS=0
用 marker 选择要跑的测试层级。例如 tests/servers/ 里是 HTTP 层面的测试,不需要真正部署服务器。标了 external 的测试会在测试模块里写明前置条件,可能需要 Docker、真实的 CLI 或配置好的网关。除了默认层级,CI 还会挑一部分外部检查来跑。
如果改了数据库,就用一个专门的测试数据库跑 make test-db。测试夹具会读取 TEST_DB_HOST、TEST_DB_PORT、TEST_DB_NAME、TEST_DB_USER 和 TEST_DB_PASSWORD,其中 TEST_DB_NAME 必须包含 _test_。跑完要看一下有没有被跳过的测试:数据库连不上时,夹具可能直接跳过,而不是报错。并行的 worker 各自需要一个事先建好的数据库。默认值和 worker 的命名规则见 ENVIRONMENT.md。
部署相关的测试会读取沙箱 CLI 的版本设置。如果你的开发工具导出了另一个 CODEX_VERSION,跑检查时把这个继承来的变量去掉:
env -u CODEX_VERSION make check
改了前端,就在仓库根目录运行下面这些检查:
cd apps/frontend
npm ci
npm run lint
npm run format:check
npm run type-check
npm run test
CI 还会跑下面两个构建守卫。如果你改了 server-only 导入或运行时站点配置,在本地跑对应的那个:
node scripts/check-server-only-guard.mjs
node scripts/check-runtime-site-config.mjs
浏览器恢复的回归测试会在 Chromium 里跑生产构建的 Next.js 应用:
npx playwright install chromium
npm run test:browser
请在测试主机上一份单独的仓库副本里,用 Node 22 运行这些命令。Chromium 依赖的系统库要事先装好,这条命令不会安装系统软件包。可以用 PLAYWRIGHT_BROWSERS_PATH 把浏览器下载到这份副本里用完即删的测试目录。测试运行器会构建根路径的 Web 变体,自己分配回环端口,跑完后关掉它起的服务器。不要同时对同一个 .next 目录再跑一次构建。
这些测试会真实地切换页面、让浏览器离线,并模拟 JavaScript 分块下载失败。另有一个独立的 HTTP 测试桩,按测试的设定返回 API 响应,也能在记录下一次创建请求之后故意延迟或截断响应。遇到没有预设的 API 请求,整个测试套件直接失败。这些测试覆盖的是浏览器端的恢复,以及客户端和 API 之间的交互;Python 控制平面、数据库、身份认证服务和沙箱主机都不在范围内,它们仍然要靠各自的集成测试和 staging 检查。
主机的网络地址一变,Chromium 就会以 net::ERR_NETWORK_CHANGED 中止还在等连接的请求。在共享的 CI runner 上,别的任务启动或停止容器时就会出现这种情况。测试失败的那次尝试如果遇到了这个错误,就会再跑一次,并报告为 flaky。其他原因导致的失败,重试会直接失败,所以这类失败照样会让整个测试套件失败(apps/frontend/e2e/network-change.ts)。
测试失败时,截图和 Playwright trace 会留在 apps/frontend/test-results/,HTML 报告留在 apps/frontend/playwright-report/,CI 会把两者都上传。根路径构建成功之后,如果只是在改测试桩或测试用例,可以用 CLOUD_AGENT_E2E_SKIP_BUILD=1 npm run test:browser 反复跑;改了应用代码就要重新构建。
make ci-frontend 会跑上面所有检查,浏览器恢复测试也在内。如果改动涉及镜像,托管 CI 还会构建根路径和 /agents 两个 Web 变体并做冒烟测试。完整的 CI 检查见 CONTRIBUTING.md。
修改 API
gateway_contracts/ 里有两类不同的 HTTP 契约:
*.openapi.yaml描述本服务调用的网关 API。客户端开始读取新字段或调用新端点时,要去网关源代码里核实,并在同一个 PR 里更新这份文档。agent-control-plane.openapi.json记录本服务对外提供的 API。有意修改 API 时,要在同一个 PR 里更新这份快照,并经过评审。
在仓库根目录重新生成控制平面的快照:
uv run python - <<'PY'
import json
from pathlib import Path
from cloud_agent.app import create_app
Path("gateway_contracts/agent-control-plane.openapi.json").write_text(
json.dumps(create_app().openapi(), sort_keys=True, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
PY
uv run pytest -q tests/unit/test_api_surface_contract.py tests/unit/test_contracts.py
仔细看一遍生成的 diff,并说明它对兼容性有什么影响。接口测试还会拦下重复注册,以及 agent 接口路由之间的重叠。快照只是记录了改动;改动是否兼容,要靠行为测试和评审来判断。契约指南讲了这些检查能验证什么,以及哪些地方仍需对照网关源代码确认。
打包与文档
新增或移动包时,要检查 pyproject.toml 里的包发现、mypy 和 linter 的根目录、Docker 的 COPY 路径,以及 .github/workflows/ci.yml 里的路径分类器。还要确认构建出来的镜像带齐了依赖:本地仓库里能导入,并不能说明镜像里也能。CI 会构建并启动部分镜像,分类器的测试会根据 Dockerfile 推出共用包的路径。
schema 改动要写成显式的增量 Alembic 修订。当前的迁移链从 migrations/versions/0001_schema_baseline.py 开始;不要在服务启动时建表。项目里没有可供自动生成迁移的 SQLAlchemy 模型元数据。如果数据库还标记着 baseline 之前的旧迁移链,请按重建手册处理。
改了配置、命令或行为,就更新负责它的那份指南:
主题 |
文档 |
|---|---|
环境变量和运行时版本固定 |
|
新增迁移 |
|
运维和故障排查 |
|
本地开发环境 |
|
发布产物与验证 |
这些指南组成了开发者文档站,用 Sphinx 和 MyST 从 docs/developer/ 构建。新写的指南放在这个目录下,并加进 docs/developer/index.md 里的某个目录树。ENVIRONMENT.md 这类放在仓库根目录的指南,通过一个只有一行 {include} 的页面展示,这个页面要在 docs/developer/conf.py 的 INCLUDED_PAGES 里注册。指向仓库里其他文件的链接可以继续用相对路径:构建时会把它们换成 GitHub 链接,路径或标题不存在就会报错。改了任何页面都要跑 make docs-verify,CI 的 Docs Build 任务跑的是同一个目标。本站还发布简体中文版,所以改了英文也要更新对应的译文,见翻译文档。
docs/agents/ 下的设计、已完成的迁移清单和验收记录,描述的都是它们写成时的状态。这些历史请保持原样;实现变了,就去更新当前在用的指南。