Agnes Agent 2d7a6002b3 docs(tdd): 研发负责人1 第28轮标准汇报 | 状态机22键=12DONE+9GREEN+1YELLOW(sleep.config.tags B-37修复41f1fc934 IN CHAIN 待测试mark-green)+0RED | 派发EVD-7报告闭环23SC(lan-189)+EVD-8测评-方案43SC(lan-248) 补P0剩余66SC | TR-11登记B-37复验+EVD-3/4/5/6证据复验登记 | SC覆盖15/241(游客域10转正dceeaaeaf) | 台账追加EVD-7/8于origin基线 5 дней назад
..
out 63f204db11 U7: 大字号段与字重收口前置方案(只读分析)完成 1 неделя назад
.gitignore e2f27e9ff7 chore: 提交工作区全量改动(业务方案/参考资料/设计稿/工具) 1 неделя назад
.model_state.json ff6b3ff233 chore(hub): 同步编排期 .model_state.json 最新状态 1 неделя назад
README.md e2f27e9ff7 chore: 提交工作区全量改动(业务方案/参考资料/设计稿/工具) 1 неделя назад
cfc_sessions_report.html e2f27e9ff7 chore: 提交工作区全量改动(业务方案/参考资料/设计稿/工具) 1 неделя назад
config.example.json bf16b36628 fix(hub): 实例级 dir_aliases 目录别名映射,根治远程 Windows 实例误传 POSIX 路径 1 неделя назад
config.json 9d151638b0 fix(xaf_agent_hub): prod-etotem 明文口令移出 config.json(回退 null),凭据由 config.local.json 覆盖 6 дней назад
err_sess.txt e2f27e9ff7 chore: 提交工作区全量改动(业务方案/参考资料/设计稿/工具) 1 неделя назад
lead_backlog.md 3036a0bd40 chore: 主工作树 WIP 落库(unified-dimension 注册体系 + docs/tdd 轮报 + hub 台账归档 + gitignore 清理运行产物;合并 origin/cfclub 前快照) 1 неделя назад
lead_backlog_archive.md 3036a0bd40 chore: 主工作树 WIP 落库(unified-dimension 注册体系 + docs/tdd 轮报 + hub 台账归档 + gitignore 清理运行产物;合并 origin/cfclub 前快照) 1 неделя назад
out_sess.json e2f27e9ff7 chore: 提交工作区全量改动(业务方案/参考资料/设计稿/工具) 1 неделя назад
tasks_backlog.json 2d7a6002b3 docs(tdd): 研发负责人1 第28轮标准汇报 | 状态机22键=12DONE+9GREEN+1YELLOW(sleep.config.tags B-37修复41f1fc934 IN CHAIN 待测试mark-green)+0RED | 派发EVD-7报告闭环23SC(lan-189)+EVD-8测评-方案43SC(lan-248) 补P0剩余66SC | TR-11登记B-37复验+EVD-3/4/5/6证据复验登记 | SC覆盖15/241(游客域10转正dceeaaeaf) | 台账追加EVD-7/8于origin基线 5 дней назад
wait_branch.sh e2f27e9ff7 chore: 提交工作区全量改动(业务方案/参考资料/设计稿/工具) 1 неделя назад

README.md

xaf_agent_hub —— opencode 多实例「浠艾福(CFC)」会话发现与调度工具

一个只依赖 Python 标准库 的命令行工具,用来:

  1. 发现:找出 6 个 opencode 实例上所有与「浠艾福开发」相关的会话(按会话工作目录判定);
  2. 调度:向指定会话投递开发需求,或自动负载均衡地新建会话执行;
  3. 智能匹配:派发时自动找出与任务最相关的已有 CFC 会话并复用,没有相关会话才新建(避免重复会话);
  4. 限流:每个实例同时最多运行 2 个 CFC 开发会话,并发安全(多进程同时调度也不会突破);
  5. 自动清理:一键清理各实例上超过 TTL(默认 10 天)未使用且非运行态的 CFC 会话;
  6. 导出:把全量清单导出为 JSON / CSV,并生成一份给非技术同事直接看的单文件 HTML 总览页。

一、环境要求

