Add a shared error-code catalog so failures can be traced by stable codes.
ci / vet-and-test (push) Successful in 3s
ci / docker-checkout-hostgateway (push) Successful in 3s

Services can resolve legacy aliases and expose the catalog over CLI and HTTP instead of ad-hoc error strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-29 13:43:12 +00:00
co-authored by Cursor
commit bd53e9ba79
33 changed files with 2489 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
# agent-native 错误码映射
本轮不修改 Agent-Native。接入时用下表把现有字符串换成规范码。别名已登记,`Registry.Lookup` / `Resolve` 可直接吃旧值。
## contract.ErrorCode
来源:`Agent-Native/contract/contract.go`。
| 现有码 | 规范码 | HTTP | retry | side_effects | 说明 |
|---|---|---|---|---|---|
| canceled | agent.canceled | 499 | never | possible | 取消必须传播;不可靠取消保持 Unknown,不伪报 Canceled |
| deadline_exceeded | agent.deadline_exceeded | 408 | never | possible | 与重试共享 ExecutionID、预算与截止时间 |
| budget_exceeded | agent.budget_exceeded | 429 | never | possible | 预算耗尽不是瞬时故障 |
| provider_unavailable | agent.provider_unavailable | 503 | transient | none | 传输级有界重试归 adapter |
| provider_rejected | agent.provider_rejected | 502 | never | none | 请求被拒绝,修正后再发 |
| tool_rejected | agent.tool_rejected | 403 | never | none | 不得自行扩大工具范围 |
| path_outside_workspace | agent.path_outside_workspace | 403 | never | none | 路径授权不是 OS 沙箱 |
| missing_capability | agent.missing_capability | 400 | never | none | 缺能力即拒绝,不降级 Stub |
| missing_deliverable | agent.missing_deliverable | 422 | transient | possible | 可能已有部分写入 |
| empty_output | agent.empty_output | 422 | never | none | 空输出不是成功 |
| unknown_outcome | agent.unknown_outcome | 500 | never | possible | 禁止降级为 Succeeded 或 Failed |
| internal | agent.internal | 500 | never | unknown | 副作用不明,禁止无条件重放 |
`contract.Error.Retryable` 仍是实例字段。规范码上的 `retry` 只是默认策略:`Retryable=true` 且 `side_effects=none` 才适合 adapter 有界重试。
## pitransport typed-result-v1
来源:`Agent-Native/adapters/pitransport/resultv1.go` 的 `allowedErrorCodes`。
| 现有码 | 规范码 | 备注 |
|---|---|---|
| PROMPT_EMPTY | agent.prompt_empty | 永久失败,与 `AssessStepFailure` 的 prompt_empty 一致 |
| UNAUTHORIZED | agent.unauthorized | 永久失败 |
| TIMEOUT | agent.timeout | 可重试 |
| CANCELLED | agent.canceled | 并入契约取消码,不另起含义 |
| TRANSIENT | agent.transient | 可重试 |
| MISSING_TARGET | agent.missing_deliverable | 并入契约交付物缺失 |
| RUN_FAILED | agent.run_failed | 永久失败,副作用按 possible |
`TypedRetryable` 只承认 TIMEOUT / CANCELLED / TRANSIENT。映射后对应 `agent.timeout`、`agent.canceled`、`agent.transient`。其中 `agent.canceled` 的默认策略是 never:实例上的 `retryable` 仍可表达「传输层把取消当成可重试」,调用方必须再看副作用。
## exec 失败分类
`exec.FailureClass`(none / transient / permanent)不是错误码。步骤评估继续用 `AssessStepFailure`;对外报告时再选上表中的规范码。永久原因(prompt_empty、unauthorized、治理拦截)对应 never,提供方连接类对应 transient。
+32
View File
@@ -0,0 +1,32 @@
# agent-squad 错误码映射
本轮不修改 Agent-Squad。当前 HTTP 层只有 `{"error":"<文本>"}`(`server.writeErr`),没有稳定码,也没有 trace。接入时把下表套进响应信封,正文理由仍放在 `reason`。
| 现状 | 规范码 | HTTP | 说明 |
|---|---|---|---|
| `role %s not found` | squad.role_not_found | 404 | `handleDeleteRole` 等 |
| `blueprint %s not found` | squad.blueprint_not_found | 404 | 蓝图删除 |
| Inspect / 黑板找不到运行 | squad.run_not_found | 404 | 磁盘为权威,重启后仍可能存在 |
| `goal.text is required` | squad.goal_required | 400 | 创建运行 |
| 运行处于 HITL 挂起(`RunStatus.IsHeld`) | squad.hitl_pending | 409 | 用 respond 续跑,不要并发再 Run |
| 计划确认 abort / 质检前的计划拒绝 | squad.plan_rejected | 422 | confirmation 挂起点拒绝 |
| 未知 backend,fail-closed | squad.unknown_backend | 400 | 禁止回落 stub |
| 步骤 `EvStepFailed` | squad.step_failed | 500 | 瞬时失败仍受步骤重试上限约束 |
| 质检失败且自动修复预算用尽 | squad.quality_failed | 422 | 可转 quality_review 人工放行 |
| `context cancelled` | squad.cancelled | 499 | 步骤保持 pending,可恢复 |
| JSON 解码失败 | sys.invalid_argument | 400 | 框架域,不单开小队码 |
| 未分类的 500 | sys.internal | 500 | 必须带 trace_id |
事件名(`step_failed`、`run_failed` 等)保持不变。事件文本里建议追加 `code` 字段,便于和日志系统的 `trace_id` 对齐。脱敏仍沿用现有 `Redact`,日志系统提供同形实现。
## 与执行错误的边界
步骤失败的根因优先用 agent 域码(例如 `agent.missing_deliverable`),小队只在编排层再包一层 `squad.step_failed`:
```text
squad.step_failed
└─ agent.missing_deliverable
└─ 原始执行错误
```
这样查 `squad.step_failed` 能看到编排后果,查 `agent.missing_deliverable` 能看到执行根因。
+34
View File
@@ -0,0 +1,34 @@
# 错误码格式
规范码只有一种形式:`domain.mnemonic`。
| 部分 | 规则 | 例子 |
|---|---|---|
| domain | 2–32 字符,小写字母开头,仅含小写字母、数字、连字符 | `sys`、`agent`、`squad` |
| mnemonic | 2–64 字符,小写字母开头,仅含小写字母、数字、下划线 | `provider_unavailable` |
正则:`^[a-z][a-z0-9-]{1,31}\.[a-z][a-z0-9_]{1,63}$`
## 别名
历史码不是第二套规范码。它们登记为别名,解析后必须回到规范码:
- agent-native `contract.ErrorCode`:`provider_unavailable` → `agent.provider_unavailable`
- pitransport:`PROMPT_EMPTY` → `agent.prompt_empty`,`CANCELLED` → `agent.canceled`,`MISSING_TARGET` → `agent.missing_deliverable`
别名匹配大小写敏感,格式为 `^[A-Za-z][A-Za-z0-9_-]{1,63}$`。
## 元数据
每条码必须同时声明:
| 字段 | 取值 | 用途 |
|---|---|---|
| http | 100–599 | HTTP 接口的默认状态码。取消使用 499(客户端关闭) |
| severity | info / warn / error / fatal | 日志与告警 |
| retry | never / transient / always | 默认重试策略。实例仍可另附是否重试 |
| side_effects | none / possible / unknown | 对齐 agent-native:possible 与 unknown 禁止无条件重放 |
| summary | 非空中文 | 给调用方的稳定摘要 |
| hint | 非空中文 | 处置建议,方便值班定位 |
未知码 `Resolve` 到 `sys.unknown_code`,HTTP 500,retry=never,side_effects=unknown。不得当成成功。
+36
View File
@@ -0,0 +1,36 @@
# 错误码注册治理
Go 定义是唯一权威来源,位于 `domains/`。JSON 文件是导出物,供服务查询、CLI 导出和跨语言消费。
## 新增一条码
1. 在 `errcode/codes.go` 增加 `Code` 常量。
2. 在对应域的 `register*` 中登记 HTTP、严重级别、重试、副作用、中文摘要与处置建议。
3. 若承接旧字符串,把它写成 `Aliases`,不要新造一套平行码。
4. 运行 `go test ./...`。目录一致性测试会失败,按测试输出重新导出 JSON:
```bash
WRITE_CATALOG=1 go test ./domains -run TestWriteCatalog -count=1
```
5. 更新 `docs/` 中对应系统的映射表。
## 不变量
- 规范码全局唯一;别名不得指向两个码,也不得与规范码同形冲突。
- 已发布的规范码只追加别名或澄清文案,不改变码字符串的含义。含义变化时新增码,旧码保留为别名或标记在映射表中。
- 副作用为 `possible` 或 `unknown` 的码,默认重试不得是 `always`。
- 种子数据在进程启动时登记。登记失败直接 panic,避免带着残缺目录对外服务。
## 自定义域
产品可以不改本仓库,在运行期加载 JSON:
```go
reg := errcode.NewRegistry()
domains.RegisterAll(reg)
data, _ := os.ReadFile("product.json")
if err := reg.LoadJSON(data); err != nil { /* 本批回滚 */ }
```
JSON 可以是数组,或 `{"codes":[...]}`。本批任一记录不合法时,整批回滚。