Skip to content

任务与批次 ​

1. 提交任务 ​

http
POST /v1/jobs
Authorization: Bearer zqa_live_xxx
Idempotency-Key: order-20261109-001
Content-Type: application/json
json
{
  "batchId": "sprint-42",
  "items": [
    {
      "kind": "ai",
      "projectId": "proj_order",
      "prompt": "检查 src 下所有 TODO 并生成 reports/todo.md",
      "mode": "auto",
      "priority": 10,
      "maxAttempts": 4,
      "externalId": "TASK-1024",
      "metadata": { "owner": "team-a" },
      "callbackUrl": "https://team.cd-zq.com/api/zqagent/webhook"
    }
  ]
}

字段 ​

字段必填说明
kind✓ai(跑一轮 agent)或 script(跑一条命令)
projectId✓目标工作区,从 GET /v1/projects 取
nodeId不填则按 projectId 反查;跨节点重名时必须填
promptkind=ai任务描述(≤8000 字符)
commandkind=script命令(≤8000 字符)
timeoutSeconds仅 script,1–300,默认 60
modeauto(默认)/ discuss / plan / interactive。无人值守请用 auto
priority-100–100,数值大者先跑
maxAttempts1–4,默认 4
externalId你系统里的任务 id,原样回传(≤128)
metadata任意 JSON,原样回传(≤2KB)
callbackUrl覆盖 Key 的默认回调地址

响应 ​

json
202 Accepted
{
  "data": {
    "batchId": "sprint-42",
    "accepted": 1,
    "items": [
      { "id": "job_9f2c", "externalId": "TASK-1024", "status": "queued",
        "nodeId": "node_be", "projectId": "proj_order" }
    ]
  },
  "requestId": "req_..."
}

注意:202 只代表已受理。真正的完成信号来自 webhook 或轮询详情。

整批原子性 ​

任一条不合法(缺 prompt、projectId 不存在、节点离线……)→ 整批 400,一条都不入队。 这样你不需要处理"一半成功"的中间态。

幂等 ​

同一 Idempotency-Key 在 24 小时内重复提交,返回首次响应(HTTP 200 而非 202),不会重复建任务。

为什么必须用:agent 会真实改文件、跑命令。网络抖动时如果你的系统自动重试, 没有幂等键就可能执行两遍。

建议:直接用业务单号(order-20261109-001)。

批次 id ​

不传 batchId 时服务端自动生成(batch_xxxx)。 同一次提交的所有任务共享它,之后可用 GET /v1/batches/{batchId} 一次拿到整批进度。

2. 查询任务 ​

列表 ​

http
GET /v1/jobs?status=failed&projectId=proj_order&limit=20
参数说明
statusqueued / running / done / failed / cancelled
nodeId / projectId按节点/项目过滤
batchId按批次过滤
externalId按你系统里的任务 id 精确定位(最常用)
limit默认 50,上限 200

按时间倒序返回,nextCursor 为上一页最后一条的 queuedAt。

推荐用法:用 externalId 精确定位单条,而不是翻页找。

详情 ​

http
GET /v1/jobs/job_9f2c
json
{
  "data": {
    "id": "job_9f2c",
    "externalId": "TASK-1024",
    "kind": "ai",
    "status": "failed",
    "nodeId": "node_be",
    "nodeLabel": "后端构建机",
    "projectId": "proj_order",
    "batchId": "sprint-42",
    "title": "检查 src 下所有 TODO 并生成 reports/todo.md",
    "attempt": 2,
    "maxAttempts": 4,
    "priority": 10,
    "queuedAt": "2026-11-09T10:05:00Z",
    "startedAt": "2026-11-09T10:05:12Z",
    "finishedAt": "2026-11-09T10:12:03Z",
    "durationMs": 411000,
    "waitMs": 12000,
    "nextRetryAt": "2026-11-09T10:14:00Z",
    "sessionId": "sess_x1",
    "result": {
      "summary": "已生成 reports/todo.md,共 23 项",
      "producedFiles": ["reports/todo.md"],
      "usage": { "inputTokens": 21000, "outputTokens": 3400 },
      "iterations": 12,
      "toolCalls": 18
    },
    "failure": {
      "type": "llm_error",
      "message": "本轮未正常完成(error):RateLimitError",
      "lastError": "429 rate limited"
    },
    "blocked": null,
    "progress": {
      "toolCalls": 18,
      "tokensIn": 21000,
      "tokensOut": 3400,
      "iterations": 12,
      "lastActivity": "调用工具 write_file",
      "updatedAt": "2026-11-09T10:11:58Z"
    },
    "metadata": { "owner": "team-a" }
  },
  "requestId": "req_..."
}

关键字段 ​

字段用途
attempt / maxAttempts第几次尝试;attempt < maxAttempts 说明还会重试
nextRetryAt已安排自动重试时,下次开始时间
failure.type机器可读的失败分类,见下节
failure.lastError自动重试时上一轮的原因(message 是当前轮的)
blocked排队中且目标节点离线时出现,见下节
sessionId在控制台点进去可看完整执行过程(工具调用、改动文件)
result.producedFiles本轮改动的文件(工作区相对路径)
progress运行进度,节点心跳(10s)上报;仅 running 时更新,终态后保留最后一份

3. 失败类型(重试策略) ​

