kc-beta 0.7.3 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +57 -4
  2. package/bin/kc-beta.js +20 -6
  3. package/package.json +3 -2
  4. package/src/agent/engine.js +493 -132
  5. package/src/agent/pipelines/_advance-hints.js +92 -0
  6. package/src/agent/pipelines/_milestone-derive.js +387 -17
  7. package/src/agent/pipelines/initializer.js +4 -1
  8. package/src/agent/pipelines/skill-authoring.js +30 -1
  9. package/src/agent/skill-loader.js +433 -111
  10. package/src/agent/tools/agent-tool.js +2 -2
  11. package/src/agent/tools/consult-skill.js +127 -0
  12. package/src/agent/tools/copy-to-workspace.js +4 -3
  13. package/src/agent/tools/dashboard-render.js +48 -1
  14. package/src/agent/tools/document-parse.js +31 -2
  15. package/src/agent/tools/phase-advance.js +17 -13
  16. package/src/agent/tools/release.js +378 -8
  17. package/src/agent/tools/sandbox-exec.js +65 -8
  18. package/src/agent/tools/worker-llm-call.js +95 -15
  19. package/src/agent/tools/workspace-file.js +7 -7
  20. package/src/agent/workspace.js +25 -4
  21. package/src/cli/components.js +4 -1
  22. package/src/cli/index.js +97 -1
  23. package/src/config.js +20 -3
  24. package/src/marathon/driver.js +217 -0
  25. package/src/marathon/prompts.js +93 -0
  26. package/template/.env.template +16 -0
  27. package/template/AGENT.md +182 -7
  28. package/template/skills/en/{meta-meta/auto-model-selection → auto-model-selection}/SKILL.md +1 -0
  29. package/template/skills/en/{meta-meta/bootstrap-workspace → bootstrap-workspace}/SKILL.md +15 -0
  30. package/template/skills/{zh/meta → en}/compliance-judgment/SKILL.md +1 -0
  31. package/template/skills/en/{meta/confidence-system → confidence-system}/SKILL.md +1 -0
  32. package/template/skills/en/{meta/corner-case-management → corner-case-management}/SKILL.md +1 -0
  33. package/template/skills/en/{meta/cross-document-verification → cross-document-verification}/SKILL.md +1 -0
  34. package/template/skills/en/{meta-meta/dashboard-reporting → dashboard-reporting}/SKILL.md +1 -0
  35. package/template/skills/en/{meta/data-sensibility → data-sensibility}/SKILL.md +1 -0
  36. package/template/skills/{zh/meta → en}/document-chunking/SKILL.md +1 -0
  37. package/template/skills/en/{meta/document-parsing → document-parsing}/SKILL.md +1 -0
  38. package/template/skills/{zh/meta → en}/entity-extraction/SKILL.md +1 -0
  39. package/template/skills/en/{meta-meta/evolution-loop → evolution-loop}/SKILL.md +1 -0
  40. package/template/skills/en/{meta-meta/pdf-review-dashboard → pdf-review-dashboard}/SKILL.md +1 -0
  41. package/template/skills/en/{meta-meta/quality-control → quality-control}/SKILL.md +10 -0
  42. package/template/skills/en/{meta-meta/rule-extraction → rule-extraction}/SKILL.md +1 -0
  43. package/template/skills/en/{meta-meta/rule-graph → rule-graph}/SKILL.md +1 -0
  44. package/template/skills/en/{meta-meta/skill-authoring → skill-authoring}/SKILL.md +40 -0
  45. package/template/skills/en/skill-creator/SKILL.md +2 -1
  46. package/template/skills/en/{meta-meta/skill-to-workflow → skill-to-workflow}/SKILL.md +58 -4
  47. package/template/skills/en/{meta-meta/task-decomposition → task-decomposition}/SKILL.md +1 -0
  48. package/template/skills/en/{meta/tree-processing → tree-processing}/SKILL.md +1 -0
  49. package/template/skills/en/{meta-meta/version-control → version-control}/SKILL.md +1 -0
  50. package/template/skills/en/{meta-meta/work-decomposition → work-decomposition}/SKILL.md +51 -6
  51. package/template/skills/phase_skills.yaml +112 -0
  52. package/template/skills/zh/{meta-meta/auto-model-selection → auto-model-selection}/SKILL.md +1 -0
  53. package/template/skills/zh/{meta-meta/bootstrap-workspace → bootstrap-workspace}/SKILL.md +15 -0
  54. package/template/skills/zh/compliance-judgment/SKILL.md +83 -0
  55. package/template/skills/zh/{meta/confidence-system → confidence-system}/SKILL.md +1 -0
  56. package/template/skills/zh/{meta/corner-case-management → corner-case-management}/SKILL.md +1 -0
  57. package/template/skills/zh/{meta/cross-document-verification → cross-document-verification}/SKILL.md +1 -0
  58. package/template/skills/zh/{meta-meta/dashboard-reporting → dashboard-reporting}/SKILL.md +1 -0
  59. package/template/skills/zh/{meta/data-sensibility → data-sensibility}/SKILL.md +1 -0
  60. package/template/skills/zh/document-chunking/SKILL.md +40 -0
  61. package/template/skills/zh/document-parsing/SKILL.md +102 -0
  62. package/template/skills/zh/entity-extraction/SKILL.md +121 -0
  63. package/template/skills/zh/{meta-meta/evolution-loop → evolution-loop}/SKILL.md +1 -0
  64. package/template/skills/zh/{meta-meta/pdf-review-dashboard → pdf-review-dashboard}/SKILL.md +1 -0
  65. package/template/skills/zh/{meta-meta/quality-control → quality-control}/SKILL.md +10 -0
  66. package/template/skills/zh/{meta-meta/rule-extraction → rule-extraction}/SKILL.md +1 -0
  67. package/template/skills/zh/{meta-meta/rule-graph → rule-graph}/SKILL.md +1 -0
  68. package/template/skills/zh/{meta-meta/skill-authoring → skill-authoring}/SKILL.md +40 -0
  69. package/template/skills/zh/skill-creator/SKILL.md +205 -200
  70. package/template/skills/zh/skill-to-workflow/SKILL.md +243 -0
  71. package/template/skills/zh/{meta-meta/task-decomposition → task-decomposition}/SKILL.md +1 -0
  72. package/template/skills/zh/tree-processing/SKILL.md +126 -0
  73. package/template/skills/zh/{meta-meta/version-control → version-control}/SKILL.md +1 -0
  74. package/template/skills/zh/{meta-meta/work-decomposition → work-decomposition}/SKILL.md +49 -4
  75. package/template/workflows/common/llm_client.py +168 -0
  76. package/template/workflows/common/utils.py +132 -0
  77. package/template/CLAUDE.md +0 -150
  78. package/template/skills/en/meta/compliance-judgment/SKILL.md +0 -82
  79. package/template/skills/en/meta/document-chunking/SKILL.md +0 -32
  80. package/template/skills/en/meta/entity-extraction/SKILL.md +0 -120
  81. package/template/skills/zh/meta/document-parsing/SKILL.md +0 -101
  82. package/template/skills/zh/meta/tree-processing/SKILL.md +0 -121
  83. package/template/skills/zh/meta-meta/skill-to-workflow/SKILL.md +0 -188
  84. /package/template/skills/en/{meta/compliance-judgment → compliance-judgment}/references/output-format.md +0 -0
  85. /package/template/skills/en/{meta/cross-document-verification → cross-document-verification}/references/contradiction-taxonomy.md +0 -0
  86. /package/template/skills/en/{meta-meta/dashboard-reporting → dashboard-reporting}/scripts/generate_dashboard.py +0 -0
  87. /package/template/skills/en/{meta/document-parsing → document-parsing}/references/parser-catalog.md +0 -0
  88. /package/template/skills/en/{meta-meta/evolution-loop → evolution-loop}/references/convergence-guide.md +0 -0
  89. /package/template/skills/en/{meta-meta/pdf-review-dashboard → pdf-review-dashboard}/scripts/generate_review.js +0 -0
  90. /package/template/skills/en/{meta-meta/quality-control → quality-control}/references/qa-layers.md +0 -0
  91. /package/template/skills/en/{meta-meta/quality-control → quality-control}/references/sampling-strategies.md +0 -0
  92. /package/template/skills/en/{meta-meta/rule-extraction → rule-extraction}/references/chunking-strategies.md +0 -0
  93. /package/template/skills/en/{meta-meta/skill-authoring → skill-authoring}/references/skill-format-spec.md +0 -0
  94. /package/template/skills/en/{meta-meta/skill-to-workflow → skill-to-workflow}/references/worker-llm-catalog.md +0 -0
  95. /package/template/skills/en/{meta-meta/task-decomposition → task-decomposition}/references/decision-matrix.md +0 -0
  96. /package/template/skills/en/{meta-meta/version-control → version-control}/references/trace-id-spec.md +0 -0
  97. /package/template/skills/zh/{meta/compliance-judgment → compliance-judgment}/references/output-format.md +0 -0
  98. /package/template/skills/zh/{meta/cross-document-verification → cross-document-verification}/references/contradiction-taxonomy.md +0 -0
  99. /package/template/skills/zh/{meta-meta/dashboard-reporting → dashboard-reporting}/scripts/generate_dashboard.py +0 -0
  100. /package/template/skills/zh/{meta/document-parsing → document-parsing}/references/parser-catalog.md +0 -0
  101. /package/template/skills/zh/{meta-meta/evolution-loop → evolution-loop}/references/convergence-guide.md +0 -0
  102. /package/template/skills/zh/{meta-meta/pdf-review-dashboard → pdf-review-dashboard}/scripts/generate_review.js +0 -0
  103. /package/template/skills/zh/{meta-meta/quality-control → quality-control}/references/qa-layers.md +0 -0
  104. /package/template/skills/zh/{meta-meta/quality-control → quality-control}/references/sampling-strategies.md +0 -0
  105. /package/template/skills/zh/{meta-meta/rule-extraction → rule-extraction}/references/chunking-strategies.md +0 -0
  106. /package/template/skills/zh/{meta-meta/skill-authoring → skill-authoring}/references/skill-format-spec.md +0 -0
  107. /package/template/skills/zh/{meta-meta/skill-to-workflow → skill-to-workflow}/references/worker-llm-catalog.md +0 -0
  108. /package/template/skills/zh/{meta-meta/task-decomposition → task-decomposition}/references/decision-matrix.md +0 -0
  109. /package/template/skills/zh/{meta-meta/version-control → version-control}/references/trace-id-spec.md +0 -0