项目 要求
Python 3.10+(实测 3.13.14,命令为 python)
第三方包 无(仅 urllib / json / csv / argparse / concurrent.futures / msvcrt·fcntl 等标准库)
网络 能访问 6 个 opencode 实例的 HTTP 端口

所有命令都在本目录下执行:

cd E:/cfc/tools/xaf_agent_hub
python hub.py instances

二、文件结构

xaf_agent_hub/
├── config.json               # 实例定义 + cfc_roots + exclude_dirs + 并发上限(**不含任何密码**)
├── config.example.json       # 配置模板(占位值,可直接复制)
├── config.local.json         # 本地凭证覆盖(已被 .gitignore 忽略,**永不提交**)
├── hub.py                    # CLI 入口(argparse 子命令 + 中文等宽表格)
├── opencode_api.py           # HTTP 客户端(Basic 认证、超时、重试、异常归一、session 归一化)
├── cfc_filter.py             # CFC 会话识别规则(归一化 / 根目录 / 路径段 / 排除)
├── scheduler.py              # 配置加载 + 并发扫描 + 调度决策 + 并发安全闸门
├── matcher.py                # 语义会话匹配(字符 n-gram + TF-IDF 余弦,无外部依赖)
├── slot_lock.py              # 实例互斥锁 + 并发槽位预留(P0 并发正确性)
├── report.py                 # 导出 JSON / CSV / 单文件 HTML 总览页
├── README.md                 # 本文档
└── tests/
    └── test_smart_cleanup.py # 单元测试(mock,无需可达实例)
├── .gitignore                # 忽略 config.local.json / *.log / __pycache__
├── cfc_sessions_report.html  # 生成产物:总览页
└── out/                      # 生成产物:cfc_sessions.json / cfc_sessions.csv

