Agent Infra - Cloud Agents V3
启动 Session
- 创建、运行、查看和归档 Cloud Agent Sessions
- Session 是 Agent 的运行工作区
- 它把 Agent 快照、Environment、可选资源和可选 Vault 凭证绑定在一起
- 新 Session 初始为
idle,发送事件后开始执行
Session 状态生命周期
Session 是一个状态机。Session 资源上的 status 字段取以下值之一:
| 状态 | 说明 | 可流转到 |
|---|---|---|
idle |
Session 空闲,可以发送消息 | running、rescheduling、terminated |
running |
Agent 正在处理 turn | idle、rescheduling、terminated |
rescheduling |
底层运行时正在重新调度,期间 Session 不可用,恢复后回到 idle |
idle、terminated |
terminated |
Session 已终止(终态) | — |
1 | stateDiagram-v2 |
另外两个生命周期标记不出现在 status 字段中:
- 已归档(archived):通过非空
archived_at时间戳标识status字段本身不会变成archived- Session 仍可被读取,但拒绝新事件
- cancel 响应:
POST /api/v1/cloud/sessions/{id}/cancel接口的响应体始终是固定字面量"status": "canceling"- 这只是响应结构,不是 Session 持久化的 status
- turn 中止后 Session 的
status仍回到idle
| 步骤 | 描述 |
|---|---|
| 创建 → idle | 新 Session 进入 idle,等待输入 |
| idle → running | 发送 user.message 事件后,状态切换到 running |
| running → idle | 本轮完成后回到 idle,可继续下一轮 |
| running → idle(cancel 后) | 取消正在执行的 Session 会中断当前 turn,Session 回到 idlecancel 接口响应体使用固定 "status": "canceling" 字面量,与持久化的状态无关Session 仍可继续使用 |
| rescheduling | 运行时可能临时进入 rescheduling,重新调度完成后会回到 idle |
| archived / terminated(终态) | 归档(通过 archived_at)或终止后 Session 永久结束,无法恢复 |
Cancel 语义
1 | flowchart TB |
- 对
idleSession 调用 cancel:空操作(no-op),返回 HTTP200,状态保持idle - 对
runningSession 调用 cancel:中断 Agent,返回 HTTP202。状态先变为canceling,中断完成后回到idle - cancel 后:Session 仍可复用 - 直接发送下一条
user.message即可开始新 turn
取消当前 turn
1 | curl -s -X POST "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID/cancel" \ |
只有 archived 和 terminated 是终态。取消后的 Session 总会回到 idle,可以继续接受消息。
向 running Session 发消息(409 错误)
如果向正在 running 的 Session 发送 user.message,API 返回 HTTP 409:
1 | { |
1 | flowchart TB |
这是新用户最常踩的坑。请始终等待 session.status_idle 事件后再发送下一条消息,或者先 cancel 当前 turn。
创建 Session
使用已有 agent 和 environment_id 创建 Session:
1 | curl -s -X POST "https://api.qoder.com/api/v1/cloud/sessions" \ |
- 创建响应是 Session 对象,包含
agent、environment_id、status、resources、vault_ids、deployment_id、outcome_evaluations、stats、usage、environment_variables、archived_at、created_at和updated_at usage.total_credits是 Session 中已记录模型调用消耗 credits 的累计快照,向下取整并最多保留 2 位小数
创建时挂载资源
通过 resources 数组挂载文件、仓库和 Memory Store:
1 | { |
- 创建后追加文件请调用 添加 Session 资源 API
- 当前 CAS 创建后追加仅支持 file resource
- resource list/get/update/delete 接口可用于查看、轮转 GitHub token 或移除资源
发送消息
通过 Events API 发送 user.message。content 必须是非空 content block 数组:
1 | curl -s -X POST "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID/events" \ |
- send-events 接口返回 HTTP 200 和
{"data":[...]} - 可发送事件类型包括:
user.message、user.interrupt、user.tool_confirmation、user.tool_result、user.custom_tool_result和user.define_outcome
读取事件
使用事件流实时读取:
1 | curl -s -N "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID/events/stream" \ |
- 事件流以 Server-Sent Events 输出
id、event和data - Stream endpoint 支持
Last-Event-IDheader 进行断线重连 - 事件类型 query filter 当前不支持
使用 list endpoint 读取历史和分页:
1 | curl -s "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID/events?limit=20&order=desc" \ |
列表响应使用 data 和 next_page。
读取和更新 Sessions
获取一个 Session
1 | curl -s "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID" \ |
列出 Sessions
1 | curl -s "https://api.qoder.com/api/v1/cloud/sessions?limit=20" \ |
更新 title、metadata 或 Agent 配置(tools、MCP servers)
1 | curl -s -X POST "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID" \ |
Session list 使用 page / next_page 分页,并支持 agent_id、agent_version、deployment_id、memory_store_id、statuses 和 created_at[...] 等过滤。
Threads
- Managed-agent Session 可以包含协调器主线程和子线程
- Thread endpoint 使用公开
session_thread结构,不再包含role、name、agent_id、agent_version或stop_reason等旧字段
1 | curl -s "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID/threads" \ |
- 子线程可以通过
POST /api/v1/cloud/sessions/{session_id}/threads/{thread_id}/archive归档 - 当前 CAS 对协调器主线程归档返回
409
Session 与 Threads 架构关系
1 | flowchart TB |
| 概念 | 角色 | 特性 |
|---|---|---|
| Session | 运行工作区容器 | 绑定 Agent、Environment、资源 |
| 协调器主线程 | 任务协调与汇总 | 接收消息、分解任务、汇总结果、归档返回 409 |
| 子线程 | 并行执行具体任务 | 由主线程调度、可独立归档 |
工作流程:
- 用户发送
user.message到 Session - 协调器主线程接收并分解任务
- 创建多个子线程并行执行子任务
- 子线程完成后,主线程汇总结果
- 通过
agent.message返回给用户
生命周期
归档 Session:
1 | curl -s -X POST "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID/archive" \ |
删除 Session:
1 | curl -s -X DELETE "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID" \ |
删除返回:
1 | { |
- cancel 接口返回
{"id":"...","type":"session","status":"canceling"} - 当存在活跃 turn 需要取消时返回 202 Accepted
- 当 Session 已处于
idle时为幂等 no-op,返回 200 OK
多轮对话工作流
Session 支持多轮对话。推荐流程如下:
1 | flowchart LR |
- 发送
user.message事件 - 监听 SSE 事件流
- 等待
session.status_idle事件 - 发送下一条
user.message
1 | #!/bin/bash |
- 请始终等待
session.status_idle后再发送下一条消息 - 在 Session 仍处于
running时发消息会返回 HTTP 409
最佳实践
| 最佳实践 | 描述 |
|---|---|
| 版本锁定 | 生产环境始终使用 {"id": ..., "type": "agent", "version": ...} 形式创建 Session,避免因 Agent 更新导致行为变化 |
| 元数据标记 | 用 metadata 记录业务上下文(任务 ID、触发来源等),便于追溯和调试 |
| 及时取消 | 不再需要的 Session 及时 cancel,释放计算资源 |
常见问题
Q: 向 running 状态的 Session 发消息会怎样?
A: 会返回 HTTP 409,type: "invalid_request_error",错误信息为 “Session is currently processing a turn. Cancel the current turn or wait for completion.”。需要先取消当前轮(cancel)或**等待 Session 回到 idle,再发送新消息。
Q: 取消后的 Session 还能继续用吗?
A: 可以。cancel 后状态从 canceling 自动回到 idle,Session 保持可用 - 直接发送下一条 user.message 即可继续对话。仅 archived 和 terminated 是终态。
Q: 如何获取 Session 的完整对话历史?
A: 通过 GET /api/v1/cloud/sessions/{id}/events 获取该 Session 的所有事件,包括用户消息和 Agent 响应。
Q: SSE 断线怎么重连?
A: 重连时在请求头中传入 Last-Event-ID,服务端会从该 ID 之后重放事件。
Q: GET /api/v1/cloud/environments 返回空数组?
A: 请检查你的 PAT 或 SAT 是否具有目标工作区的权限。Environment 的访问范围取决于认证主体的权限。
SSE 事件流
Qoder Cloud Agents 通过 Server-Sent Events (SSE) 流式输出 Session 公开事件
多连接模型
1 | flowchart TB |
关键特性:
- 同一 Session 支持多个并发 SSE 连接
event_deltas[]参数只对当前连接生效- 不同连接可以有不同的增量配置,互不影响
典型场景:
- Web UI:请求
agent.message增量,实现流式显示 - CLI 监控:只收完整事件和状态变化
- 日志服务:请求
agent.thinking增量,记录推理过程
连接 URL
1 | GET https://api.qoder.com/api/v1/cloud/sessions/{session_id}/events/stream |
请求头:
1 | Authorization: Bearer $QODER_ACCESS_TOKEN |
- 默认情况下,Agent 响应会在生成完成后,以完整公开事件(例如
agent.message)的形式写入 Session 事件历史并通过 stream 输出。下文将这种完整事件称为 buffered 事件。 - Stream endpoint 支持使用
Last-Event-IDheader 断线重连。对于普通 buffered 事件,stream 从该 ID 之后继续;在途 event delta 的特殊行为见下文。 - 使用
event_deltas[]可在当前 stream 连接中增量接收 Agent 响应。重复传入该参数可同时请求两种支持的事件类型:agent.message+agent.thinking - 该选项只对当前 stream 连接生效,不会影响同一 Session 的其它连接。不传
event_deltas[]时,连接只会在响应完成后收到完整事件。Thread event stream 不支持该参数。
1 | GET /api/v1/cloud/sessions/{session_id}/events/stream?event_deltas[]=agent.message |
SSE 格式
每条事件使用标准 SSE 字段:
1 | id: evt_019e392c0d787cfaa21bda98e06cd913 |
服务端可能发送 heartbeat comment 以保持连接。
Event Delta
增量输出机制
1 | flowchart TB |
关键特性:
- agent.message:
event_start→ 多个event_delta→agent.message(完整) - agent.thinking:
event_start→agent.thinking(完整,无内容暴露) - 所有帧使用相同 ID(
evt_...)
agent.message 的增量输出以 event_start 开始,随后输出一个或多个 event_delta:
1 | id: evt_00jjujk9fbnr4wkj2gh8 |
agent.thinking 的增量输出只有开始事件,不会输出 event_delta:
1 | id: evt_00jjujk9fbnr55q5rtyp |
对于同一个 message 或 thinking 事件,SSE id:、event_start.event.id、每个 event_delta.event_id 以及后续 buffered 事件的 id 完全相同。
完整事件流顺序
同时请求两种事件类型时,典型顺序如下:
1 | flowchart LR |
Event delta 帧的 JSON payload 不包含顶层 id 或 processed_at 字段,也不会出现在事件 list/history 响应中。
Event Delta 的本质:
- ❌ 不是完整事件:缺少标准事件字段(
id、processed_at)- ❌ 不保存到历史:不会出现在事件 list/history 响应中
- ✅ 仅用于流式传输:只存在于 SSE 实时流中
设计目的:
- 📺 流式显示:支持实时打字效果(类似 ChatGPT)
- ⚡ 降低延迟感:用户无需等待完整响应生成
权威结果:
- 只有最终的 buffered
agent.message才是完整事件- 它会被保存到事件历史,可用于重放和查询
buffered agent.message 是权威结果,agent.thinking 不会暴露思考内容。
Event Delta 期间重连
具体场景示例
核心概念:
event_start是 Agent 开始生成一个新agent.message时发送的开始标记事件。下面的三种场景都是相对于同一个正在生成中的 message 的event_start来说的。
断线重连时使用 SSE Last-Event-ID header。具体行为取决于游标位置,以及当前 message 是否仍在生成:
🟢 场景 1:游标在 event_start 之前
1 | flowchart LR |
特点:完整重建,重放所有历史增量
🟡 场景 2:游标等于 event_start.id
1 | flowchart LR |
特点:节省带宽,只获取新增内容
🔵 场景 3:Message 已生成完成
1 | flowchart LR |
三种场景对照表
| 场景 | 游标位置 | Message 状态 | 服务端行为 | 用途 |
|---|---|---|---|---|
| 🟢 场景 1 | 在 event_start 之前 |
仍在生成 | 重放 event_start + 历史 delta + 新 delta + buffered |
需要完整重建增量事件 |
| 🟡 场景 2 | 等于 event_start 的 ID |
仍在生成 | 只输出新生成的 delta + buffered(不重放历史) |
节省带宽,只获取新增内容 |
| 🔵 场景 3 | 任意位置 | 已生成完成 | 只输出 buffered 事件(无 delta 重放) |
跳过增量,直接获取最终结果 |
客户端处理策略
同一个增量事件的所有帧使用相同 ID。根据重连需求选择策略:
| 需求 | 推荐做法 | Last-Event-ID 设置 |
|---|---|---|
| 完整重建增量事件 | 游标回退到 event_start 之前的 buffered 事件 |
使用 evt_001(event_start 之前的 ID) |
| 继续接收新增量 | 使用当前 event_start.event.id 作为游标 |
使用 evt_002(event_start 的 ID) |
| 跳过增量获取结果 | 等待生成完成后再重连 | 使用任意已接收的 ID |
⚠️ 去重提醒:同一 ID 的
event_delta可能在场景 1 中重复接收,建议客户端根据event_id进行去重处理。
常见事件流
1 | session.status_running |
一个 Turn 的完整事件时序
1 | sequenceDiagram |
事件分组速查
| 阶段 | 事件 | 说明 |
|---|---|---|
| Turn 开始 | session.status_running → session.thread_status_running → user.message |
状态切换 + 服务端回显用户消息 |
| 模型请求 span | span.model_request_start … span.model_request_end |
一对 start/end 包裹一次模型调用,end 可含 model_usage.credits |
| Agent 活动 | agent.thinking / agent.tool_use / agent.tool_result / agent.message |
thinking 与 tool 调用可交替出现多轮,agent.message 是最终回复 |
| Turn 结束 | session.thread_status_idle → session.status_idle |
回到 idle,可发送下一条消息 |
- 并非每一轮都会包含全部事件
- Managed-agent Session 还可能产生
session.thread_created、session.thread_status_running、agent.thread_message_sent、agent.thread_message_received等 thread 事件
模型请求 span
span.model_request_start包含id、processed_at和type- 与之配对的
span.model_request_end包含id、is_error、model_request_start_id、processed_at和type,其中model_request_start_id等于对应 start 事件的id - 本次模型调用有 credits 数据时,end 事件还包含
model_usage: {"credits": 1.25};credits向下取整,最多保留 2 位小数 - 说明:公开的
agent.thinking事件 payload 只包含id、processed_at、type三个字段,思考内容不对外公开- 把该事件当作”Agent 暂停推理“的标记即可
- 其它若干 agent.* 事件也可能省略
processed_at,解析时请将其视为可选字段
连接生命周期
| 生命周期 | 描述 |
|---|---|
session.status_idle |
表示当前 turn 结束,连接应当保持,等待下一轮 |
session.status_terminated 和 session.deleted |
是终态事件 - 客户端应停止重连,不会再有更多事件 |
session.status_rescheduled |
是临时信号,连接可能短暂断开,运行时恢复后会自动重连 |
| 网络中断时使用 Last-Event-ID header 重连;在途增量事件的特殊处理见 Event Delta 期间重连 |
工具响应
当事件流产生需要确认的 agent.tool_use 时,向 POST /api/v1/cloud/sessions/{session_id}/events 发送 user.tool_confirmation,并使用工具事件 ID:
1 | { |
当事件流产生 agent.custom_tool_use 时,由客户端执行自定义工具,并发送 user.custom_tool_result。
事件历史
历史事件和分页请使用 list endpoint:
1 | curl -s "https://api.qoder.com/api/v1/cloud/sessions/$SESSION_ID/events?limit=20&order=desc" \ |
访问 GitHub
- 把 GitHub 仓库挂载到 Session 容器,让 Agent 直接读取、修改代码并提交 Pull Request
- Qoder Cloud Agents 支持把 GitHub 仓库作为 Session 的资源挂载到容器中
- Session 启动后,平台会自动 clone 仓库到指定路径,Agent 可以像在本地工作树中一样读取、修改、提交、推送代码,并配合 gh CLI 创建 Pull Request
- 仓库资源的归属与 Session 一致
- Session 归档后挂载关系一并失效,Session 期内若需要替换仓库或修改克隆路径,必须创建新的 Session
核心流程
- 准备 GitHub 令牌
- 生成 GitHub Personal Access Token(推荐使用 fine-grained PAT),授予仓库读取/写入、Pull Request 等所需权限。该 token 是 GitHub 仓库资源的必填字段。
- 创建 Session 时挂载仓库
- 在创建 Session 的
resources数组中加入type: "github_repository",把仓库 URL 与 PAT 一并传入。
- 在创建 Session 的
- Agent 在容器内访问代码
- Agent 启动后即可在挂载路径下读取代码,使用
Bash/Read/Write/Edit等工具修改文件。
- Agent 启动后即可在挂载路径下读取代码,使用
- (可选)创建 Pull Request
- Agent 在仓库目录中使用
git push推送分支,再调用 gh CLI 创建 Pull Request。
- Agent 在仓库目录中使用
文件持久化
- 仓库资源的克隆与容器临时存储共享同一份磁盘空间
- 容器临时存储在连续 24 小时未活跃后可能被回收;回收后再次唤醒 Session 时,平台会重新初始化容器并按需重新克隆仓库,磁盘上未提交的中间产物会丢失
- 请及时 commit 并 push,或通过 Files API 保存重要产物
仓库资源字段
GitHub 仓库资源使用以下字段;推荐在创建 Session 时一次性传入:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
string | 是 | 固定为 "github_repository" |
url |
string | 是 | 仓库 URL,例如 https://github.com/your-org/your-repo |
mount_path |
string | 否 | 克隆到容器中的路径。省略时按仓库名自动推断 |
authorization_token |
string | 是 | GitHub Personal Access Token,用于克隆与推送仓库 |
authorization_token仅在创建请求或 token 轮转请求中传入- 读取 Session 详情或 Session resources 时不会返回该字段
- 建议每个 Session 使用最小权限的 PAT,并在用完后吊销
创建 Session 时挂载 GitHub 仓库
调用 创建 Session 接口,在 resources[] 中传入仓库描述:
1 | curl -s -X POST https://api.qoder.com/api/v1/cloud/sessions \ |
成功返回 HTTP 200 OK,resources 字段包含归一化后的挂载描述。响应中不会返回 token:
1 | { |
- Agent 命令默认 cwd 为
/data - 建议把
mount_path设为/data/workspace/<repo-name>,并在 system prompt 或用户消息中明确仓库路径。
挂载多个仓库
一次请求即可挂载多个仓库到不同 mount_path,例如同时挂载前端和后端代码:
1 | curl -s -X POST https://api.qoder.com/api/v1/cloud/sessions \ |
也可以与文件、Memory Store、Vault 等其他资源混用,详见 Sessions — 创建时挂载资源。
令牌权限模型
- GitHub 提供两种 PAT:fine-grained PAT(推荐)和 classic PAT
- 在 Qoder Cloud Agents 中使用时,按”最小权限原则“申请仅与本次任务相关的权限
推荐权限对照
下表给出常见 Agent 操作对应的 fine-grained PAT 权限。classic PAT 对应的是仓库范围的 repo scope。
| Agent 操作 | fine-grained PAT 权限(Repository permissions) |
|---|---|
| 克隆/读取私有仓库 | Contents: Read |
| 创建分支并推送 | Contents: Read & Write |
| 创建 / 评论 Pull Request | Pull requests: Read & Write |
| 读取 Issues | Issues: Read |
| 创建 / 评论 Issues | Issues: Read & Write |
| 读取仓库元信息(必备) | Metadata: Read |
- fine-grained PAT 可绑定到具体仓库(甚至特定的 organization 资源),相比 classic PAT 风险更低
- 建议为每个 Agent 任务申请独立的、短期有效的 PAT
安全建议
- 不要将包含
authorization_token的 Session 创建或 token 轮转请求写入日志、截图或版本库。Session 查询响应不会返回 PAT - 任务完成后及时在 GitHub 设置中吊销 PAT;fine-grained PAT 也支持设置较短的
Expiration - 即使是公开仓库也需要传入 PAT;建议为公开仓库使用只读、短期有效的 token,将权限暴露面降到最低
- 不同环境(开发 / 生产)使用不同的 PAT,便于审计
创建 Pull Request 工作流
- Agent 在挂载好的仓库目录中可以直接执行
git与gh命令 - 运行镜像已内置
git与 gh CLI,平台也会用仓库资源的authorization_token自动为容器配置GH_TOKEN,无需手动安装或导出环境变量 - 要让 Agent 完成”修改 -> push -> 创建 PR”全流程,只需:
- Agent 配置中启用
agent_toolset_20260401工具集,至少包含Bash、Read、Write、Edit - 在 user message 中清晰描述任务、仓库路径与目标分支
- Agent 配置中启用
下面是一个端到端示例,假设 Session ID 为 sess_019e5ce0bf9074b69c3481e93771a522、仓库挂载在 /data/workspace/your-repo:
1 | curl -s -X POST "https://api.qoder.com/api/v1/cloud/sessions/sess_019e5ce0bf9074b69c3481e93771a522/events" \ |
- 平台会用仓库资源的
authorization_token自动为容器配置GH_TOKEN,gh pr create在容器内可直接调用 - 如果该 PAT 没有
Pull requests: Read & Write权限,PR 创建会失败;申请 PAT 时请按上文 推荐权限对照 一并授予
配合 Agent 配置的最佳实践
- 在 Agent 的
tools中启用Bash、Read、Write、Edit、Glob、Grep,覆盖代码搜索与修改场景。 - 在 Agent 的
system提示中明确仓库挂载路径,例如:”你的仓库目录是/data/workspace/your-repo,所有 git 与gh操作都应在该目录下执行。” - 对长任务可以在 system prompt 中要求 Agent 在每轮结束时执行
git status自检,避免漏 commit。 - 如需跨 Session 复用产物,让 Agent 在每轮结束前用 Files API 上传关键产物(patch 文件、报告);沙箱被回收或重建后,未上传的中间文件可能丢失。
常见问题
Q: 仓库太大,克隆很慢怎么办?
A: 当前资源挂载使用完整 clone,资源字段里没有浅克隆开关。对于体积非常大的 monorepo,建议拆分任务范围,或使用 Files API 上传关键子目录/文件作为补充上下文。
Q: PAT 过期或被吊销了怎么办?
A: 后续 git/gh 操作会返回 401。需要创建新的 Session 并使用新 PAT;对于已挂载但尚未推送的修改,可以让 Agent 在 Session 内先 git diff 输出 patch,再用 Files API 上传备份。
Q: 是否支持 fork 私有仓库或访问 organization 内部仓库?
A: 支持,只要 PAT 对目标仓库具备 Contents: Read 权限即可。组织开启了 SSO 时,PAT 必须先经过 SSO 授权(Authorize 按钮)才能用于克隆。
Q: 是否支持 git submodule?
A: 仓库资源不会额外声明 submodule 字段。需要使用 submodule 时,让 Agent 在仓库目录中执行 git submodule update --init --recursive,并确保 PAT 对所有 submodule 仓库都有读权限。
Q: Session 运行中能换仓库吗?
A: 不能。一旦 Session 创建完成且某个仓库已挂载,挂载关系不会因为 update 调用而被替换。如需切换仓库,请创建新的 Session。
Q: Agent 修改的代码会自动推回 GitHub 吗?
A: 不会。除非 Agent 在任务中显式执行 git push,所有修改只存在于容器临时存储。请在 user message 中明确要求”push 分支”或”创建 PR”。
Q: GitHub Enterprise Server (GHES) 是否支持?
A: GitHub 仓库资源只暴露 url、mount_path 和 authorization_token 字段,没有单独的 GHES 配置字段。使用 GitHub Enterprise Server 时,请确认 GHES 端点对外可达,并确认对应 token 可被 gh/git 用于该主机。