@@ -0,0 +1,243 @@
1
+ ---
2
+ name: skill-to-workflow
3
+ tier: meta
4
+ description: 将一条已通过测试的验证 skill 蒸馏为带 worker LLM 提示词的 Python workflow。当某条规则 skill 已经过测试、达到 `.env` 中定义的 SKILL_ACCURACY 阈值时使用。覆盖如下决定:哪些部分用代码实现、哪些部分用 LLM 调用;针对小上下文窗口的提示词工程;模型层级选择与渐进式降级;以及如何用编码 agent 自己的 skill 结果作为 ground truth 来测试 workflow。也用于对已有 workflow 做成本或速度上的优化。
5
+ ---
6
+
7
+ # Skill 到 Workflow(Skill to Workflow)
8
+
9
+ skill 是 ground truth。workflow 是更便宜、更快的近似。你的工作是让这个近似在尽可能便宜的同时,逼近原版的精度。
10
+
11
+ ## 工程目标
12
+
13
+ 优化整条链路:**最短 workflow**(节点数最少)→ **每个节点用最小模型**(在满足精度的前提下用最便宜的层级)→ **每个模型用最短提示词**(最少 token)。这才是工程目标——不是提示词模板的华丽程度,也不是某种框架的合规性。
14
+
15
+ ## 何时开始
16
+
17
+ 满足以下条件时,一条 skill 才算准备好被蒸馏为 workflow:
18
+
19
+ - 它已经在 Samples/ 下所有文档上跑过测试。
20
+ - 它的准确率达到或超过 `.env` 中的 SKILL_ACCURACY 阈值。
21
+ - 边缘案例都记录在 skill 的 `assets/corner_cases.json` 里。
22
+ - 你对这条规则的理解,已经足以一字一句地说清楚自己是怎么验证它的。
23
+
24
+ 任意一条不成立,就回去继续迭代 skill,先别开始蒸馏。
25
+
26
+ ## 蒸馏决策
27
+
28
+ 对基于 skill 的验证流程里每一步,问自己:
29
+
30
+ ### 这一步能用正则或 Python 完成吗?(成本:零)
31
+ - 已知格式的日期抽取 → 正则
32
+ - 阈值数值比较 → Python 算术
33
+ - 中文数字转换 → Python 查表
34
+ - 格式校验(身份证号、代码) → 正则
35
+ - 从结构化 markdown 抽表格单元 → 字符串处理
36
+
37
+ 如果能,就写成代码。这类操作免费、快速、确定性强。
38
+
39
+ ### 这一步需要语言理解吗?(成本:一次 worker LLM 调用)
40
+ - 在文档里找出相关段落 → LLM
41
+ - 抽取一个用自然语言描述的实体 → LLM
42
+ - 判定语义充分性("披露是否充分") → LLM
43
+ - 解析有歧义的引用 → LLM
44
+
45
+ 如果是,就设计一个 worker LLM 提示词。在保证精度的前提下,用最小的模型层级。
46
+
47
+ ### 混合方案(最常见)
48
+ 大多数规则是混合体:正则抽数字,Python 比阈值,LLM 处理少数特殊情形。把 workflow 设计成流水线——便宜的步骤先跑,昂贵的步骤只在需要时才跑。
49
+
50
+ ### 正则不足以应付时——决策标准
51
+
52
+ 在宣布蒸馏完成之前,先审计每条规则的 `verification_type` / `metric` / `evidence_type`(或目录里对应的字段)。如果某条规则所需的验证属于以下类型之一:
53
+
54
+ - **语义** 判断
55
+ - **上下文** 解读
56
+ - **反事实** 推理
57
+ - **跨字段算术**
58
+
59
+ 仅靠正则几乎肯定不够。可接受的形式有三种:
60
+
61
+ 1. **纯正则,附带显式限制说明** —— 写正则核查,并在注释里说明脆弱性(例如:"只匹配语法模式;无法检测语义保证")
62
+ 2. **正则 + LLM 混合** —— 正则基线处理明显的情形,`worker_llm_call`(tier1-2)处理有歧义的情形。混合 workflow 要显式声明哪些 rule_id 会被上升到 LLM。
63
+ 3. **纯 LLM,通过 `worker_llm_call`** —— 对完全语义化、没有有意义正则基线的规则。
64
+
65
+ 对 `verification_type` 是 `judgment` / `semantic` 的规则,不要只交付一段纯正则、又不附"显式限制"的说明。未来的你或同事会以为正则就够用——这种 bug 能埋藏好几个月。
66
+
67
+ ### Worker LLM 的成本-意识层级选择
68
+
69
+ 如果确实要上 LLM:
70
+ - **tier1**(能力最强,~¥0.001-0.002/doc):跨字段推理、歧义解析、能受益于 chain-of-thought 的规则
71
+ - **tier2-3**:批量抽取 + 简单语义检查
72
+ - **tier4**(最便宜):正则无法覆盖、量又很大的关键词识别。注意:SiliconFlow 上的 tier4 模型是 Qwen3.5 thinking 模式——如果 `reasoning_content` 把 max_tokens 用光,`content` 可能返回空字符串。在依赖之前先用真实提示词测试。如果出现空响应,要么把 max_tokens 提到 ≥8192,要么缩短提示词,要么回退到 tier1-2。
73
+
74
+ v0.7.1 两位审计 conductor(DS 和 GLM)默认都走全正则蒸馏,只有当用户显式要求"V2,带 worker LLM"时才加上 LLM 上升路径。如果你的规则目录里有任何一条规则的验证本质上就是语义性的,你应当主动伸手去用 `worker_llm_call`——不要等别人要你才用。
75
+
76
+ ## Workflow 结构
77
+
78
+ 一个 workflow 是 `workflows/` 下的一个 Python 文件(或几个相关的小文件):
79
+
80
+ ```
81
+ workflows/
82
+ rule_001_capital_adequacy/
83
+ workflow_v1.py # The main workflow script
84
+ prompts/
85
+ extract.txt # Worker LLM prompt for extraction
86
+ judge.txt # Worker LLM prompt for judgment (if needed)
87
+ config.json # Model assignments, thresholds
88
+ ```
89
+
90
+ workflow 文件应当有清晰的入口:
91
+
92
+ ```python
93
+ def verify(document_text: str, config: dict) -> dict:
94
+ """
95
+ Returns:
96
+ {
97
+ "rule_id": "R001",
98
+ "result": "pass" | "fail" | "missing" | "error",
99
+ "extracted_value": ...,
100
+ "confidence": 0.0-1.0,
101
+ "comment": "..." (only when fail),
102
+ "model_used": "...",
103
+ "llm_calls": int,
104
+ "llm_tokens": int
105
+ }
106
+ """
107
+ ```
108
+
109
+ 这是参考,不是死契约。按具体规则的需要调整结构。重要的是每个 workflow 都能产出可以与 skill ground truth 做对比的结果。
110
+
111
+ ## Worker LLM 的提示词工程
112
+
113
+ worker LLM 的上下文窗口较小(典型 16K-32K token)。设计提示词时要满足:
114
+
115
+ 1. **自包含。** 模型需要的一切都写进提示词。不要假设模型还记得之前几次调用的上下文。
116
+ 2. **指定输出格式。** "返回一个 JSON 对象,字段包括:value、confidence、reasoning。" 结构化输出能减少解析错误。
117
+ 3. **只送进窄化后的上下文。** 不要把整篇文档喂给它。用树状处理流水线(整篇文档 → 相关章 → 相关节)把上下文窄化之后再调 worker LLM。
118
+ 4. **提示词用文档同语言。** 中文文档配中文提示词,英文文档配英文提示词。不要在同一份提示词里混用两种语言。
119
+ 5. **示例要克制。** 一两个例子有用,十个例子既浪费上下文窗口、又容易过拟合。
120
+
121
+ ## 模型层级选择
122
+
123
+ 对每一步,先用最高层级(TIER1)。测精度。再尝试更低的层级:
124
+
125
+ 1. 用 TIER1 在所有 Samples/ 上跑 workflow,记录每一步的精度。
126
+ 2. 对每一步,换 TIER2 试一次。如果精度仍高于 WORKFLOW_ACCURACY,就保留 TIER2。
127
+ 3. 继续逐步降级,直到精度跌破阈值。
128
+ 4. 把每一步的最优层级写进 `config.json`。
129
+
130
+ 同一个 workflow 内不同步骤可以用不同层级。抽取也许需要 TIER2,而判断也许用 TIER3 就够。
131
+
132
+ ### 正式降级协议
133
+
134
+ 上面这种基础做法可用,但更严格的协议能避免过早锁定层级:
135
+
136
+ **方向**:从上往下(TIER1 → TIER4),先确立精度上限。你得先知道最优精度能到哪里,才能开始用它换成本。
137
+
138
+ **最小测试样本**:在做层级决定之前,每个候选层级至少跑足够数量的文档(例如 `min(10, total_samples)`)。小样本不可靠——3 篇文档的测试可能完全误导你。
139
+
140
+ **精度差触发条件**:如果某个较低层级的精度明显低于较高层级(例如差超过 5 个百分点),该步就保留较高层级。差距在容差内,就用更便宜的层级。
141
+
142
+ **逐步独立**:每个 workflow 步骤单独评估。把每一步的最优层级写到 `config.json` 里。不要假设整条 workflow 都得用同一个层级。
143
+
144
+ **再评估触发条件**:如果生产质控发现某一步的精度在退化(例如出现了新格式的文档),就对那一步重跑层级评估。
145
+
146
+ **模型-任务推荐表**:维护一份"任务类型 → 推荐层级"的项目级映射,基于你自己的测试经验。时间长了,这些表可以跨项目汇总,形成通用的层级建议。
147
+
148
+ 这里所有数字(10 篇文档、5 个百分点等)都只是推荐起点。编码 agent 和开发者用户应当根据具体的体量、精度要求、成本约束做校准——甚至彻底替换为别的评估方法。重要的是模式:**在每个层级测试 → 对比精度 → 在容差内时锁定 → 退化时再评估**。
149
+
150
+ 这与 `document-parsing` 里 parser 上升的层级转移框架是同一套:由一个质量/精度评分驱动"保留 / 上升 / 跳过"的决定。
151
+
152
+ ## 用 Ground Truth 做测试
153
+
154
+ 编码 agent 基于 skill 的结果就是 ground truth。对 Samples/ 下每篇文档:
155
+
156
+ 1. 跑一遍 workflow。
157
+ 2. 把 workflow 的结果与 skill 的结果对比。
158
+ 3. 记录差异:哪一步失败,期望值 vs 实际值。
159
+ 4. 计算精度:`(匹配的结果数) / (总文档数)`。
160
+ 5. 如果精度 < WORKFLOW_ACCURACY,定位并修复。用 `evolution-loop` 方法学。
161
+
162
+ ## 版本管理
163
+
164
+ 每次迭代是一个新版本文件:`workflow_v1.py`、`workflow_v2.py`,依此类推。在 `config.json` 里追踪当前激活的版本。完整方法学见 `version-control` skill。
165
+
166
+ ## Workflow 发布
167
+
168
+ workflow 达到精度阈值后,就可以通过 `release` 工具打包给最终用户。每次发布是 `output/releases/<slug>/` 下的一个自包含目录,里面有钉住的 workflow、一个 Python 运行器、一个置信度评分器、一个 HTML 仪表盘生成器,以及一个 `serve.sh` 启动脚本。整个包不依赖 kc-beta——任何装了 Python 并有 worker LLM API key 的人都能跑 `python run.py <doc>` 并得到验证结果。
169
+
170
+ 打包什么内容由你决定:是 catalog 里所有规则,还是用 `include` 参数挑出来的子集;要不要捆绑 1-3 份代表性样本放到 `fixtures/`,好让接收方在没有自己数据的情况下也能空跑一遍。
171
+
172
+ `release` 工具会先给工作区打 git 快照(tag 是 `snap/release-<slug>`),即使 `output/releases/` 之后被清理,整个包也能从 git 再生。何时发布由你决定——没有自动化,也没有强制的节奏。常见触发点:workflow 达到 SKILL/WORKFLOW_ACCURACY 阈值;某位利益相关者需要交接;生产 cron 应该跑钉住的版本而不是最新版。和开发者用户讨论后决定。
173
+
174
+ ## 成本追踪
175
+
176
+ 追踪每次 workflow 运行的成本:
177
+ - 每篇文档的 LLM 调用次数。
178
+ - 每篇文档消耗的总 token 数。
179
+ - 每次调用使用的模型层级。
180
+
181
+ 这份数据帮助开发者用户理解生产成本,也为后续优化提供依据。
182
+
183
+ ## Worker LLM API
184
+
185
+ Worker LLM 通过 SiliconFlow API 访问。连接信息在 `.env` 里:
186
+ - `SILICONFLOW_API_KEY` —— 鉴权
187
+ - `SILICONFLOW_BASE_URL` —— API 端点
188
+ - `TIER1` 到 `TIER4` —— 各层级的模型名称
189
+
190
+ 各模型当前的能力与上下文窗口大小,见 `references/worker-llm-catalog.md`。
191
+
192
+ ## 两条访问路径:`worker_llm_call` 工具(优先)vs 直接 HTTP
193
+
194
+ KC 自带一个 `worker_llm_call` 工具。能用就用 —— 引擎能看到每次调用,能统计成本和 token、做限流、并把数据进入审计。v0.8 P2-B 增加了批量模式:
195
+
196
+ ```
197
+ worker_llm_call({
198
+ tier: "tier1",
199
+ prompts: ["核查文档 A...", "核查文档 B...", "核查文档 C..."],
200
+ system_prompt: "你是合规助手。返回 JSON {verdict, evidence, confidence}。",
201
+ concurrency: 5 // 1-10,默认 5
202
+ })
203
+ ```
204
+
205
+ 返回 `{n_total, n_succeeded, n_failed, total_tokens_in, total_tokens_out, results: [...]}` 摘要。部分失败不会让整批失败。
206
+
207
+ ### 规范的 `workflows/common/llm_client.py`(v0.8.1 起作为模板文件随包发布)
208
+
209
+ 对于一个 **独立运行** 的 workflow(没有 KC 会话 —— 比如客户把 release 包部署后跑 `python run.py doc.pdf`),workflow 拿不到 `worker_llm_call`。规范的 HTTP 客户端 shim 现在作为模板文件随 kc-beta 一起发布;引擎初始化时会自动把它写入工作区的 `workflows/common/llm_client.py`。**不要自己重写**。直接用这个已经放好的文件:
210
+
211
+ ```python
212
+ from workflows.common.llm_client import call
213
+
214
+ result = call(
215
+ tier="tier2",
216
+ prompt=user_prompt,
217
+ system_prompt="你是合规助手。返回 JSON。",
218
+ max_tokens=2048,
219
+ )
220
+ # result = {"response": "...", "model_used": "...", "tier": "tier2",
221
+ # "tokens_in": N, "tokens_out": N}
222
+ ```
223
+
224
+ shim 做的事:
225
+ - 从 `.env` 读 `LLM_API_KEY` + `LLM_BASE_URL` + `TIER1..4`(多 provider 友好 —— SiliconFlow、OpenAI、Anthropic、阿里、火山等都能用)
226
+ - 以 OpenAI 兼容的 chat completions 格式发请求到配置好的 base URL
227
+ - 每次调用往 `output/llm_ledger.jsonl` 写一行,KC 审计即使在你没走 worker_llm_call 时也能还原成本
228
+ - 如果 `LLM_BASE_URL` 缺失,会显式抛错(不会偷偷回退到某个写死的 vendor URL)
229
+
230
+ **不要自己从零写 llm_client.py**。v0.7.x → v0.8 的三个连续会话里都出现过 agent 自己造轮子 —— 拼出来的版本要么模型 ID 过期、要么写死 SiliconFlow、要么不写 ledger,且对引擎不可见。优先用规范化版本;如果因为某种原因没有,从 kc-beta 安装目录的 `template/workflows/common/llm_client.py` 复制过来(引擎也会在 init 时自动写入 —— 检查 events.jsonl 里的 `workflows_common_populated` 事件)。
231
+
232
+ ## sandbox_exec 超时设置(已知耗时长的命令)
233
+
234
+ `sandbox_exec` 默认超时是 120 秒。对于你预期会跑得更久的命令 —— LLM 批处理、大型回归测试、文档解析 —— 显式传 `timeout_ms`(最大 600000ms = 10 分钟)。不要靠把任务切成不必要的小块来绕开默认值;那只会浪费回合数并模糊意图。
235
+
236
+ ```
237
+ sandbox_exec({
238
+ command: "python scripts/v2_full_test.py",
239
+ timeout_ms: 480000 // 14 条规则 × 6 篇文档走 worker LLM,预留 8 分钟
240
+ })
241
+ ```
242
+
243
+ 如果已经顶到 10 分钟上限还在超时,把工作拆成多次调用,或者交给子代理(子代理的超时和父进程相互独立)。
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: task-decomposition
3
+ tier: meta-meta
3
4
  description: Decompose each verification rule into independent sub-tasks and assign the optimal method (rule, code, LLM, manual) to each. Use when converting extracted rules into implementation plans, when a rule skill is too expensive or inaccurate and needs restructuring, or when designing a multi-step verification pipeline. Covers MECE decomposition, method selection via the four-dimension decision matrix, cost-benefit analysis, and source tagging. Also use when auditing an existing workflow for cost optimization opportunities.