failure.type含义自动重试
node_lost执行节点失联(租约到期)✅
llm_error模型侧错误;仅 retriable=true(429/5xx/网络)才重试✅ 条件
engine_error引擎/框架异常❌
timeout节点侧整轮超时(AI 25 分钟上限)❌
max_iterations达到最大迭代次数❌
project_not_found目标项目在该节点上不存在❌
rejected因未决审批等原因被拒绝提交❌
cancelled被取消❌
script_failed脚本退出码非 0❌

为什么 timeout / max_iterations 不重试:同样的输入与同样的上限, 重跑必然再撞一次,只是白烧额度。需要你调整 prompt 或拆小任务。

重试次数:共 maxAttempts 次(默认 4,含首次)。退避 30s → 2min → 10min。

⚠️ 副作用提醒:node_lost 重试时,上一次可能已经改了一半文件。 每次重试都是新会话(不继承上下文),但工作区改动是累积的。 需要严格回滚时,可在项目上开启变更追踪,用 sessionId 查每轮 diff。

3.1 实时跟进:事件流(SSE) ​

轮询是最简单的方式,但长任务(几分钟到几十分钟)用轮询既费请求也慢半拍。 GET /v1/jobs/{id}/events 直接推:

bash
curl -N https://hub.example.com/v1/jobs/job_9f2c/events \
  -H "Authorization: Bearer $ZQA_API_KEY"
: ping

id: 1
event: job.state
data: {"id":"job_9f2c","status":"running","progress":{"toolCalls":3,...}}
event何时发
job.state连接建立时先发一次当前状态(可能在跑,不能等变化)
job.progress仍在 running,但进度/尝试次数变了
job.completedstatus = done(随后服务端关闭连接)
job.failedstatus = failed 且没有待重试(随后关闭)
job.cancelled已取消(随后关闭)
job.deleted作业记录已不存在(随后关闭)
  • 每 15 秒发一次注释行 : ping 保活(反代常在 60 秒空闲后断流)。
  • 断线重连带 Last-Event-ID(或 ?since=N),从上次的 id 之后继续编号。
  • 不是逐 token 流:Hub 到节点没有入站通道,这里推的是"状态 + 进度摘要"。 要看逐条工具调用,用 sessionId 到控制台看。

建议的接入方式 ​

场景方式
任务管理系统(要跨重启、要审计)webhook 为主,GET /v1/jobs/{id} 兜底对账
前台等结果、要实时进度条SSE
脚本 / 一次性任务GET /v1/jobs/{id} 轮询,3–5 秒一次

4. 取消与重试 ​

http
POST /v1/jobs/job_9f2c/cancel
POST /v1/jobs/job_9f2c/retry
  • 取消:排队中立即生效;运行中只改状态,节点手里那一轮无法远程打断(结果会被丢弃)。
  • 已结束的取消返回 409 job_finished。
  • 重试仅对 failed / cancelled 生效,返回 409 job_not_retryable 否则。
  • 重试会清零自动重试配额(你主动干预时信任你)。

5. 批次汇总 ​

http
GET /v1/batches/sprint-42
json
{
  "data": {
    "batchId": "sprint-42",
    "total": 100,
    "counts": { "queued": 60, "running": 3, "done": 34, "failed": 2, "cancelled": 1 },
    "progress": 0.37,
    "estimatedRemainingMs": 5400000,
    "byProject": [
      { "projectId": "proj_order", "total": 40, "done": 20, "failed": 1,
        "cancelled": 0, "queued": 19, "running": 1, "avgDurationMs": 310000 },
      { "projectId": "proj_web", "total": 60, "done": 14, "failed": 1,
        "cancelled": 1, "queued": 41, "running": 2, "avgDurationMs": 240000 }
    ],
    "failures": [
      { "jobId": "job_9f2c", "externalId": "TASK-17",
        "failureType": "llm_error", "message": "429 rate limited", "attempt": 2 }
    ]
  },
  "requestId": "req_..."
}
  • progress = 已到终态(done + failed + cancelled)/ 总数
  • estimatedRemainingMs 取最慢项目的剩余时间(跨项目并行,串行只发生在项目内)
  • failures 最多返回 100 条

整批全部到终态时,会额外推送一次 batch.completed webhook(见 webhooks.md)。

整批取消:POST /v1/batches/sprint-42/cancel → { "cancelled": 37 }。

6. 常见坑 ​

6.1 projectId 跨节点可能重名 ​

projectId 是节点本地的租户 id。两个节点上可能都存在 proj_order, 但指向不同的目录。此时不传 nodeId 会返回:

json
{ "error": { "code": "ambiguous_project",
  "message": "项目 proj_order 存在于多个节点(node_a, node_be),请显式指定 nodeId" } }

建议:建项目时不要手填 id,让服务端生成(或统一加前缀 be- / fe-)。

6.2 指定节点的任务不会改派 ​

节点离线时提交会被拒绝(409 node_offline)。已经提交的任务如果目标节点 中途离线,会一直排队,详情里出现:

json
"blocked": { "reason": "node_offline", "nodeId": "node_be", "lastSeen": "2026-11-09T08:30:00Z" }

GET /v1/overview 的 load.stalledQueued 会随之增长。

处理方式:节点恢复后自动开始跑;或取消后重新提交到别的节点。 (刻意不做自动改派:前端构建这类任务必须在指定机器上跑。)

6.3 提交成功不等于执行成功 ​

Hub 侧的校验(项目存在、节点在线)是尽力而为: 它基于节点最近一次上报的快照(约 5 秒延迟,网关不可达时 30 秒后才判离线)。 最终以节点执行时的结果为准。