跳至内容

队列与重试

queue 命名空间决定评审任务在哪里排队、同时跑多少个、对每个 provider 的调用多快、 以及任务失败时怎么重试。默认是内存队列;生产环境建议切换到持久化 SQLite 队列, 让任务在重启后仍然存在。

Redis 的 queue.redis.url_env 指定保存连接 URL 的环境变量名。 TLS 与 ACL 认证使用 rediss://username:password@host:port/db,凭据按 URI 百分号编码。 队列与自动批次存储将完整 URL 交给驱动解析。私有 CA 可在启动 Node 前通过 NODE_EXTRA_CA_CERTS 指向 PEM 文件,证书验证保持开启。

queue:
kind: sqlite # memory(默认)| sqlite | redis
workers:
concurrency: 4
per_workspace_concurrency: 1
lock_ttl_seconds: 1800
rate_limit:
per_provider_rps:
gitea-internal: 5
retry:
attempts: 3
backoff:
kind: exponential
base_ms: 2000
max_ms: 60000
jitter: true

Git push、P4 change-commit、SVN post-commit 默认等待 300 秒。 review.auto_commit 配置延迟、每周可执行时段和来源排除规则,可放在全局、 workspaces.defaults.review 或 workspaces.instances.<id>.review 下。 最近一层的 schedule 或 exclude_sources 整体替换继承值。

review:
auto_commit:
delay_seconds: 300
queued_timeout_hours: 72
schedule:
timezone: Asia/Shanghai
rules:
- days: [mon, tue, wed, thu, fri]
windows:
- { start: "00:00", end: "13:00" }
- { start: "18:00", end: "24:00" }
- days: [sat, sun]
windows:
- { start: "00:00", end: "24:00" }
exclude_sources:
- id: ci-client
vcs: p4
match:
client: { glob: "ci-*" }

多组时段取并集,包含开始时间、不包含结束时间;跨午夜时段归属于开始的星期。 省略 schedule 或设置 schedule.rules: [] 都表示全天可用,默认时区为 UTC。 已经开始的分析可以在时段关闭后完成。时段同样约束异步 PR/MR、issue、评论处理: 窗口外到达的事件把首次尝试和每次重试推迟到下一窗口,日志为 trigger processing deferred by execution window。 exclude_sources: [] 清除继承的排除规则; 规则之间为 OR,同一规则内的字段为 AND,每个字段可选 glob 或 RE2 regex。

include_branches 是同一批自动提交事件的接收侧分支白名单:解析出非空 列表时,未列分支的推送在接收时直接忽略且不持久化 receipt。最近一层整体 胜出,[] 清除后回到接受全部分支。PR/MR、评论、issue 流程永不过滤, 无分支的 P4/SVN hook 也不受此检查影响。 填写 main 或 release/1.x 这样的完整分支名,不带 refs/heads/ 前缀; 匹配区分大小写,不展开 glob 或正则表达式。GitLab Push Hook 也走同一筛选和 持久化队列。before/after SHA 全零的分支创建、删除通知会被忽略。

queued_timeout_hours 限制一条自动提交在队列中的最长等待时间:超过该 时限的待处理条目和排队批次(dispatch_pending/queued/retry_wait) 会被终结为 queued_timeout,对应进行中的运行记录标记为 timeout,Events 面板的决策从 queued 翻转为 timeout,队列不会无限静默堆积,也不会在 长时间停机后重放远古任务。超时按成员首次接收时间计龄,包含元数据 准备和首次延迟;批次继承最早成员的接收时间。 且所有自动恢复路径都会先检查:服务启动清扫(调度开始前)、租约回收、 中断批次恢复、遗留 dead 批次重新排队以及调度领取执行,超龄任务一律终结 而非恢复。管理面板的人工重排(Retry)重置等待期限并保留发布检查点。默认 72; 0 关闭清扫,最大 8760(365 天)。与其他字段一样按最近显式设置层 生效。正在执行的批次不受周期清扫影响,仍由租约与重试生命周期管理; 需要提前终止时用 IM aicr cancel、管理面板 Queue 页的 Cancel 或 Runs 页的 Terminate。过期租约和中断执行在自动恢复前检查年龄,有有效租约的活动执行 保持运行;已完成收据不会被误标为排队超时。

同一解析后的超时也用于等待中的 IM 评审请求,从接受请求时开始计龄。 超时请求以 im.queued_timeout 关闭,并通过通知 outbox 告知原会话,运行中的 请求保持执行。Queue 的 Requeue 清除待执行批次的重试退避;Runs 的 Re-review 用保存的完整事件和当前配置创建新运行,保留 PR/MR 目标、fork 和提交区间。 缺少充分事件数据的历史记录会明确报错,避免评审错误的目标。

等待执行窗口的 PR/MR 延期事件也受同一超时限制,启动恢复前检查年龄; 机器人取消会持久化删除匹配的等待事件,阻止其恢复执行。