4
5
  ---
5
6
 
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: tree-processing
3
+ tier: meta
4
+ description: >
5
+ Design production-grade document chunking mechanisms for verification workflows. Use when
6
+ building the chunking step of a workflow that will run repeatedly on many documents.
7
+ The approach: observe sample documents, find structural patterns, write a chunking script
8
+ in code, that script runs in production. Also use for navigating large documents via
9
+ hierarchical structure when a rule targets a specific section.
10
+ For quick, cheap batch chunking during exploration, use document-chunking instead.
11
+ ---
12
+
13
+ # Tree Processing
14
+
15
+ 绝大多数验证规则并不需要整篇文档。它们只需要某个特定的章节、某张特定的表格、或某条特定的披露内容。树就是你在大型文档中高效导航的地图。把一份动辄数百页、上千页的法规摊在工作 LLM 面前,既装不进上下文窗口,也会被无关内容稀释关键事实。建好树之后,验证就从"在整片汪洋里捞针",收敛为"先按图找到房间,再在房间里翻箱倒柜"。
16
+
17
+ ## 生产级分块方法论
18
+
19
+ 对于需要处理大量文档的验证工作流而言,分块机制必须做到精确、一致且快速。"精确"意味着同一份文档被切出的边界总是落在正确的位置;"一致"意味着今天切和明天切的结果一模一样;"快速"意味着不会成为流水线里的瓶颈。要同时满足这三点,几乎只有"用代码固化结构规律"这一条路径。基本路径如下:
20
+
21
+ 1. **观察**:阅读 3 到 5 份样本文档。记录其结构特征——标题样式、编号方式、章节划分规律。不要只看一份就动手,小样本的偶然格式会把你引向一段过拟合的脆弱正则。
22
+ 2. **找出模式**:识别那些保持一致的元素(标题格式、编号约定、目录结构)。同时把不一致的部分单独列出来,这些就是后续脚本需要兜底处理的边缘情形。
23
+ 3. **编写代码**:设计一段分块脚本(基于正则的切分器、标题检测器、目录解析器),用代码固化所发现的模式。脚本应当是确定性的、可重入的,并且对输入文本的小幅扰动具有鲁棒性。
24
+ 4. **测试**:在样本上运行脚本。验证它产出的分块结果与你人工标注的边界一致。如果出现错切、漏切或越切,先回到第 1 步补充观察样本,再来调整规则。
25
+ 5. **部署**:脚本在生产工作流中正式运行。它是确定性的、零成本的、且执行迅速。一份脚本写好,就可以服务于同类型文档的全部后续处理。
26
+
27
+ 这与 `document-chunking` 不同(后者用于探索阶段的快速、低成本切分)。生产级分块是一次性的设计投入,但其收益会在同类型文档的所有处理过程中持续兑现。换言之,前者是面向"我先粗略看看这文档大致长什么样"的临时手段,后者是面向"我每天都要处理一千份这种文档"的工程资产。
28
+
29
+ ## 为什么要使用树
30
+
31
+ 两条理由,都很硬:
32
+
33
+ 1. **规则带有作用域。**"第 5 章中的风险披露必须包含……"——你需要定位第 5 章,而不是把 1000 页全部读一遍。验证类规则几乎天生就是带作用域的:它要么针对某个章节,要么针对某张表,要么针对某条具体的条款。把规则原原本本喂给一份完整文档,等于在让 LLM 自己先承担"找位置"这件本不该由它承担的工作。
34
+ 2. **工作 LLM 有上下文上限。**16K 到 32K 的上下文窗口装不下一份 1000 页的文档。你必须把范围收窄到相关章节。即便是更大的上下文窗口,只要你把无关内容也一起喂进去,准确率就会被稀释,延迟会上升,Token 成本也会随之上涨。
35
+
36
+ 树结构同时解决了这两个问题:它告诉你"东西在哪里",并让你只抽取"你真正需要的那部分"。从工程视角看,树是文档的索引;从语义视角看,树是规则与文本之间的桥梁。把这座桥建好,后续每一条规则的验证都会变得简单、可靠、可解释。
37
+
38
+ ## 构建树
39
+
40
+ ### 步骤 1:发现结构
41
+
42
+ 在动手实现树解析器之前,先去探查几份样本文档,找出其结构上的规律。这一步看似只是"翻一翻文档",但它直接决定了后续所有工程决策的下限。请把它当作严肃的需求调研来做,而不是顺手扫一眼。关注以下要素:
43
+
44
+ - **标题约定**:章是以 "Chapter X" 开头?"第X章"?"Part X"?还是罗马数字?同一份文档中,顶层与子层的标题格式是否一致?中英混排的文档是否两种约定并存?
45
+ - **编号体系**:"1.1.2"、"Article 3"、"(a)(i)"、还是层级化的编号?编号是否每章重置?是否存在跨章共享的全局编号?编号缺位或跳号的情况是个例还是规律?
46
+ - **视觉标记**:加粗字体、更大的字号、水平分隔线、章节前的分页符?这些信息在转成纯文本以后是否还能保留?如果输入是 PDF 解析后的文本,是否需要先在更早的环节注入这些标记?
47
+ - **目录(TOC)**:大多数正式文档都带有目录。它本身就是这份文档自带的树。目录还能告诉你页码区间、官方的层级深度、以及哪些标题是法定的、哪些是排版插入的。
48
+
49
+ 在这一步多花点时间。你找到的模式将直接决定:树构建器是一段简单的正则,还是一个复杂的解析器。经验上,凡是受监管发布的法规文档,几乎都遵循同一套排版规范;凡是来自不同机构的合并文档,则常常需要把多套规则同时纳入解析器的考量。
50
+
51
+ ### 步骤 2:选择解析器
52
+
53
+ **如果模式足够一致**(在受监管的法规类文档中通常都是一致的):
54
+ - 写一个基于正则的切分器。例如:
55
+ - `^第[一二三四五六七八九十百千]+章` 用于匹配中文的章标题
56
+ - `^Chapter \d+` 用于匹配英文章标题
57
+ - `^\d+\.\d+(\.\d+)*\s` 用于匹配带编号的小节
58
+ - 这种方案快速、确定、可靠。只要正则跑得通,优先选它。不要因为追求"看起来更智能"就放弃确定性的方案——确定性本身就是生产环境最稀缺、最值钱的属性。
59
+ - 在调试阶段,记得为正则写一组小型的单元测试:包括典型的命中样例、明确不应命中的反例,以及容易混淆的边界样例(比如标题中混入的全角空格、不可见控制字符)。
60
+
61
+ **如果模式不一致或根本不存在**:
62
+ - 使用 LLM 引导的"楔入式"切分方法(完整算法见 `rule-extraction/references/chunking-strategies.md`:滚动上下文窗口、K-token 引用比对、Levenshtein 模糊匹配)。
63
+ - 这种方式较慢,且要消耗 LLM 调用,但能处理非结构化的文档。滚动窗口的意义在于:即便是非常巨大的非结构化叶子节点,也可以逐段递进地完成切分。
64
+ - 一个务实的折中是混合策略:能用正则切到的层级先用正则切,正则啃不动的子节点再交给 LLM 引导式切分。这样可以把昂贵的 LLM 调用集中投放在真正需要语义判断的地方。
65
+
66
+ **如果文档自带目录**:
67
+ - 先解析目录。它免费地给了你一棵树的结构,外加每个节点的页码。
68
+ - 然后再用从目录派生出的结构去切分文档正文。
69
+ - 需要注意目录与正文之间偶尔会出现不一致(目录漏列了某节、或正文新增了目录里没有的小节)。把这些差异当作日志输出,便于后续人工核对,而不是默默吞掉。
70
+
71
+ ### 步骤 3:构建树
72
+
73
+ 树本身是一种简单的嵌套结构:
74
+
75
+ ```
76
+ Document
77
+ ├── Part I: General Provisions
78
+ │ ├── Chapter 1: Definitions (pages 1-15)
79
+ │ └── Chapter 2: Scope (pages 16-22)
80
+ ├── Part II: Capital Requirements
81
+ │ ├── Chapter 3: Minimum Capital (pages 23-45)
82
+ │ │ ├── Section 3.1: Tier 1 Capital
83
+ │ │ └── Section 3.2: Tier 2 Capital
84
+ │ └── Chapter 4: Risk Weighting (pages 46-78)
85
+ └── Part III: Disclosure
86
+ └── Chapter 5: Risk Disclosure (pages 79-120)
87
+ ```
88
+
89
+ 每个节点都需要存储:标题文本、所在层级、在文档中的起止位置、以及内容规模(以 token 数或字符数计)。在工程实现上,建议同时保留一个稳定的节点 ID(例如从根到当前节点的编号路径),便于后续的引用追踪、缓存命中以及跨规则的复用。父节点和子节点之间通过显式的指针或 ID 关联,这样无论是自顶向下遍历还是自底向上追溯祖先,代价都是常数级的。
90
+
91
+ ### 步骤 4:使用树
92
+
93
+ 假设有一条规则要求"检查风险披露章节":
94
+
95
+ 1. **在树中检索**目标节点。把规则中描述的作用域与节点标题做匹配。
96
+ - 精确匹配:"Chapter 5" → 找到标题为 "Chapter 5" 的节点。这种命中是最理想的情况,可以直接落点,不留歧义。
97
+ - 语义匹配:"风险披露章节" → 查找其标题或内容与"风险披露"相关的节点。这一步可能需要模糊匹配,或者用 LLM 做分类判断。在大型文档中,语义匹配应当先在标题层面尝试命中,只有标题不足以判断时,才下沉到摘要或正文。
98
+ 2. **抽取该节点的内容**(必要时也包括其子节点的内容)。抽取时同时记录这次抽取的来源节点 ID,这样验证结论就能反向追溯到文档中的具体位置,而不是悬空于"模型说"。
99
+ 3. **检查规模。**如果内容能够塞进工作 LLM 的上下文窗口,就直接使用。如果塞不下,则向下进入子节点,定位到真正需要的那个小节。在下沉过程中,记得保留祖先节点的标题链,使得 LLM 始终知道它正在阅读的是文档中的哪个位置。
100
+
101
+ ## "全文 → 章 → 实体" 流水线
102
+
103
+ 这是从文档中抽取待验证实体的标准漏斗式收窄过程,也是 KC 推荐的默认验证编排方式。每一步都把范围进一步收紧,把验证任务交给一个能力恰好匹配、上下文恰好够用的环节去完成:
104
+
105
+ 1. **全文上下文**:借助树来理解整份文档的结构。知道每样东西分别在哪里。这一步不需要 LLM 真的去读全文,只需要让规则与树之间建立索引关系。
106
+ 2. **章节**:导航到这条规则所针对的具体章节。抽取其内容。注意章节边界要严格按照树上的起止位置来,不要凭印象多取或少取,否则验证准确率会被边界噪声拖累。
107
+ 3. **实体**:在章节内容内,使用 `entity-extraction` 中的方法,把具体的实体(数字、文本片段、条款)抽取出来。这是最贴近规则原子比对单元的一步。
108
+
109
+ 对于上下文窗口为 16K–32K 的工作 LLM:
110
+ - 章节内容加上抽取提示词必须能够装进上下文窗口。把这两部分的预估 Token 数加起来,留出至少 10% 余量给输出。
111
+ - 如果某一章过大,就继续向下进入树的更深层。优先选择能完整覆盖规则作用域的最小子节点。
112
+ - 始终把父级标题链一并附带上,作为定位上下文:例如 "Part II > Chapter 3 > Section 3.1",这样 LLM 才知道这段内容在整份文档中处于什么位置。缺少这条标题链,LLM 容易把同名的小节弄混,尤其是在带有"通则—分则"结构的法规中。
113
+
114
+ ## 缓存与复用
115
+
116
+ 每份文档只需构建一次树,然后在所有规则上复用:
117
+ - 把树结构以 JSON 形式与解析后的文档一同保存下来。文件名可以采用 `<doc_id>_tree.json` 这样的稳定模式,便于后续按文档 ID 直接读取。
118
+ - 同一份文档常常会被多条规则命中不同的章节。树让每条规则都能直接跳转到自己关心的位置,而无需重新解析文档。这一点在批量验证场景下尤其重要,它把"O(规则数 × 文档解析)"的代价降到了"O(文档解析)+O(规则数 × 树检索)"。
119
+ - 当文档版本发生变化时,把新旧两版的树做对比,可以快速看出新增、删除、合并、重排的章节,从而决定哪些旧的验证结论需要重跑、哪些可以延续。
120
+
121
+ ## 边界情况
122
+
123
+ - **扁平文档**:有些文档完全没有结构化的层级。把整份文档当作一个节点处理。如果其规模超出上下文窗口,则改用 LLM 引导式分块。在这种情形下,要特别注意保留一份原文的连续性索引,以便后续把抽取结果回贴到正确的字符偏移上。
124
+ - **嵌套很深的结构**:某些法律文档有 6 层及以上的嵌套层级。构建时把所有层级都建起来,但对任何一条具体规则,通常只需向下导航 2 到 3 层即可。过度下钻反而会让规则失去其应有的上下文,使得 LLM 看不到关键的限定性表述。
125
+ - **跨章节的交叉引用**:某节可能写有"如第 1.2 节所定义"这样的字样。在抽取时,你可能需要同时从树上多个节点取内容。把它们拼成一个统一的上下文,再交给 LLM。在记录验证依据时,要分别标注每段来源节点,避免把"第 5 章的结论"与"第 1.2 节的定义"混为一谈。
126
+ - **附录与附件**:附录和附件中往往承载着关键的表格和数据。要把它们作为顶层节点纳入树中,不要遗漏。许多披露类规则的"数字真相"恰恰躲在附录的表格里,正文反而只是导引性的描述。
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: version-control
3
+ tier: meta
3
4
  description: Manage versioning of skills, workflows, prompts, and system configuration throughout the lifecycle. Use when skills are modified, workflows are regenerated, prompts are updated, or any artifact needs rollback capability. Covers what to version, how to version with file-system conventions, maintaining a version manifest, and rollback procedures. Also use when comparing performance between versions or when production results need to trace back to the exact workflow version that produced them.
