队列与重试
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自动提交调度
Section titled “自动提交调度”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 执行时段
Section titled “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 历史使用可配置的 存储保留策略。
queue.kind
Section titled “queue.kind”| 取值 | 说明 |
|---|---|
memory(默认) |
进程内队列,重启即丢失。适合单实例开发。 |
sqlite |
持久化队列,重启后仍在(单进程或多进程共享同一文件)。生产推荐。 |
redis |
持久化到 Redis,适合多实例部署。配置见下。 |
rabbitmq |
预留——尚未实现,配置了会告警并回退到 memory。 |
queue.redis —— Redis 队列选项
Section titled “queue.redis —— Redis 队列选项”Redis 队列的连接字段以透传方式接受:
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
url_env |
string | – | 存放 Redis URL 的环境变量名。 |
url |
string | – | 直接写 Redis URL(也可拆成 host / port / password / db)。 |
tls |
bool | false |
使用 TLS 连接。 |
key_prefix |
string | "aicr:" |
队列键前缀。共享 Redis 时请按环境取唯一值。 |
queue.workers
Section titled “queue.workers”| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
concurrency |
int > 0 | 4 |
同一进程所有调度入口同时分析的总上限。 |
per_workspace_concurrency |
int > 0 | 1 |
这些入口在每个 workspace 中同时分析的上限。 |
lock_ttl_seconds |
int > 0 | 1800 |
预留;锁过期时间由 queue backend 配置。 |
queue.sqlite —— 持久化队列选项
Section titled “queue.sqlite —— 持久化队列选项”| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
path |
string | data/queue.sqlite |
队列使用的 SQLite 数据库文件。 |
lock_ttl_seconds |
int > 0 | 300 |
陈旧运行回收 TTL。运行锁早于该时长的任务视为崩溃并被回收。 |
SQLite 持久化队列的工作原理
Section titled “SQLite 持久化队列的工作原理”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 生效,不取消正在运行的任务。 排队任务保留接收时的配置版本;持久后端上的队列版本账本支持重启恢复。
queue.rate_limit
Section titled “queue.rate_limit”限流按实际 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 rpsqueue.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 两个字段,但当前
运行时没有消费它们:重试耗尽的任务按失败处理并写入运行历史,没有独立的停放区。
这两个字段保留给后续版本,现在配置不会产生任何行为。