PR/MR 分析可以使用独立的周计划,配置形状与 review.auto_commit.schedule 相同,放在 review.pull_request.schedule 下,同样支持三层(全局、 workspaces.defaults.review、workspaces.instances.<id>.review; 最近一层的 schedule 整体替换继承值):

review:
pull_request:
schedule:
timezone: Asia/Shanghai
rules:
- days: [mon, tue, wed, thu, fri]
windows:
- { start: "00:00", end: "13:00" }
- { start: "18:00", end: "24:00" }
- days: [sat, sun]
windows:
- { start: "00:00", end: "24:00" }

各层都未设置 review.pull_request.schedule 时,PR/MR 事件回退到解析后的 review.auto_commit.schedule;两者都未设置时不做任何时段限制。时段只门控 自动 PR 事件和评论命令触发的评审——评论命令被推迟时会在 PR/MR 上回复一条 说明计划开始时间的评论。PR/MR 没有首次接收延迟,也不组装提交批次。

review.pull_request.include_target_branches 把 PR/MR 分析限制在列出的目标 (base)分支——例如 [main] 只分析合入 main 的 PR/MR。同样三层整体替换, [] 清除后回到全部目标分支。目标分支未知的事件(评论命令的 PR 详情拉取 失败)放行;push、issue 和手动流程永不过滤。 目标分支同样按完整名称区分大小写匹配。允许缺失 ref;webhook 中明确传入空字符串 或非字符串 ref 会作为无效请求拒绝。两个列表只在接收时应用,不重新筛选已接收任务。

workspaces:
instances:
atframe-utils:
review:
pull_request:
include_target_branches: [main]

窗口外到达的事件由延期注册表接管,而不是裸定时器。配置了可观测存储 (storage.database 加 admin,见仪表盘页面)时,延期会持久化并在重启后 恢复;同一目标的重复事件会替换保存的事件内容,只评审最新状态,且恢复时刻 不会提前。没有存储时延期退化为进程内存,重启即丢失,与异步触发路径的其余 状态一致。 等待中的目标不占用正在运行的去重标记。安排定时器和实际开始尝试时都会检查窗口, 进程暂停或时钟跳变后也要重新检查。已经开始的分析可以在窗口关闭后完成。

一次 Git push 是完整的评审单元,包含其中的不同作者和 merge commit。 元数据分页和跨事件合并的 50 成员上限都不会拆分它。多个 push 之间,原始 author name + email 相同、连续且到期的完整单元可合并至 50 个成员;包含多个作者、merge 或历史重写的 push 独立执行。两次分别含 40 和 20 个提交的 push 形成两个完整批次, 一次含 550 个提交的 push 形成一个批次。原子存储上限为 4,096 个成员;超限 push 以 push_batch_too_large 整体失败,不执行前缀。内部排除缺口以 exclusion_scope_conflict 失败;排除前缀或尾部后仍连续的范围可评审。

收据按仓库和分支隔离;已评审提交进入另一个受监控分支会产生该分支的新任务。 排队年龄从事件接收起算,与提交日期无关。重复投递保留等待年龄及已封存成员关系。 P4/SVN 保留按 User + Client 或 svn:author 合并连续同来源提交的规则,每批最多 50 个成员,hook 只覆盖所报 revision。排除判定缺少来源证据时,有界重试后将成员 标记为失败,不会默认放行。

接收回执、批次成员关系和执行检查点使用 queue.kind 对应的后端,memory 重启会丢失。 完成检查点仅恢复本地结果记账,不重跑分析或发布。待发布检查点保留分析、逐渠道回执及 远端操作 ID,见远端对账。终态失败 获得一次自动恢复,耗尽后跳过批次并释放 stream。Admin Queue 的 Retry 使用当前配置并 保留已有远端日志。未保存操作身份的旧检查点和重启后的内存后端无法防止重复发布。

自动批次、普通队列 worker、PR/MR、issue、comment 和手动分析在同一服务进程中共享 执行名额:queue.workers.concurrency 默认 4, queue.workers.per_workspace_concurrency 默认 1。P4 workspace 正在运行时,不会阻止 另一个 GitHub workspace 使用空闲名额。claim 在截取候选数量前跳过已达上限的 workspace。 调度扫描不等待分析结束;元数据准备按 workspace 单独限制并发。同一 stream 的已封存 批次仍按序执行,即使 workspace 并发设置大于 1。

每次重试重新获取名额;退避和执行时段等待不占分析名额,实际开始时重新检查时段。 降低并发不会取消已启动的任务,后续任务等待活动数量降到新上限以下;提高并发后允许 等待任务启动。批次存储租约还限制共享该 store 的多个消费者的批次并发;覆盖所有入口的 共享名额属于单进程限制,不是集群总配额。

Recent Runs、Events 和已结束 Queue 历史使用可配置的 存储保留策略。