4
5
  ---
5
6
 
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: work-decomposition
3
+ tier: meta-meta
3
4
  description: 在 rule_extraction → skill_authoring 过渡阶段决定如何把规则集拆分为 TaskBoard 任务。涵盖排序方法(难度优先 / Shannon–Huffman、广度优先、深度优先、二分切分)、分组策略(多条规则合并到一个任务 vs. 各自独立的判断标准)、三轴难度评估、以及如何写一份贯穿全流程都能用得上的 PATTERNS.md 项目记忆。当进入 rule_extraction、进入 skill_authoring 或感觉 TaskBoard 走偏想要重新拆分时使用。
4
5
  ---
5
6
 
@@ -7,13 +8,21 @@ description: 在 rule_extraction → skill_authoring 过渡阶段决定如何把
7
8
 
8
9
  KC 的 main agent 是指挥者。指挥者决定下一步做什么——而这个决定凌驾于后续所有选择之上。错误的拆分会让整个会话变得昂贵:规则顺序错了,agent 会把同一种结构重新设计三遍;不相关的规则被合并到一个 skill 里,最终 check.py 就会变成 E2E #4 那种"统一执行器"反模式;本应合并的相关规则被分散到不同 skill,agent 会把同样的 chunker 逻辑重新推导 17 次。