运行期状态目录(不在仓库内):~/.xaf_agent_hub/locks/*.lock、~/.xaf_agent_hub/slots/*.slot。


三、凭证配置(务必先读)

config.json 里永远不写密码,5 个实例的 password 一律为 null,只保留 password_env 提示。 真实密码放在 config.local.json(已被 .gitignore 忽略),或用环境变量临时覆盖。

密码解析优先级(高 → 低):

优先级 来源 用法
1 password_env 指定的环境变量 set XAF_HUB_PW_LAN_189=xxx(临时覆盖/排障最方便)
2 XAF_HUB_PW_<实例名大写,-/.→_> 环境变量 同上,不需要改配置
3 config.local.json 的 instances.<name>.password 日常使用,随实例配置一起覆盖
4 password_file + password_key 如 ~/.config/opencode/service.json 的 password

config.local.json 格式(支持 {实例名: {...}} 字典,也支持数组):

{
  "instances": {
    "lan-189": {"password": "<在此填写>"},
    "prod-etotem": {"password": "<在此填写>"}
  }
}

用 --config 其它路径 时,若该路径同级没有 config.local.json,会自动回落到 本工具目录的 config.local.json 只借凭证:仅合并密码相关字段(password / password_key / password_file / password_env),不会把本工具的实例列表或其它配置 注入到你自己的配置文件里;你配置里没有的实例名会被直接忽略。

校验是否泄漏:

grep -rn "IwinTrue\|OpenCode@" E:/cfc/tools/xaf_agent_hub/     # 只应命中 config.local.json
cd E:/cfc && git check-ignore -v tools/xaf_agent_hub/config.local.json   # 必须命中忽略规则

四、实例与主要配置字段

逻辑名 地址 环境 备注
local-45278 http://127.0.0.1:45278 本机 密码来自 ~/.config/opencode/service.json
lan-248 http://192.168.16.248:4090 内网
lan-204 http://192.168.16.204:4091 内网 4091 端口(不是 4090)
lan-189 http://192.168.16.189:4090 内网
prod-etotem https://opencode.etotem.com.cn 生产 danger: true + readonly_default: true,写操作需 --allow-prod
pub-iwintrue http://opencode.iwintrue.com 公网 http(https 会 401)
字段 说明
concurrency_limit 每实例同时运行的 CFC 会话上限,默认 2
session_ttl_days 自动清理的「未使用」阈值(天),默认 10;超过该时长且非运行态的 CFC 会话可被清理
match_threshold 智能匹配的相似度阈值,默认 0.12;低于此值视为「无相关会话」,改走新建
auto_match 是否默认启用智能匹配(dispatch 缺省行为),默认 true
timeouts.connect_seconds TCP 连接超时(默认 8s,预检用)
timeouts.read_seconds 请求读超时(默认 30s)
retries 网络错误 / 5xx / 协议中断重试次数(默认 1,4xx 不重试)
page_size GET /api/session 的 limit(默认 8000;必须显式传,否则服务端只给 50 条)
scan_workers 多实例并发扫描线程数(默认 6)
lock.slot_ttl_seconds 槽位预留有效期(默认 60s,超时自动清理)
lock.lock_timeout_seconds 获取实例调度锁的最长等待(默认 20s)
lock.slot_grace_seconds 会话创建后等待 /api/session/active 反映的宽限(默认 5s)
lock.dir 锁与槽位存储目录,null = ~/.xaf_agent_hub
instances[].cfc_roots 该实例的 CFC 根目录清单(前缀匹配,含子目录)
instances[].default_directory 该实例新建会话时的默认工作目录
instances[].danger true = 危险实例(生产),写入需 --allow-prod
exclude_dirs / exclude_substrings 排除目录(前缀)/ 排除子串(如 workbuddy)
fallback_segments 兜底路径段:cfc / caremate / danshop / xaf

五、CFC 会话识别规则

判定依据是会话的 location.directory(工作目录),先归一化:统一 / 分隔符、折叠重复分隔符、去尾分隔符、整体小写(Windows 盘符大小写不敏感)。规则优先级 从高到低:

  1. 排除清单(exclude_dirs 前缀 + exclude_substrings 子串)——命中即判为非 CFC,即使同时命中后两条。 当前排除:E:\workspace\mes、E:\wbspace\train、E:\workspace\totem、E:\workspace\iwt、 /home/liaoxg/.config/opencode/plugins/omf、/sc-data/mes、/home/iwt/.config/opencode/plugins/omf、 D:\Documents、D:\tmp、D:\360极速、C:\Users\iwt、含 workbuddy 的任意目录。
  2. 实例显式根目录(cfc_roots)——目录等于根目录或位于其下:
    • local-45278: E:\cfc(覆盖 E:\cfc\cfc-frontend、E:\cfc\docs 等)
    • lan-248: /sc-data/cfc
    • lan-204: D:\cfc
    • lan-189: C:\code\cfc(覆盖 book、运营文案、小红书便秘和腹泻帐号)
    • prod-etotem: /root/cfc、/root/caremate
    • pub-iwintrue: /sc-data/cfc、/sc-data/caremate
  3. 兜底路径段——目录任一段等于 cfc / caremate / danshop / xaf 时也算 CFC(识别未知新目录)。

--keyword 是在目录判定之上再按 标题 做包含过滤(默认不过滤)。


六、子命令

1) instances —— 实例健康与总量

python hub.py instances
python hub.py instances --json

输出:实例 / 地址 / 状态 / 版本 / 会话总数 / CFC 会话数 / 运行中(总/CFC) / 上限 / 说明。 不可达实例只标记原因,单实例任何异常都不中断整体扫描(协议中断、超时、401、5xx 均已归一)。

2) sessions —— CFC 会话清单(可导出)

python hub.py sessions                                  # 表格,按最近更新倒序
python hub.py sessions --instance lan-189               # 只看某实例
python hub.py sessions --keyword 打卡 --limit 20        # 标题关键词 + 限制条数
python hub.py sessions --json                           # JSON 到 stdout
python hub.py sessions --out out/cfc_sessions.json      # 导出 JSON 全量
python hub.py sessions --out out/cfc_sessions.csv       # 导出 CSV(utf-8-sig,Excel 可直接打开)
python hub.py sessions --html cfc_sessions_report.html  # 生成单文件 HTML 总览页

--limit 只影响屏幕显示;--out / --html 永远导出 全量。

CSV 列:实例, 会话ID, 工作目录, 运行状态, 最近更新, 创建时间, outcome, 标题, 消息成本

3) status —— 负载与可调度性

python hub.py status
python hub.py status --json
python hub.py status --allow-prod       # 把生产实例也计为可调度(仅影响展示)

输出:运行中总数 / 运行中 CFC / 在途预留 / CFC 会话总数 / 上限 / 是否可调度 / 原因。 可调度判定 = 运行中 CFC + 在途预留 < 上限 且非生产只读。

4) dispatch —— 调度会话做开发

# 智能匹配:跨全部实例找出与任务最相关的已有 CFC 会话并复用;无相关则新建
python hub.py dispatch --text "修复登录页样式"

# 只看计划(含匹配相似度与候选)不发送
python hub.py dispatch --text "修复登录页样式" --dry-run

# 指定实例(在该实例内做智能匹配)
python hub.py dispatch --text "补充单元测试" --instance lan-189

# 复用指定会话(复用不占新额度,满载时也能追加指令)
python hub.py dispatch --text "继续上一轮改造" --session ses_xxx --instance local-45278

# 指定目录 / 标题(--title 关键词匹配)
python hub.py dispatch --text "改造打卡页" --instance lan-189 --dir 'C:\code\cfc\book' --title 打卡

# 强制新建(忽略任何匹配,--new 优先级最高)
python hub.py dispatch --text "改造打卡页" --instance lan-189 --dir 'C:\code\cfc\book' --title 打卡 --new

# 关闭智能匹配(按旧逻辑:无 --session/--title 则新建会话)
python hub.py dispatch --text "改造打卡页" --instance lan-189 --no-match

# 派发前先自动清理各实例上 10 天未用的 CFC 会话
python hub.py dispatch --text "修复登录页" --auto-cleanup

# 生产实例(默认拒绝,需显式放行)
python hub.py dispatch --text "只更新文档" --instance prod-etotem --allow-prod --dry-run

智能会话匹配(默认行为)

未显式指定 --session / --title / --new 时,dispatch 默认启用智能匹配:

  1. 把每个实例上全部 CFC 会话的「标题 + 工作目录」拼成一个文档;
  2. 用字符 n-gram + TF-IDF + 余弦相似度对任务文本做检索,排出最相关的前 5 个候选;
  3. 从候选中按优先级挑选可复用者:
    • 相似度 ≥ match_threshold(默认 0.12)才开始考虑;
    • 正在运行的相关会话优先 —— 直接 steer 追加指令,不占新额度(即便实例已满也能复用);
    • 否则若相关会话空闲且实例尚有空额 —— 复用并启动运行,占用一个并发额度(受并发闸门约束);
    • 实例已满时,空闲相关会话无法复用,会继续看下一候选;
  4. 若全部候选相似度都低于阈值 —— 退化为「在负载最低的可调度实例新建会话」。

匹配基于标题与目录(字符级),不读取会话正文,因此短标题的语义重合度足够区分「同一需求的续做」与「全新需求」。阈值可在 config.json 的 match_threshold 调整;--dry-run 会打印匹配相似度,便于校准。

执行顺序:

  1. 智能匹配(命中则直接复用,跳过后续选实例);
  2. 否则选定实例(--instance,或自动挑选「可达 + 有 CFC 目录 + 未满载 + 非生产」中负载最低者);
  3. 解析目标会话:--session → 否则 --title 关键词匹配最近更新的 CFC 会话 → 否则新建(--new 强制新建);
  4. 并发闸门(对「新建会话」与「复用空闲会话」生效,复用运行中会话不占额度): 运行中 CFC + 在途预留 >= 2 → 拒绝,退出码 3;
  5. POST /api/session/:id/prompt,body {"text": ...},异步投递(不阻塞等模型跑完)。

行为要点:

  • 复用已在运行的会话不占新额度(prompt 是 steer 语义,不新增并发会话),因此满载时仍可给运行中的会话追加指令;
  • 复用空闲会话会启动运行,等价于占用一个新并发额度,同样受并发上限约束(避免绕过「每实例最多 2 个」的硬规则);
  • --title 命中已有会话但 --dir 与它的目录不一致时,会打印 [!] 复用了已有会话 …,已忽略 --dir …;如需在指定目录新建请加 --new;
  • 目标不是 CFC 会话 → 拒绝,退出码 5(--force 才放行);
  • 生产实例(danger)→ 拒绝,退出码 4(--allow-prod 才放行)。

4.5) cleanup —— 清理超期未用的 CFC 会话

# 安全预览:列出各实例上超过 10 天未使用、且当前未在运行的 CFC 会话
python hub.py cleanup

# 自定义阈值(天)
python hub.py cleanup --ttl-days 30

# 真正删除(默认只预览!必须显式 --execute)
python hub.py cleanup --execute

# 仅清理某个实例 / 生产实例
python hub.py cleanup --instance lan-189
python hub.py cleanup --allow-prod --execute

判定口径:

  • 以会话的「最后更新时间」(time.updated)为「最后使用」依据;早于 now - ttl_days 即视为过期;
  • 当前正在运行的会话一律跳过(不删);
  • time.updated 缺失(≤0)的会话保守跳过(避免误删只是未采集到时间的新鲜会话);
  • danger 实例(生产)默认跳过,需 --allow-prod;
  • 工具只枚举 CFC 会话,因此只清理 CFC 会话(非 CFC 会话不在范围内)。

默认安全:cleanup 默认只统计、不删除(dry-run);只有显式 --execute 才会真正 DELETE。删除不可恢复,建议先用 cleanup(无参数)预览确认。

5) interrupt —— 中断运行

python hub.py interrupt --session ses_xxx --instance lan-248
python hub.py interrupt --session ses_xxx              # 不指定实例则全实例搜索

同样受「非 CFC 会话 → 5」「生产 → 4」闸门约束。

6) watch —— 观察运行状态变化

python hub.py watch                 # 每 10s 打印一次状态变化
python hub.py watch --interval 5
python hub.py watch --once          # 只打印一次当前状态
python hub.py watch --instance lan-189 --verbose

只打印变化(新运行 / 开始运行 / 运行结束 / 会话消失),Ctrl+C 退出。


七、并发安全机制(为什么不会突破上限)

dispatch 的新建路径由两级机制保护,避免「检查 → 创建」之间的竞态:

  1. 实例互斥锁 ~/.xaf_agent_hub/locks/<instance>.lock
    • Windows 用 msvcrt.locking,POSIX 用 fcntl.flock,非阻塞轮询 + 超时(默认 20s,超时 → 退出码 3 并提示「未获取到调度锁」);
    • 持锁期间完成:读 /api/session/active → 统计 running_cfc → 加上有效在途预留 → 判定 → 创建会话 → 投递指令。
  2. 槽位预留 ~/.xaf_agent_hub/slots/<instance>.<token>.slot(JSON,含 pid / 创建时间 / session_id)
    • 判定式:running_cfc + 有效预留数 >= concurrency_limit 即拒绝;
    • 创建成功后写回真实 session_id 并重置计时,保留 slot_ttl_seconds(默认 60s)作为 /api/session/active 的传播窗口;
    • 超过 TTL、会话已不在 active 且超过 grace、或持锁进程已退出 → 自动清理,不会死占额度;
    • 失败/异常路径 try/finally 立即释放。

实测(3 个进程同时 dispatch,起点 running_cfc=0):恰好 2 个成功、1 个退出码 3,复核 running_cfc=2。

说明:--dry-run 只做预检(不做锁定),因此两次 dry-run 之间若有人真实调度,结果可能变化;真实执行时会重新加锁复核。


八、退出码

退出码 含义 典型场景
0 成功 正常输出 / 投递成功 / dry-run 通过
1 内部错误(未预期异常) 打印简短原因,加 --debug 看调用栈
2 实例不可达 DNS/连接失败、超时、5xx、401 认证被拒、响应中断
3 触发并发上限 / 未取得调度锁 目标实例 running_cfc + 预留 >= 2;或锁等待超时
4 参数 / 配置错误 未知实例、--text 为空、配置 JSON 非法、生产未放行、导出格式不支持
5 目标不是 CFC 会话 误操作非 CFC 会话(需 --force)

九、导出产物

python hub.py sessions --out out/cfc_sessions.json --html cfc_sessions_report.html
python hub.py sessions --out out/cfc_sessions.csv
  • out/cfc_sessions.json:完整结构化数据(generated_at / by_instance / unreachable / instances / sessions);
  • out/cfc_sessions.csv:9 列清单,utf-8-sig 编码,Excel 双击可用;
  • cfc_sessions_report.html:单文件、无外部依赖(无 CDN / 无图片 / 无 src=),顶部统计卡(实例健康数、CFC 会话总数、正在运行数、各实例分布),下方按实例分组表格,输入框实时过滤标题 / 目录 / 会话 ID,深色标题栏 + 浅色内容,字体优先 "Microsoft YaHei", system-ui。

十、故障排查

现象 排查方向
不可达: DNS 解析失败 域名写错或本机 DNS 不通;pub-iwintrue 必须用 http
不可达: 认证被拒 (HTTP 401) 密码错。检查 config.local.json,或用 XAF_HUB_PW_<实例> 覆盖
不可达: HTTP 502 实例服务挂了 / 反代异常(检查端口是否为 4091 而非 4090)
不可达: 响应中断 (IncompleteRead) 实例端连接被截断;工具已重试 1 次并标记该实例不可达,不影响其他实例
[并发上限] … 未获取到调度锁 另有 dispatch 正在执行;等几秒重试,或调大 lock.lock_timeout_seconds
满载但想给运行中的会话追加指令 用 --session <正在运行的 CFC 会话>(复用不占新额度)
明明目录对却复用了别的会话 --title 会优先匹配已有会话,加 --new 强制新建
CFC 会话数明显偏少 检查 cfc_roots 是否遗漏新目录;或该目录被 exclude_dirs 命中
CFC 会话数明显偏多 兜底段 cfc/caremate/xaf 命中了无关目录,把该目录加到 exclude_dirs
服务端只返回 50 条会话 page_size 被改小;工具默认显式传 limit=8000
投递成功但会话没反应 指令是异步的:hub.py watch --interval 5 观察;或该会话正被其他运行占用
新建会话落在了错误目录 只有 location.directory 生效;--dir 必须属于该实例的 CFC 根目录

调试技巧:instances 看健康 → status 看负载/预留 → sessions --instance X --json --limit 5 看会话 → --debug 看调用栈。

性能参考(6 实例、约 2800 条会话,本机实测):dispatch --dry-run ≈ 3–5s、status ≈ 7s、watch --once ≈ 7.5s、sessions ≈ 7s。 dispatch 走「只取运行态 + 按需单查会话」的快路径,不再拉全量会话列表。


十一、已知限制

  1. opencode 没有 /api/version:版本号取自 GET /api/info({"version": "2.0.32", ...}),无此端点时显示 -。
  2. 并发判定依赖 /api/session/active:槽位预留只能覆盖本工具的调度;若有人绕过本工具、直接用别的客户端开跑,实例端仍可能超载。运行中会话若连目录都查不到(GET /api/session/:id 失败),会按「占用额度」处理(fail-closed)并打印 [warn],宁可误拦不漏拦。
  3. 预留的传播窗口为 slot_grace_seconds(5s):会话创建后 5s 内若已结束且 /active 不再返回,会短暂计入占用(偏保守)。
  4. 自动选实例只按「运行中 CFC → CFC 会话数 → 名称」排序,不看 CPU/内存真实负载。
  5. --keyword 只匹配标题,不搜会话正文。
  6. 兜底路径段匹配是启发式:目录名含 cfc/caremate/danshop/xaf 作为独立路径段即判为 CFC;同名无关目录请加入 exclude_dirs。
  7. status / instances / sessions 为统计 CFC 总数必须拉全量会话,耗时随会话数增长(约 7s)。
  8. CSV 的 消息成本 来自 session 的 cost 字段,未开计费时恒为 0.0000。
  9. 会话被删除后无法恢复;dispatch --new 会真实创建会话,请确认目录与用途;cleanup --execute 会真实删除过期 CFC 会话(默认仅预览)。
  10. 生产实例(prod-etotem)默认只读,工具不会在没有 --allow-prod 的情况下向其发送任何指令。
  11. 智能匹配是词面匹配:仅基于会话「标题 + 目录」的字符 n-gram 相似度,不读取会话正文 / 消息历史;标题信息量过低或需求措辞差异过大时可能匹配不到(退化新建),此时可显式用 --session / --title 或调高 match_threshold。
  12. cleanup 只枚举并清理 CFC 会话(按 cfc_roots 判定),非 CFC 会话不受影响;time.updated 缺失的会话会被跳过而非删除。

十二、定时巡检(loop_resume)与派发任务池

1) 巡检链路

loop_resume.py 是常驻/定时巡检编排器(WorkBuddy 自动化「XAF Hub 巡检·10分错峰01-06」每小时错峰跑 loop_resume.py --once)。每个周期依次执行:

fix_free_limit  →  check_agents  →  sync_design_tasks  →  fill_slots
  额度自愈           执行情况检查        需求/设计进度扫描        任务池补位派发
  • fix_free_limit:撞上「免费额度耗尽 / 限流 429」的会话自动切链上下一档自愈;若命中 opencode 家族模型的 429/限流,会把 opencode 全家族封禁至次日 08:00(见「opencode 429 黑窗」节);
  • check_agents:检查各实例会话运行/完成/失败,恢复中断会话,产出巡检报告(含「待分派」节,读取任务池 + 主理人台账);
  • sync_design_tasks(2026-10-25 新增):扫描需求/设计文档,按完成进度生成 pending 任务写入任务池(见下);
  • fill_slots:把每个实例的并发位补满——从 tasks_backlog.json 取 status=pending 的任务派发,成功后标 dispatched,会话结束且 succeeded 标 done。

可用开关:--no-freelimit / --no-check / --no-design / --no-fill(分别对应跳过某步);--design-script 可换扫描脚本。

2) 派发任务池 tasks_backlog.json

自动任务池,字段:id / title / priority(P0-P3) / module / instance(空=通用) / status(pending|dispatched|done) / spec(设计文档相对路径) / prompt(派发正文) / session_id / dispatched_at / dispatched_to / model / done_at。fill_slots 按优先级取 pending 派发,三级取用:指定本实例 → 通用 → 借用其他实例(各实例都有完整仓库副本)。

治理纪律(2026-10-25 定稿):

  • 未派发的任务必须进入任务池(tasks_backlog.json 加一条 pending),由巡检自动补位,不要在群里口头记;
  • 人工直接 dispatch 的任务也应回填 status=dispatched + session_id(或先入池再让 fill_slots 派),保证池子与实际一致;
  • 主理人台账 lead_backlog.md 只放「等你拍板 / 决策类」事项,不放可自动派发的执行任务;
  • 所有会话完成一项任务后必须自行 git commit + git push 并回报 commit 哈希,不需要主理人确认(合并主干仍需人工裁决)——本工具派给远程 opencode 智能体的每一条任务指令都必须带上这条纪律;
  • 纪律文案由 discipline.py 的 单一事实源统一注入派发链路,且按目标实例的分支策略决定口径: sync_design_tasks(生成任务的业务正文,纪律不再内嵌)、fill_slots(巡检补位)、resume_interrupted(恢复中断会话,check_agents 复用同一 RESUME_PROMPT)、hub.py dispatch(人工派发,dry-run 亦可见); fix_free_limit 的续做提示(CONTINUE_PROMPT)同步采用相同提交口径; 注入点收敛在 scheduler.plan_dispatch(目标实例经智能匹配/自动选路确定后才拼纪律),人工派发与巡检补位共用; tasks_backlog.json 顶层的 discipline 字段可作任务池级覆盖(按实例策略改写后注入)。

2.4) 分支策略:local 只在主分支,不分配其他分支的任务(2026-10-27 主理人定稿)

  • branch 模式(远端实例默认):基于 origin/cfclub 新建 feature/<slug> 分支,完成后 push 自己的分支,不合并主干——隔离开发,由主理人裁决合并;
  • mainline 模式(local 实例):直接 checkout 到 cfclub 主分支工作,不新建任何分支,完成后 push 到 origin/cfclub;
  • 判定口径:实例名以 local 开头即 mainline;也可在 config.json 实例级显式写 "branch_policy": "mainline"(显式声明优先,便于日后把某个远端实例也切到主分支口径);
  • lan-204 已显式声明 branch_policy: mainline(2026-10-29 主理人定稿):204 实例分派时保持在 cfclub 主分支工作,不再新建 feature/<slug> 分支;其余远端实例(lan-248 / lan-189 / prod-etotem / pub-iwintrue)仍为 branch 隔离口径; -派发时 discipline.rewrite_branch_to_mainline() 会把任务书/规格文档里写死的「新建 feature/ 分支」类指令改写为主分支口径,剩余由追加的 mainline 纪律段声明覆盖;
  • 事实性引用不被改写:判定=实质完成:分支 feature/u3 @ 300dd0765、「核验方违规在 feature/xxx 多推了 1 个空commit」这类核验结论只在其处于「新建/push/checkout/基于…」等指令上下文时才改写,避免历史记录失真;
  • 否定句受保护:「不要新建 feature/xxx 分支」「不要新建任何分支」原样保留,不会被兜底正则改成病句;
  • 远端实例行为完全不变(仍为 feature/<slug> 隔离口径);
  • 验证脚本 out/verify_branch_policy.py(37 项断言)。
  • 3) 需求/设计进度 → 自动生成任务(sync_design_tasks)

    sync_design_tasks.py 实现「检查需求/设计情况,根据完成进度生成新任务」:

    • 扫描 docs/superpowers/plans/ 与 docs/superpowers/specs/ 下 mtime ≤ 30 天 的 .md;
    • 仅匹配表格行(含 ≥2 个 |)中同时含待派发标记(待派发 / 未派发 / ⏳)与批次代号(F-?数字 / T数字 / W0.数字 / 批次 / 步骤 / 任务 / 阶段)的行;
    • 取首个单元格为 title、id 取批次代号(无则 AUTO-<slug>-<n>);
    • 去重:同 spec + (id 或 title) 已存在则跳过,只新增 pending、不自动关闭(关闭以智能体汇报 / 主理人核验为准);
    • 写入 tasks_backlog.json(仅在有新增时改写)。

    示例:CF 值统一方案文档把 F-8 行标为「待派发收尾」→ 巡检自动把 F-8 清理 作为 pending 任务入池,下一轮 fill_slots 即派发。 注意:池任务 spec 引用的设计文档必须已提交到 origin/cfclub,远端 opencode 才能读到;未提交的设计文档请先 commit,否则远程智能体会在 prompt 中被告知「文档不存在则停止并说明」。

    2.5) opencode 429 黑窗(2026-10-26 主理人定稿)

    口径:任务分派/模型切换时,无论哪个实例、哪个会话,只要 opencode 报 429(含免费档 free usage limit reached、日文本配额、rps exhausted / quota exhausted / rate limit 等以 429 呈现的限流),opencode 全家族模型(opencode/ 与 opencode-go/ 前缀)当日一律不可用,直到次日 08:00 才解禁。

    • 触发即生效:fix_free_limit.py 命中即写 .model_state.json 的 opencode_blackout_until_ms = 次日 08:00(含 reason/source/时间戳,可审计);同日内重复触发只延长到次日 08:00,不累加。
    • 派发/切换全链收敛:scheduler.resolve_instance_model / resolve_dispatch_model 都走 _blacked_out_models() 排除黑窗内的 opencode 家族——fill_slots、hub.py dispatch、fix_free_limit 的切换目标、实例链顺延全部生效。
    • 显式指定也不放行:resolve_dispatch_model(explicit=...) 对 opencode 家族不再走 ignore_blackout=True 的旧语义(2026-10-24 为防「显式模型被策略链替换回 opencode」而设),否则 config.default_dispatch_model = opencode/deepseek-v4.1-flash-free 会绕过封禁把新任务继续派到已 429 的免费档;非 opencode 的显式模型仍保持「显式优先」语义。
    • 非 429 不封禁:endpoint is unavailable / no-route / model unavailable / provider.internal / not available in the current token plan 属「模型不可用」而非限流,只切下一档、**不触发全家族封禁(常量清单见scheduler.RATE_LIMIT_PATTERNS`)。
    • 写黑窗的四个入口统一到单一事实源 scheduler.mark_opencode_blackout(reason, source):fix_free_limit(idle + running 两条路径)、hub.py 的 confirm-wait 配额降级、confirm_session.py fix 子命令。
    • 修复的历史问题:2026-10-26 12:54→13:02 出现「axon→切回 opencode free→openencode 又 429→切回 axon」的反复横跳——根因是只有 confirm_session.py 会写黑窗,巡检链(fix_free_limit 等)从不调用它;现已把写黑窗接到巡检链并改为「先命中→先写黑窗→再算切换目标」的判定顺序。
    • 验证脚本:out/_verify_blackout.py(30 项断言:写入/解禁时刻、三个入口跳过 opencode、过期恢复、429 vs 非429特征边界、家族识别)。