取值 说明
memory(默认) 进程内队列,重启即丢失。适合单实例开发。
sqlite 持久化队列,重启后仍在(单进程或多进程共享同一文件)。生产推荐。
redis 持久化到 Redis,适合多实例部署。配置见下。
rabbitmq 预留——尚未实现,配置了会告警并回退到 memory。

Redis 队列的连接字段以透传方式接受:

字段 类型 默认 说明
url_env string – 存放 Redis URL 的环境变量名。
url string – 直接写 Redis URL(也可拆成 host / port / password / db)。
tls bool false 使用 TLS 连接。
key_prefix string "aicr:" 队列键前缀。共享 Redis 时请按环境取唯一值。
字段 类型 默认 说明
concurrency int > 0 4 同一进程所有调度入口同时分析的总上限。
per_workspace_concurrency int > 0 1 这些入口在每个 workspace 中同时分析的上限。
lock_ttl_seconds int > 0 1800 预留;锁过期时间由 queue backend 配置。
字段 类型 默认 说明
path string data/queue.sqlite 队列使用的 SQLite 数据库文件。
lock_ttl_seconds int > 0 300 陈旧运行回收 TTL。运行锁早于该时长的任务视为崩溃并被回收。

SQLite 队列基于 better-sqlite3, 单进程或多个进程共享同一文件都安全。关键特性:

  • 经 UPDATE ... RETURNING 原子领取。 worker 用单条语句领取下一个 queued 任务并标记为 running,因此两个 worker 永远不会抢到同一个任务。
  • 锁 TTL 后回收陈旧任务。 后台扫描会把运行锁早于 lock_ttl_seconds 的 running 任务重新入队,因此崩溃 worker 的任务最终会被其他 worker 重试。
  • WAL + busy_timeout 保证跨进程安全。 队列以 PRAGMA journal_mode = WAL 和 PRAGMA busy_timeout = 5000 打开,来自不同进程的并发写入会协作而非报错。

启用数据库配置后,并发更新在下一次 claim 生效,不取消正在运行的任务。 排队任务保留接收时的配置版本;持久后端上的队列版本账本支持重启恢复。

限流按实际 LLM provider ID 生效,包含重试及 fallback 请求。发布新速率保留 token bucket 累计状态。原生 agent CLI 按 launch 限流,内部 HTTP 请求由 CLI 管理。

字段 类型 说明
per_provider_rps map<string, number> 按 provider id 设置的每秒请求数上限。
queue:
rate_limit:
per_provider_rps:
openai-prod: 5 # 对此 LLM provider id 最多 5 rps

queue.retry —— 请用 attempts + backoff

Section titled “queue.retry —— 请用 attempts + backoff”

规范字段

规范的重试字段是 attempts 与 backoff。旧字段 max_attempts / backoff_seconds 仍然会被接受并归一化,但已弃用—— 请迁移到 attempts + backoff。

字段 类型 默认 说明
attempts int > 0 3 总尝试次数(含首次)。1 = 不重试。
backoff.kind enum exponential exponential、linear 或 constant。
backoff.base_ms number > 0 5000 首次/基础退避延迟(毫秒)。
backoff.max_ms number > 0 60000 单次退避延迟上限。
backoff.jitter bool true 是否加入随机抖动。

trigger 级重试用于吸收瞬时 IO 失败(超时、连接重置、DNS 抖动、HTTP 408/5xx)。 只有被分类为瞬时的错误才会重试;确定性失败——尤其是 context_overflow—— 无论 attempts 是多少都不会重试。更细粒度的重试发生在下一层:LLM provider 调用、输出渠道 fetch、VCS CLI 网络操作、GitHub App token 交换与 issue triage API 客户端,各自对瞬时 IO 错误按短指数退避最多重试 3 次,之后才把整个 trigger run 判为失败。输出与 triage 层只对幂等方法重试;非幂等 POST 不盲目重试。 带远端日志的自动批次写入按对账协议恢复,包括飞书 UUID 去重。HTTP 429 交由 LLM gateway 按 Retry-After 处理。

queue:
retry:
attempts: 3 # 瞬时失败重试(1 = 不重试)
backoff:
kind: exponential
base_ms: 5000
max_ms: 60000
jitter: true

旧字段(已弃用,会被归一化)

Section titled “旧字段(已弃用,会被归一化)”

为向后兼容,加载器仍会读取并归一化这些字段,但新配置不应再使用:

旧字段 归一化为
max_attempts attempts(向下取整)。
backoff_seconds 一个 constant 退避,base_ms = max_ms = backoff_seconds * 1000,jitter: false。

两者同时存在时,attempts / backoff 始终优先。

queue.dead_letter —— 预留,尚未生效

Section titled “queue.dead_letter —— 预留,尚未生效”

schema 接受 dead_letter.enabled 和 dead_letter.max_age_hours 两个字段,但当前 运行时没有消费它们:重试耗尽的任务按失败处理并写入运行历史,没有独立的停放区。 这两个字段保留给后续版本,现在配置不会产生任何行为。