9
10
 
10
- 这份 skill 是指挥者做这类决定的操作手册。它放在 `meta-meta/` 下,因为工作拆分是系统级的纪律,不是某条规则的具体技巧。互补的 `task-decomposition`(同样在 `meta-meta/` 下)覆盖单条规则**内部**的结构——locate → extract → normalize → judge → comment。本 skill 覆盖的是规则**集合**该如何切分成 TaskBoard 任务。
11
+ 这份 skill 是指挥者做这类决定的操作手册。它的层级标记是 `tier: meta-meta`,因为工作拆分是系统级的纪律,不是某条规则的具体技巧。互补的 `task-decomposition`(同样 `tier: meta-meta`)覆盖单条规则**内部**的结构——locate → extract → normalize → judge → comment。本 skill 覆盖的是规则**集合**该如何切分成 TaskBoard 任务。
11
12
 
12
13
  ## 何时使用本 skill
13
14
 
14
15
  - **进入 rule_extraction 时**。读完法规、拆出规则之后,在宣布该阶段完成之前,先决定这些规则会以什么顺序被处理、是否分组。覆盖审计与 chunk refs 都是这两个决定下游的工作。
15
16
  - **进入 skill_authoring 时**。TaskBoard 是空的(引擎不再自动生成 per-rule 任务)。从 `describeState` 读取规则列表,决定分组与顺序,然后为每个工作单元调用 `TaskCreate`。
