sub-agy 运行时契约
sub-agy 是 Antigravity CLI (agy) 的异步作业封装层。Claude Code 通过插件命令把代码执行任务派发到后台,agy 在独立 git worktree 里执行,规划侧只负责编排,不占用 Claude 调用额度。
命令全集
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" watch <job-id ...> [--interval 秒] [--timeout 时长] [--strict] [--pretty]
- 轮询每个作业直到全部进入终态。barrier 语义:传多个 id 时要等最后一个才退出,所以增量汇报场景请一个 id 一个 watcher。
- 任一 id 不存在时立即 exit 3。
- interval 越界(不在 0.5–60 秒之间)exit 64。
- 全部终态时输出 JSON 数组,每个元素包含:
job_id, state, round, agy_status, summary, contract_ok, tests_passed, elapsed_seconds, tokens, diff_stat, result_path, events_path, worktree, branch。--pretty 表格包含 elapsed 与 tokens 列。
- 退出码:全部作业进入终态 → 0;job 不存在 → 3;超时 → 124。
--strict:把成败也编码进退出码——全部 done → 0,任一 error/cancelled/interrupted → 1。不传时沿用旧行为(只要终态就 0)。
--timeout 默认 60m,未把排队等待计入,排队靠后的作业要显式加大。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" run --plan <file.md> --cwd <project> [--model ...] [--effort ...] [--timeout ...] [--no-worktree] [--wait]
- 创建作业,写入 plan/prompt/schema/meta,spawn detached supervisor,默认立即返回
job_id, state, queue_position, worktree, branch, events_log。
- 不会因并发上限失败:超出
max_concurrent 时作业以 queued 落盘等槽位,queue_position 给出 1 起的 FIFO 位次(null = 已拿到槽位直接跑)。
- 加
--wait 时不立即退出,原地等待该作业到终态,输出与 watch 相同的 JSON 对象并遵循相同退出码(含排队等待时间)。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" status <job-id> | --all [--pretty]
- 查看作业状态、队列位次、已运行时间、tokens 消耗、最近一步摘要。包含惰性 interrupted 和解。
--pretty 表格包含 queue/elapsed/tokens 列。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" result <job-id> [--events]
- 输出
result.json + git diff --stat。作业未完成时 exit 4。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" feedback <job-id> "<message>"
- 在保留 conversation 的前提下启动新一轮修复。要求状态为 done/error 且 conversation_id 存在。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" cancel <job-id>
- 向 supervisor 发送 SIGTERM;由 supervisor 杀掉 agy 进程组并落
cancelled 状态。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" list [--state ...]
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" cleanup <job-id> [--purge] [--delete-branch] [--force]
- 移除 worktree,可选删除分支与日志。默认拒绝清理 running/queued 作业。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" doctor [--pretty]
- 检查 agy/PATH/git/Python/配置。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" pending [--pretty]
- 列出
done/error/interrupted 且未收割(meta.harvested_at 为空)的作业,JSON 数组,恒 exit 0。Stop hook 兜底提醒的数据源。
- 收割标记:
result <id> 首次成功读取即写 harvested_at;feedback 打回会重置,新一轮需重新收割。旧版作业(meta 无此键)不会出现在 pending 里。
bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" quota [--pretty]
- 无头额度查询,0 token。优先解析结构化数据,缺失时降级解析 TSV。
- 退出码:
0 成功;1 失败;127 agy 未安装。
watcher 与主动通知
dispatch 在汇报完作业卡片后,应为每个 job 各挂一个 Bash run_in_background 的 watch <单个 job id> --strict --cwd <project>。某个 watcher 退出即表示那一个作业进入终态,主 agent 会收到系统通知并只对该 job id 进入 harvest.md 的审查流程。
- 一 job 一 shell:
watch 是 barrier,多 id 共用一个 watcher 会让最快的作业被最慢的拖住,失去增量汇报的意义。
- 主 agent 提示语统一为:每个作业一个独立 watcher,谁先完成我就先审谁。
--strict 让退出码直接区分成败(0 = done,1 = error/cancelled/interrupted),通知无需再解析 JSON 就能判断走向。
- watcher 完成通知只是触发器,实际审查仍必须遵守 harvest 的硬性规则。
并发与排队
max_concurrent(默认 3)限制的是 同时 running 的作业数,不再是"能不能派发"。
run/feedback 一律接受作业并 spawn detached supervisor;supervisor 在拉起 agy 之前先调用 queue.acquire_slot,拿不到槽位就以 queued 原地等。
- 槽位记账在
<project>/.subagy/queue.lock 上加 flock 串行化,两个 supervisor 不会抢到同一个槽位。
- 排队顺序按
meta.queued_at FIFO(旧 meta 无此字段时回落 created_at)。feedback 打回会刷新 queued_at,即重新排到队尾。
- 排队中的作业进
events.ndjson 一条 {"type":"queued","running":N,"queued_ahead":M} 事件。
queue_timeout(默认 2h)是安全阀:等槽位超时 → state=error,error 写明等待时长。
- 排队中的作业可以直接
cancel,落 cancelled 而非 error(agy 从未被拉起)。
- exit code 5(
concurrency)保留在退出码表里但 run 已不再产出。
设计约定
- 面向用户的展示型输出必须经 Bash 工具调用呈现(工具调用对用户可见)。不要用内联执行语法(感叹号紧跟反引号命令的写法——本文档不能出现其字面形态,否则技能加载时会被真的执行)把结果藏进提示词。例如
quota、status、doctor 等展示型命令,应调用 Bash 工具执行 bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" <子命令> --pretty,再把输出逐字贴进回复。
作业状态机
queued → running → done | error | cancelled | interrupted
queued:已创建,supervisor 尚未拿到运行槽位(或刚拿到还没拉起 agy)。status 会给出 queue_position。
running:supervisor 已占住一个槽位并正在运行 agy。
done:agy exit 0 且 status SUCCESS,已写 result.json。
error:agy 非零退出、status ERROR/INVALID、无结果事件且无 transcript 兜底,或等槽位超过 queue_timeout。
cancelled:用户主动 cancel(含还在排队、agy 尚未拉起时)。
interrupted:惰性和解状态。当 state=running/queued 但 supervisor pid 已不存在且 finished_at 为空时,status/list 会现场改写为 interrupted。(queued 且 pid 尚未记录时不判定,避免误伤刚 spawn 的作业。)
result.json 字段解释
{
"job_id": "...",
"state": "done",
"agy_status": "SUCCESS",
"round": 1,
"summary": "...",
"structured_output": {...}|null,
"contract_ok": true,
"response_text": "agy 原始 response",
"files_changed_git": ["..."],
"diff_stat": "git diff --stat 输出",
"usage": {...},
"conversation_id": "...",
"duration_seconds": 0,
"num_turns": 0,
"recovered_from_transcript": false,
"attempts": 1,
"worktree": "...",
"branch": "agy/<id>",
"base_sha": "..."
}
contract_ok:结构化输出存在且满足 schema 时为 true;若 round>=2 因兼容性丢弃 schema,则为 false。
recovered_from_transcript:agy stdout bug 触发,从 transcript 兜底恢复时为 true。
structured_output:agy 按 JSON schema 返回的对象;缺失时 contract_ok=false。
response_text:agy 原始文本响应。
false_error(§19.2):当 agy 状态为 ERROR 但实际代码执行和验收正常(例:agy 输出 "not a valid artifact path" 误报)时会在此标 "artifact_path"。见到此字段时按 done 验收,不要按 error 打回。
反馈轮次语义
feedback 会让 round += 1、状态回到 queued,并用 --conversation <id> 续会话。
- 作业的
model 与 effort 记录于 meta.json(model/effort 字段已存在),打回轮次复用同一档位与模型。
- 新一轮 prompt 包含上一轮 summary 与本次 message,要求 agy 修复后重新满足契约。
- 若
round>=2 时 --json-schema 导致参数错误,sub-agy 会去掉 schema 重试一次,并在 result 中标 contract_ok=false, contract_note="schema dropped on round>=2"。
退出码表
| 码 |
含义 |
| 0 |
成功 |
| 1 |
通用错误;watch --strict 下也表示有作业未 done |
| 3 |
作业不存在 |
| 4 |
作业未完成(result 时) |
| 5 |
超过并发上限(保留;run 改为排队后已不产出) |
| 6 |
需要 git 仓库但未找到 |
| 64 |
CLI 用法错误/参数无效 |
| 124 |
watcher/run --wait 超时 |
| 127 |
agy 未安装 |
.subagy/ 目录结构
<project>/.subagy/
├── queue.lock # 运行槽位记账的 flock 文件
├── jobs/<job_id>/
│ ├── meta.json
│ ├── plan.md
│ ├── prompt.txt
│ ├── schema.json
│ ├── events.ndjson
│ ├── stderr.log
│ └── result.json
└── worktrees/<job_id>/ # git worktree,分支 agy/<job_id>
铁律
- 合并与 cleanup 由用户决定:收割通过时只给出
git merge agy/<job_id> 或 cherry-pick 建议,绝不要自动合并、提交或清理。
- 不打回未完成的作业:feedback/cleanup/harvest 动作只针对
done/error 作业。看到 running 或 queued 请让用户等待或 cancel。
- 不修改用户 agy 配置:sub-agy 不读取/修改
~/.gemini/antigravity-cli/settings.json,只读取自己的 ~/.config/sub-agy/config.toml。
- 零 API key / 零代理:sub-agy 只做本地进程编排,所有 LLM 调用都走用户本机已安装的
agy。
1---2name: subagy-runtime3description: sub-agy 异步作业执行后端的运行时契约、状态机与铁律4---56# sub-agy 运行时契约78sub-agy 是 Antigravity CLI (`agy`) 的异步作业封装层。Claude Code 通过插件命令把代码执行任务派发到后台,`agy` 在独立 git worktree 里执行,规划侧只负责编排,不占用 Claude 调用额度。910## 命令全集1112- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" watch <job-id ...> [--interval 秒] [--timeout 时长] [--strict] [--pretty]`13 - 轮询每个作业直到全部进入终态。**barrier 语义**:传多个 id 时要等最后一个才退出,所以增量汇报场景请一个 id 一个 watcher。14 - 任一 id 不存在时立即 exit 3。15 - interval 越界(不在 0.5–60 秒之间)exit 64。16 - 全部终态时输出 JSON 数组,每个元素包含:`job_id, state, round, agy_status, summary, contract_ok, tests_passed, elapsed_seconds, tokens, diff_stat, result_path, events_path, worktree, branch`。`--pretty` 表格包含 `elapsed` 与 `tokens` 列。17 - 退出码:全部作业进入终态 → 0;job 不存在 → 3;超时 → 124。18 - `--strict`:把成败也编码进退出码——全部 `done` → 0,任一 `error`/`cancelled`/`interrupted` → 1。不传时沿用旧行为(只要终态就 0)。19 - `--timeout` 默认 60m,未把排队等待计入,排队靠后的作业要显式加大。20- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" run --plan <file.md> --cwd <project> [--model ...] [--effort ...] [--timeout ...] [--no-worktree] [--wait]`21 - 创建作业,写入 plan/prompt/schema/meta,spawn detached supervisor,默认立即返回 `job_id, state, queue_position, worktree, branch, events_log`。22 - **不会因并发上限失败**:超出 `max_concurrent` 时作业以 `queued` 落盘等槽位,`queue_position` 给出 1 起的 FIFO 位次(`null` = 已拿到槽位直接跑)。23 - 加 `--wait` 时不立即退出,原地等待该作业到终态,输出与 `watch` 相同的 JSON 对象并遵循相同退出码(含排队等待时间)。24- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" status <job-id> | --all [--pretty]`25 - 查看作业状态、队列位次、已运行时间、tokens 消耗、最近一步摘要。包含惰性 interrupted 和解。`--pretty` 表格包含 `queue`/`elapsed`/`tokens` 列。26- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" result <job-id> [--events]`27 - 输出 `result.json` + `git diff --stat`。作业未完成时 exit 4。28- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" feedback <job-id> "<message>"`29 - 在保留 conversation 的前提下启动新一轮修复。要求状态为 done/error 且 conversation_id 存在。30- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" cancel <job-id>`31 - 向 supervisor 发送 SIGTERM;由 supervisor 杀掉 agy 进程组并落 `cancelled` 状态。32- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" list [--state ...]`33 - 列出所有作业。34- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" cleanup <job-id> [--purge] [--delete-branch] [--force]`35 - 移除 worktree,可选删除分支与日志。默认拒绝清理 running/queued 作业。36- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" doctor [--pretty]`37 - 检查 agy/PATH/git/Python/配置。38- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" pending [--pretty]`39 - 列出 `done`/`error`/`interrupted` 且**未收割**(`meta.harvested_at` 为空)的作业,JSON 数组,恒 exit 0。Stop hook 兜底提醒的数据源。40 - 收割标记:`result <id>` 首次成功读取即写 `harvested_at`;`feedback` 打回会重置,新一轮需重新收割。旧版作业(meta 无此键)不会出现在 pending 里。41- `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" quota [--pretty]`42 - 无头额度查询,0 token。优先解析结构化数据,缺失时降级解析 TSV。43 - 退出码:`0` 成功;`1` 失败;`127` agy 未安装。4445## watcher 与主动通知4647`dispatch` 在汇报完作业卡片后,应**为每个 job 各挂一个** Bash `run_in_background` 的 `watch <单个 job id> --strict --cwd <project>`。某个 watcher 退出即表示**那一个**作业进入终态,主 agent 会收到系统通知并只对该 job id 进入 `harvest.md` 的审查流程。4849- **一 job 一 shell**:`watch` 是 barrier,多 id 共用一个 watcher 会让最快的作业被最慢的拖住,失去增量汇报的意义。50- 主 agent 提示语统一为:**每个作业一个独立 watcher,谁先完成我就先审谁**。51- `--strict` 让退出码直接区分成败(0 = done,1 = error/cancelled/interrupted),通知无需再解析 JSON 就能判断走向。52- watcher 完成通知只是触发器,实际审查仍必须遵守 harvest 的硬性规则。5354## 并发与排队5556- `max_concurrent`(默认 3)限制的是 **同时 `running`** 的作业数,不再是"能不能派发"。57- `run`/`feedback` 一律接受作业并 spawn detached supervisor;supervisor 在拉起 agy 之前先调用 `queue.acquire_slot`,拿不到槽位就以 `queued` 原地等。58- 槽位记账在 `<project>/.subagy/queue.lock` 上加 flock 串行化,两个 supervisor 不会抢到同一个槽位。59- 排队顺序按 `meta.queued_at` FIFO(旧 meta 无此字段时回落 `created_at`)。`feedback` 打回会刷新 `queued_at`,即重新排到队尾。60- 排队中的作业进 `events.ndjson` 一条 `{"type":"queued","running":N,"queued_ahead":M}` 事件。61- `queue_timeout`(默认 `2h`)是安全阀:等槽位超时 → `state=error`,`error` 写明等待时长。62- 排队中的作业可以直接 `cancel`,落 `cancelled` 而非 `error`(agy 从未被拉起)。63- exit code 5(`concurrency`)保留在退出码表里但 `run` 已不再产出。6465## 设计约定6667- **面向用户的展示型输出必须经 Bash 工具调用呈现**(工具调用对用户可见)。不要用内联执行语法(感叹号紧跟反引号命令的写法——本文档不能出现其字面形态,否则技能加载时会被真的执行)把结果藏进提示词。例如 `quota`、`status`、`doctor` 等展示型命令,应调用 Bash 工具执行 `bash "${CLAUDE_PLUGIN_ROOT}/scripts/ab" <子命令> --pretty`,再把输出逐字贴进回复。6869## 作业状态机7071`queued` → `running` → `done` | `error` | `cancelled` | `interrupted`7273- `queued`:已创建,supervisor 尚未拿到运行槽位(或刚拿到还没拉起 agy)。`status` 会给出 `queue_position`。74- `running`:supervisor 已占住一个槽位并正在运行 agy。75- `done`:agy exit 0 且 status SUCCESS,已写 result.json。76- `error`:agy 非零退出、status ERROR/INVALID、无结果事件且无 transcript 兜底,或等槽位超过 `queue_timeout`。77- `cancelled`:用户主动 cancel(含还在排队、agy 尚未拉起时)。78- `interrupted`:惰性和解状态。当 `state=running`/`queued` 但 supervisor pid 已不存在且 `finished_at` 为空时,`status`/`list` 会现场改写为 `interrupted`。(`queued` 且 pid 尚未记录时不判定,避免误伤刚 spawn 的作业。)7980## result.json 字段解释8182```json83{84 "job_id": "...",85 "state": "done",86 "agy_status": "SUCCESS",87 "round": 1,88 "summary": "...",89 "structured_output": {...}|null,90 "contract_ok": true,91 "response_text": "agy 原始 response",92 "files_changed_git": ["..."],93 "diff_stat": "git diff --stat 输出",94 "usage": {...},95 "conversation_id": "...",96 "duration_seconds": 0,97 "num_turns": 0,98 "recovered_from_transcript": false,99 "attempts": 1,100 "worktree": "...",101 "branch": "agy/<id>",102 "base_sha": "..."103}104```105106- `contract_ok`:结构化输出存在且满足 schema 时为 true;若 `round>=2` 因兼容性丢弃 schema,则为 false。107- `recovered_from_transcript`:agy stdout bug 触发,从 transcript 兜底恢复时为 true。108- `structured_output`:agy 按 JSON schema 返回的对象;缺失时 `contract_ok=false`。109- `response_text`:agy 原始文本响应。110- `false_error`(§19.2):当 agy 状态为 ERROR 但实际代码执行和验收正常(例:agy 输出 "not a valid artifact path" 误报)时会在此标 `"artifact_path"`。见到此字段时按 done 验收,不要按 error 打回。111112## 反馈轮次语义113114- `feedback` 会让 `round += 1`、状态回到 `queued`,并用 `--conversation <id>` 续会话。115- 作业的 `model` 与 `effort` 记录于 `meta.json`(`model`/`effort` 字段已存在),打回轮次复用同一档位与模型。116- 新一轮 prompt 包含上一轮 summary 与本次 message,要求 agy 修复后重新满足契约。117- 若 `round>=2` 时 `--json-schema` 导致参数错误,sub-agy 会去掉 schema 重试一次,并在 result 中标 `contract_ok=false, contract_note="schema dropped on round>=2"`。118119## 退出码表120121| 码 | 含义 |122|---|---|123| 0 | 成功 |124| 1 | 通用错误;`watch --strict` 下也表示有作业未 done |125| 3 | 作业不存在 |126| 4 | 作业未完成(result 时) |127| 5 | 超过并发上限(保留;`run` 改为排队后已不产出) |128| 6 | 需要 git 仓库但未找到 |129| 64 | CLI 用法错误/参数无效 |130| 124 | watcher/run --wait 超时 |131| 127 | agy 未安装 |132133## `.subagy/` 目录结构134135```136<project>/.subagy/137├── queue.lock # 运行槽位记账的 flock 文件138├── jobs/<job_id>/139│ ├── meta.json140│ ├── plan.md141│ ├── prompt.txt142│ ├── schema.json143│ ├── events.ndjson144│ ├── stderr.log145│ └── result.json146└── worktrees/<job_id>/ # git worktree,分支 agy/<job_id>147```148149## 铁律1501511. **合并与 cleanup 由用户决定**:收割通过时只给出 `git merge agy/<job_id>` 或 cherry-pick 建议,绝不要自动合并、提交或清理。1522. **不打回未完成的作业**:feedback/cleanup/harvest 动作只针对 `done`/`error` 作业。看到 `running` 或 `queued` 请让用户等待或 `cancel`。1533. **不修改用户 agy 配置**:sub-agy 不读取/修改 `~/.gemini/antigravity-cli/settings.json`,只读取自己的 `~/.config/sub-agy/config.toml`。1544. **零 API key / 零代理**:sub-agy 只做本地进程编排,所有 LLM 调用都走用户本机已安装的 `agy`。