代码结构
这份指南讲各类行为该放在哪里,以及怎样在仓库里顺藤摸瓜找到它们。环境搭建和检查看 development.md,服务边界和安全边界看 AGENTS.md。
先看部署边界
apps/
backend/
cloud_agent/ Control plane and its HTTP APIs
cloud_agent_host/ Host execution and retained sandbox management
cloud_agent_shared/ Contracts and algorithms shipped in both images
frontend/ Next.js browser UI and backend-for-frontend routes
gateway_contracts/ Gateway HTTP contracts and control-plane OpenAPI
migrations/ Agent-owned database schema, managed by Alembic
deploy/ Image builds and deployment entry points
scripts/ Development, qualification, and release tooling
tests/ Backend behavior, integration, and boundary checks
docs/ Maintained guides and dated design evidence
控制平面和主机端是分开部署的两套实现,谁也不导入谁。共用代码只放双方都认可的事实和契约,本身也不导入任何一方的实现。和网关之间通过 HTTP 契约对接。Agent 数据库和网关数据库始终是分开的。
跟着一个控制平面请求走
flowchart TD
App[app.py: listener and route assembly] --> API[api/: HTTP adapters]
API --> Flows[application/ and services: orchestration]
API --> Ports[ports/: capability contracts]
Flows --> Ports
Flows --> Domain[domain/: rules and vocabulary]
Bootstrap[bootstrap/: resource and lifecycle assembly] --> Workers[workers/: background loops]
Workers --> Flows
Bootstrap --> Storage[storage/: Postgres implementations]
Storage --> Ports
Storage --> Domain
app.py 挂载所有者/管理员 API 和单独的 MCP 专用监听器,并装上统一的 HTTP 响应和异常处理策略。两个监听器上还都装了一个依赖,它排在每个路由自己的依赖之前(api/_parameters.py):路径或查询参数里带 NUL,或者路由的整数参数超出了 bigint 的范围,都会在任何查询读到它之前直接返回 422。由所有者、管理员或值班中继调用的路由,JSON 请求体里带 NUL 或者只有半个代理对,也一样拒绝;主机和沙箱的上报有自己的模型,会把这些字符替换掉,而不是拒绝。bootstrap/lifespan.py 负责创建资源,以及启动、停止后台任务;服务的启动和关闭不归路由模块管。
目录或模块 |
负责的内容 |
什么时候改这里 |
|---|---|---|
|
HTTP 解析、依赖注入、授权入口、响应映射 |
请求或响应的行为要变 |
|
Pydantic 请求和响应模型,以及主机/worker 之间的传输模型 |
传输字段或校验规则要变 |
|
准入、后续消息、Fork、投递、调和等业务流程 |
一个用例要协调好几项能力 |
|
执行状态、请求身份和业务术语 |
规则本身和持久化、HTTP 都无关 |
|
范围很窄、带类型的能力接口 |
调用方需要适配器提供新的操作 |
|
组装 Postgres repository 提供的各项能力 |
要把一项存储能力接入服务 |
|
SQL,以及事务和栅栏逻辑 |
持久化的查询或写入要变 |
|
定时任务和恢复循环 |
这项工作不依附于某个请求,要自己持续运行;队列和目标的恢复拖不住 attempt 租约的恢复 |
|
资源和生命周期 |
某个组件需要创建、启动或关闭 |
|
MCP 托管、传输、策略和进程内运行时 |
协议行为或上游会话行为要变 |
|
目标(goal)的编排和重试策略 |
目标的调度或进度策略要变 |
|
外部服务商的具体实现 |
某个服务商的对接方式要变 |
存储组装对外提供 store.host_pool、store.pending_messages 这样的能力。调用方的类型只针对自己用到的能力来写,既不导入 Postgres repository,也不绕过组装直接去拿它的连接池。应用流程可以沿用现有的 FastAPI 异常约定;文件挪了位置,并不代表这些接口已经和框架脱钩。
MCP 会话归 mcp/runtime.py 管:按需创建 host、带栅栏的会话回收、日常维护和关闭。中继的处理函数和 bootstrap 都直接调用这个运行时。立即发送(send-now)交接任务的保留和取消在 application/send_now.py;崩溃恢复仍然靠持久化的投递记录和恢复 worker。
API schema 按领域划分
公开的模型从 cloud_agent.schemas 导入,由它的 __init__.py 显式导出。实现代码在各领域模块之间直接互相导入,不要再绕回门面(facade)导入自己。
模块 |
包含的模型 |
|---|---|
|
任务、会话、队列、提问和目标 |
|
事件流、测量数据、文件、终端和 Git 视图 |
|
worker 请求、主机注册、心跳和管理员的主机控制 |
|
归档设置、管理员审计和对外公布的配置 |
|
从管理员设置页面发起的 Agent CLI 升级 |
|
代码托管平台、MCP 选择、身份和用户偏好 |
|
团队、成员、邀请和加入申请 |
|
私有辅助模块:主机和 worker 上报内容的上下限、Sandbox Manager 字段名兼容,以及提示词文本规则 |
做结构性重构时,模型名称和序列化结果都不能变。跨领域的类型注解在导入后必须能立即解析。有意修改传输格式时,要按开发者指南的说明同步更新契约。
跟着一次主机执行走
runner.py 负责认领任务、attempt 租约、执行轮次、心跳和排空。claimed_job.py 定义认领的数据结构和租约错误;control_plane_client.py 负责以 attempt 为范围的 worker HTTP 调用、请求超时和事件重放。runner 会显式地把这两个模块原有的公开名称再导出一遍,客户端的创建仍留在 runner 的编排层。
chat_sandbox_models.py 里放的是保留容器的规格、检查和状态值、归属标签,以及身份字段的上下限。沙箱组的网桥、中继成员关系、Kata guest 路由同步,以及网络的证明和释放,都放在单独拆出来的 chat_sandbox_network.py 组件里。这个组件只依赖一个很窄的容器协议;精确的容器身份、有限制的 exec,以及出错就停的操作仍然由 manager 负责。manager 原有的网络入口都转交给这个组件处理。网络快照仍只在一次操作内有效,运维日志的名称保持不变。chat_sandbox.py 管理保留的沙箱组和组内成员;容器和进程之间的边界归 sandbox.py。原生会话和工作区操作由 Sandbox Manager 和会话 supervisor 共同协调。supervisor 的元数据锁只保护内存里的归属关系。停止、孤儿恢复和归档删除在动 Docker 或读写标记文件之前,会先占住这一个沙箱组的生命周期;拿到结果后,先重新核对组、成员、容器和生命周期的身份,核对通过才采纳。其他沙箱组不受影响。核对失败会释放临时占用,但持久的归档屏障和原生刷新屏障依然有效。原来通过 chat_sandbox 导入公开沙箱模型的写法照样可用。
运行时适配器是 cloud_agent_host/runtimes/ 下的一个包:
模块 |
职责 |
|---|---|
|
适配器契约、能力声明、MCP 配置和运行时错误 |
|
统一的事件类型,以及控制数据大小的辅助函数 |
|
把部分回复攒成批次做预览 |
|
某一种 CLI 的命令格式和事件解析 |
|
通用的按行解析的适配器 |
|
查找和创建适配器 |
已有的调用方都通过 cloud_agent_host.runtimes 使用适配器,这个包会显式导出公开 API。运行时的发现信息放在不可变的 cloud_agent_shared/runtime_catalog.py 里,所以控制平面不用加载主机端的适配器,也能对外公布运行时 ID、是否支持 MCP 和可选的推理强度。运行时的版本固定仍在 cloud_agent_shared/shared_protocol.py 和镜像文件里。
跟着一次浏览器操作走
Next.js 的路由文件在 apps/frontend/src/app/ 下。各功能共用的界面在 components/features/ 下:agents 是任务和对话,settings 是设置页面,另外还有 teams、profile 和 auth。可复用的展示组件在 components/ 下,浏览器端的工具函数和 API 客户端在 lib/ 下。只能在服务端运行的集成放在现有的服务端模块里;浏览器组件的导入链里绝不能带进服务端凭据。
lib/api/agents.ts 是显式的公开 API 门面,具体工作交给 lib/api/agents/ 下各管一摊的模块:任务、对话、会话、目标、配置、集成、资源和用量查询、工作区文件、终端和事件流。创建和 Fork 共用的幂等响应处理只在这个包内部使用。端点路径、重试、凭据、游标处理和通知,都跟着它所属的那个操作放。
任务页面由 components/features/agents/JobDetail.tsx 组装而成,拆出去的实现都在 job-detail/ 下:
模块 |
职责 |
|---|---|
|
页面顶部和对话相关操作 |
|
对话记录的呈现和导航 |
|
消息输入框和输入设置 |
|
运行结果、发布和 Git 交互 |
|
工作区和检查器面板的渲染与延迟加载 |
|
面板的选择、焦点、尺寸调整,以及保留的面板状态 |
|
轮次导航、对话记录滚动和阅读位置 |
|
停止、Fork 和恢复操作,带防重复请求的保护 |
状态放在负责它的那一块里。保留的面板在隐藏时也会留住终端、文件预览和草稿;切换作用范围时,必须把相关状态重置掉。拆组件时,hook 的依赖要写明,延迟加载也要保留。
加功能时别把结构弄乱
对照上面的几张表,确定这项行为归哪个服务、哪一块负责。
领域规则、传输模型和 port,确实需要时才新增或修改。持久化操作在 storage 里实现。
编排逻辑放在应用流程或负责它的服务里,再由 HTTP 适配器或 worker 去调用。新的资源通过 bootstrap 接入。
门面的公开导出要想清楚再加。包内部直接导入实现模块,这样依赖关系一目了然。
测试要覆盖外部可观察的行为和受影响的边界。做结构性重构时,比对前后的契约,并复用已有的行为测试。
跑完相关的完整检查;如果职责归属、命令或行为变了,就更新对应的维护中指南。
后端的架构检查会确保:服务镜像彼此独立、API 的导入方向正确、domain 和 port 不依赖其他层、repository 被隔离。后端导入必须用安装后的包名(cloud_agent、cloud_agent_host 和 cloud_agent_shared),不能用仓库里的命名空间 apps.backend。主机镜像会把这些包展平,带仓库前缀的导入在本地也许能用,部署后就会失败,所以三个包里都会拦下这种写法。导入扫描会覆盖相对导入、函数内部的导入和 TYPE_CHECKING 块。后端的行为测试放在 tests/ 下;前端测试和它测试的功能或工具函数放在一起。