16
17
  - **运行中觉得拆分不对时**。如果 TaskBoard 越走越奇怪(规则按错误顺序累积、明明该合并的两条规则被拆到两个任务里),停下来重新拆分。暂停 5 分钟重新规划的代价,会在接下来 2 条规则里被更合理的形状收回。
18
+ - **任意阶段同时跑 3+ 个并行子目标时**。如果你发现自己在工作记忆里同时拎着多个并行子目标(3+ 条规则 × 文档、finalization 阶段的多份交付物、production_qc 的多个 QC 批次),把它们丢进 TaskBoard 串行处理。v0.7.5 审计显示 distillation 和 production_qc 阶段即便注册表里没显式暴露这个 skill 也能从显式任务化中获益 —— v0.8 P2-E 把它对所有阶段都开放。
19
+
20
+ ## 简明判断:什么时候用 TaskBoard
21
+
22
+ - 同时处理 N+ 条规则或 N+ 份文档?→ 开工之前先 `TaskCreate` 每个为一个任务。
23
+ - 一步就能干完的小请求?→ 跳过,直接做。
24
+ - 子代理内部协调?→ 跳过,子代理不暴露 TaskBoard。
25
+ - 任何你心里要靠"待会儿再回来"才能撑住的事?→ 现在就 TaskCreate 出来。长回合下工作记忆会漏掉半成品。
17
26
 
18
27
  ## 锁定原则
19
28
 
@@ -339,13 +348,23 @@ PATTERNS.md 全文控制在约 5 KB 之内。超过时,剪掉最不可执行
339
348
 
340
349
  ### 调用 TaskCreate / TaskUpdate / TaskComplete
341
350
 
342
- 引擎注册了三个任务面板工具(v0.7.3+):
351
+ 引擎注册了三个任务面板工具(v0.7.4):
343
352
 
344
- - `TaskCreate({id, title, phase, ruleId?})` —— 在 `tasks.json` 中新增一条任务。`id` 在本会话内必须唯一;per-rule 任务建议用 `<rule_id>-<phase>` 这种稳定形状,分组 / 非规则任务用 `<group-name>-<phase>`。`phase` 是该任务所属的阶段(当前阶段或你预先排好的未来阶段)。`ruleId` 可选 —— 设上之后,引擎在里程碑推导时能把这个 rule_id 计入覆盖。
353
+ - `TaskCreate({id, title, phase, ruleId?})` —— 在 `tasks.json` 中新增一条任务。`id` 在本会话内必须唯一;per-rule 任务建议用 `<rule_id>-<phase>` 这种稳定形状,分组 / 非规则任务用 `<group-name>-<phase>`。`phase` 是该任务所属的当前阶段。`ruleId` 可选 —— 设上之后引擎在里程碑推导时能把这个 rule_id 计入覆盖。
345
354
  - `TaskUpdate({id, status?, summary?})` —— 把任务状态改为 `pending` / `in_progress` / `completed` / `failed`,可选附一行简要 summary。
346
355
  - `TaskComplete({id, summary?})` —— `TaskUpdate({id, status:"completed", summary})` 的语法糖。完成一个工作单元后走这条最常用的路径。
347
356
 
348
- 调用 `TaskCreate` 把你的拆分写进面板、本回合结束之后,Ralph 循环会取下一条 pending 任务执行。完成工作、调 `TaskComplete`,循环再前进。如果一条任务无法完成(不可恢复的错误),调 `TaskUpdate({id, status:"failed", summary:"原因"})`,让队列继续推进而不是被堵在那里。
357
+ ### Ralph 循环范围 —— 仅限当前阶段
358
+
359
+ 重要契约(v0.7.4 在团队反馈后调整):
360
+
361
+ - **循环范围 = 仅当前阶段**。TaskCreate 只能为当前阶段建任务,Ralph 循环在阶段内逐条处理。
362
+ - **阶段边界 = 循环退出**。当前阶段任务全部完成、或阶段推进(你调 `phase_advance`、或任何其他地方改了 `currentPhase`)时,循环干净退出,控制权回到用户。
363
+ - **引擎不再自动推进阶段**。即使所有任务完成 + 退出条件满足,引擎也不会自动跳到下一阶段。阶段推进是你**显式调** `phase_advance`,或用户重新 prompt 的事。
364
+ - **不要为未来阶段预先建任务**。它们会被忽略 —— 循环在阶段边界先退出,根本不处理它们。只为你**当前所在**的阶段建任务。
365
+ - **阶段边界 = 用户检查点**。这是有意为之。团队需要在自然断点上看进度。完成你这一批任务、调 `phase_advance` 之后,循环退出,你在最后一条消息里向用户汇报进度,用户再 prompt 你开始下一阶段。
366
+
367
+ "从 bootstrap 一路跑到 finalization 不停"这种端到端无人值守,**不是引擎该做的事** —— 这个能力以后会以外部 driver 的形式实现(`/loop` 风格命令),由它跨阶段地反复调用 agent。在一次调用内,你就把当前阶段做完、推进、回到用户。
349
368
 
350
369
  示例:
351
370
 
@@ -384,3 +403,29 @@ E2E 历史:
384
403
  - E2E #7 v071 DS 和 GLM 都没写 PATTERNS.md,但 GLM 写了 6 篇 phase 完成日志和一份内容详尽的 AGENT.md —— 方法论 *捕获了*,只是放在了不同文件里。v0.7.2 把更宽的原则写进 skill:推进之前先持久化,格式灵活。
385
404
 
386
405
  引擎从文件系统推导里程碑(v0.7.0 Group A)会按磁盘事实核验覆盖率,无论你怎么切分工作。TaskBoard 是你的草稿;磁盘才是契约;持久化文件是项目的记忆。
406
+
407
+ ## 子代理批处理:滚动窗口写入(rolling-window)
408
+
409
+ 当你派发 N 个子代理做批量工作(回归测试、批量核查、并行规则处理)时,**不要**让它们写同一个协调文件。v0.7.5 审计发现:子代理在 `tasks.json` / `rules/catalog.json` / `output/results/summary.json` 上互相抢锁 —— 一个占着工作区锁 5 分钟以上,其他在静默等待。
410
+
411
+ 正确的模式:每个子代理写到**自己**专属的、有已知前缀的文件。父代理在所有子代理完成后再做聚合。
412
+
413
+ ```
414
+ sub_agents/
415
+ batch-001-regression/
416
+ output/results/v2_regression.json # ❌ 多个子代理共用 — 抢锁
417
+ batch-002-regression/
418
+ output/results/v2_regression.json # ❌ 同一路径,竞争
419
+
420
+ # 改为:
421
+
422
+ output/
423
+ batch_regression_001.json # ✓ 每个子代理一个文件
424
+ batch_regression_002.json # ✓
425
+ batch_regression_003.json # ✓
426
+ # 父代理读所有 batch_regression_*.json,写汇总。
427
+ ```
428
+
429
+ 引擎信号:如果你在 events.jsonl 里看到 `lock_blocked` 事件出现在子代理工作期间,那就是症状。v0.8 P4-C 加了这个事件发射,让父代理在子代理超时之前就看见冲突。出现就立刻改成滚动窗口写。
430
+
431
+ 不要写"用文件锁协调"的子代理批处理。锁原语是用来防止意外并发写入的安全机制,不是队列。用文件系统布局作为协调机制。
@@ -0,0 +1,168 @@
1
+ """KC worker-LLM client (v0.8.1 P10-A canonical shim).
2
+
3
+ Distilled workflows use this module to call worker LLMs. Provider-agnostic:
4
+ reads connection info from workspace `.env` so the same workflow can run
5
+ against SiliconFlow, OpenAI, Anthropic, Aliyun, Volcanocloud, etc.
6
+
7
+ Two modes:
8
+ - Inside a KC session: the engine's `worker_llm_call` tool is preferred
9
+ for new code (it tracks cost, applies rate limiting, and writes to
10
+ events.jsonl). This shim is fine if the workflow needs to be
11
+ portable to standalone (no-KC) deployment.
12
+ - Standalone (deployed release bundle): this shim is the only LLM
13
+ access path. Every call writes a line to `output/llm_ledger.jsonl`
14
+ so post-hoc analysis can reconstruct cost and traffic.
15
+
16
+ Required `.env` fields:
17
+ LLM_API_KEY API key for the provider
18
+ LLM_BASE_URL Provider base URL (e.g. https://api.siliconflow.cn/v1)
19
+ TIER1..TIER4 Comma-separated model names per tier
20
+
21
+ Optional:
22
+ LLM_AUTH_TYPE "bearer" (default) | "x-api-key" (Anthropic native)
23
+ LLM_API_FORMAT "openai" (default) — only OpenAI-format chat completions
24
+ are supported by this shim. Use worker_llm_call for
25
+ non-OpenAI-format providers (e.g. Anthropic native).
26
+
27
+ If LLM_BASE_URL is missing, the shim raises explicitly — no silent
28
+ fallback to a hardcoded vendor URL. This avoids accidentally sending
29
+ traffic to siliconflow.cn from an OpenAI-configured workspace.
30
+
31
+ Migration aliases:
32
+ SILICONFLOW_API_KEY → falls back to LLM_API_KEY if the canonical
33
+ name is missing (for workspaces predating v0.8.1).
34
+ """
35
+ import json
36
+ import os
37
+ import time
38
+ import urllib.error
39
+ import urllib.request
40
+
41
+ _LEDGER_PATH = os.path.join("output", "llm_ledger.jsonl")
42
+
43
+
44
+ def call(tier="tier2", prompt="", system_prompt=None, max_tokens=4096, timeout_s=120):
45
+ """Single-prompt chat-completions call.
46
+
47
+ Returns: {response, model_used, tier, tokens_in, tokens_out}.
48
+ Raises: RuntimeError on missing config; urllib HTTPError on transport.
49
+ """
50
+ if not prompt:
51
+ raise RuntimeError("call() requires a non-empty `prompt`")
52
+
53
+ api_key = _env("LLM_API_KEY") or _env("SILICONFLOW_API_KEY")
54
+ if not api_key:
55
+ raise RuntimeError(
56
+ "LLM_API_KEY not configured. Set it in .env or run `kc-beta onboard`."
57
+ )
58
+
59
+ base_url = _env("LLM_BASE_URL") or _env("SILICONFLOW_BASE_URL")
60
+ if not base_url:
61
+ raise RuntimeError(
62
+ "LLM_BASE_URL not configured. Set the canonical name in .env "
63
+ "(e.g. https://api.openai.com/v1 for OpenAI; "
64
+ "https://api.siliconflow.cn/v1 for SiliconFlow). "
65
+ "Run `kc-beta onboard` to configure interactively."
66
+ )
67
+ base_url = base_url.rstrip("/")
68
+
69
+ auth_type = (_env("LLM_AUTH_TYPE") or "bearer").lower()
70
+ api_format = (_env("LLM_API_FORMAT") or "openai").lower()
71
+ if api_format != "openai":
72
+ raise RuntimeError(
73
+ f"LLM_API_FORMAT={api_format} not supported by this shim. "
74
+ f"Only `openai` chat-completions wire format is implemented. "
75
+ f"Use the engine's `worker_llm_call` tool for native non-OpenAI providers."
76
+ )
77
+
78
+ tier_models = _load_tier_models(tier)
79
+ if not tier_models:
80
+ raise RuntimeError(f"No models configured for {tier.upper()}; check .env TIER1-TIER4")
81
+
82
+ messages = []
83
+ if system_prompt:
84
+ messages.append({"role": "system", "content": system_prompt})
85
+ messages.append({"role": "user", "content": prompt})
86
+
87
+ body = json.dumps(
88
+ {"model": tier_models[0], "messages": messages, "max_tokens": max_tokens}
89
+ ).encode("utf-8")
90
+
91
+ headers = {"Content-Type": "application/json"}
92
+ if auth_type == "x-api-key":
93
+ headers["x-api-key"] = api_key
94
+ headers["anthropic-version"] = "2023-06-01"
95
+ else:
96
+ headers["Authorization"] = f"Bearer {api_key}"
97
+
98
+ req = urllib.request.Request(f"{base_url}/chat/completions", data=body, headers=headers)
99
+ t0 = time.time()
100
+ try:
101
+ with urllib.request.urlopen(req, timeout=timeout_s) as resp:
102
+ data = json.loads(resp.read())
103
+ except urllib.error.HTTPError as e:
104
+ # Preserve the body for debugging — providers often return useful errors
105
+ err_body = e.read().decode("utf-8", errors="replace")[:500] if e.fp else ""
106
+ raise RuntimeError(f"LLM call HTTP {e.code} from {base_url}: {err_body}") from e
107
+
108
+ usage = data.get("usage") or {}
109
+ result = {
110
+ "response": data["choices"][0]["message"]["content"],
111
+ "model_used": tier_models[0],
112
+ "tier": tier,
113
+ "tokens_in": usage.get("prompt_tokens", 0),
114
+ "tokens_out": usage.get("completion_tokens", 0),
115
+ }
116
+ _write_ledger({
117
+ **result,
118
+ "duration_s": round(time.time() - t0, 3),
119
+ "ts": time.time(),
120
+ "base_url": base_url,
121
+ "auth_type": auth_type,
122
+ })
123
+ return result
124
+
125
+
126
+ def _env(key):
127
+ """Read `key` from process env first, then workspace .env file."""
128
+ v = os.environ.get(key)
129
+ if v:
130
+ return v
131
+ if os.path.exists(".env"):
132
+ try:
133
+ with open(".env", "r", encoding="utf-8") as f:
134
+ for raw in f:
135
+ line = raw.strip()
136
+ if not line or line.startswith("#"):
137
+ continue
138
+ if "=" not in line:
139
+ continue
140
+ k, val = line.split("=", 1)
141
+ if k.strip() == key:
142
+ val = val.strip()
143
+ if (val.startswith('"') and val.endswith('"')) or (
144
+ val.startswith("'") and val.endswith("'")
145
+ ):
146
+ val = val[1:-1]
147
+ return val
148
+ except OSError:
149
+ return None
150
+ return None
151
+
152
+
153
+ def _load_tier_models(tier):
154
+ raw = _env(tier.upper()) or ""
155
+ return [m.strip() for m in raw.split(",") if m.strip()]
156
+
157
+
158
+ def _write_ledger(record):
159
+ try:
160
+ os.makedirs(os.path.dirname(_LEDGER_PATH), exist_ok=True)
161
+ with open(_LEDGER_PATH, "a", encoding="utf-8") as f:
162
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
163
+ except OSError:
164
+ # Ledger is best-effort; never break the workflow over a write failure.
165
+ pass
166
+
167
+
168
+ __all__ = ["call"]