手改是**兜 AI 的错**的那一手 —— 这一轮把它从「摆在那儿但落不了地」做成真的: 件上就地改 · 保存**按地址收**,不看当下那张卡:载荷从前借道工单的 `blocks[i]` 取列名, 于是熔断卡(没有 blocks)在屏上时,人补的那一格一个字都送不出去 —— 而他要补的恰恰是把这一单卡住的那个必填格。授权面改由活动清单的台账声明把关。 · **能增删行**:错不止「这一格填错了」,还有「这一行不该在」与「漏了一行」。 删挂记号不当场抹掉(看得见、可后悔),且**删要说出来**(`drops`)—— 「没回传就是删了」会把一次不完整的回传变成一次删表。 · **必填没填按不下提交**:与放行门 L1 同一个数同一套算法;闸上摆出差在哪一行哪一格。 · 只写真改了的那几行 —— 改一格却留「修订 4 行」的痕,那句话是假的。 判据与门 · 裁量判据带谓词时**谓词说了算**:「选填缺口已补齐」基座数得出来, 人填好的目标价它照样判「客户未提供」的毛病由此消掉。 · 验收卡判据**读时现算** —— 人在成果件上补一格,判据跟着变。 · 放行门认「**办完了没有**」不认「有人写过」:从前写个 `false` 就把门推开, 于是一单没打标就进了放行门。三个数全由基座现数。 · 拒回把「活没干完」说成「数或行对不上」的毛病:判据点名的表一行都没写时直说那一步没办。 **指向两扇打不开的门,比不指还糟。** 界面不撒谎 · 日志只有三色:负责人绿 · 智能体蓝 · 系统灰(归侧五种是度量的分法,不是给人看的)。 · 验收收口答两件事:落了什么(按**交付物**点名,一份成果的几张表并成一行)、 往哪儿去(转出哪一口、带过去哪些字段);一个口都没发就直说到此为止。 · 签完到收尾之间那一段有自己的话「字已签下,正在落地」—— 中间态本来就该说出来,只是要说对。 · 消息不再被内部事件刷屏:只有从通道进来的件才谈得上「无人认领」。 · 建客户公司与联系人**一次确认落两行**;联系人编码自增不问人;卡面摆表的人读名。 A1 三案例端到端全 released:全 A 类直通 12 拍 / 客户点名跳一条 10 拍 / 新客户缺必填 14 拍。 五道门禁全绿(971 passed)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsTDjoHovERXyy6Wmw1ALp
47 KiB
活动开发规范
2026-07-30 第二版(第一版是「现状如实、差距列账」的勘误体;本版按跑通后的现状重写成 开发手册)。目标读者:开发活动的 AI 或人 —— 读完这一份,你应当能独立把
docs/MVP示例工作流.html里的一个 SOP 节点做成一个能released的案例活动, 不必读apps/api的源码。范本:
activities/quote-intake(A1 客户RFQ受理,2026-07-30 已两条路径released、 常路 7-8 拍收口)。本文与代码冲突时以代码为准,发现即改本文。 相关:docs/编码规范.md§8(平台契约)、各模块CONTRACT.md(模块级不变量)、docs/debt.md(欠账)。历史语义源(操作系统 / 活动模型两卷)已归档docs/archive/。
1. 世界观:一个活动是怎么跑起来的
活动 = 任务的定义。 编排引用一个活动,运行时长出一个任务(恰一名负责人)。
活动在系统外开发、经安装通道入库(pnpm activities:install),版本恒新增、
同版重装 no-op、在途任务按启动时版本跑完。
活动没有代码逻辑。 2026-07-29 范式退役后,活动一律案例活动:行为全部写在
清单里(案例模型 CMMN + 说明书 + 判定表 DMN + 技能 + 台账声明 + 报告模版),
「系统不执行代码即知包为何物」。src/index.ts 只是被静态审扫描的产物文本
(证明零环境能力),defineActivity({ name }) 即可,run 可选且通常不写。
推进由 Loop 驱动,一拍的解剖(loop-engine/internal/usecase/turn-runner.ts):
语义引擎发布局面 ──> Agent 决断一串行动 ──> 引擎逐个执行 ──> 留痕 ──> 下一拍
· 当下可为(Enabled) (1-6 个,一次模型调用) 被拒停在谁那儿
· 进行中(Active) tool 后截断
· 目标缺口(未完成项) 挂起型收拍
· 最近轨迹 + 工具清单 + 台账字典 + 任务数据
拍是按模型调用计费的 —— 每拍把说明书 + 轨迹 + 局面整段重读一遍。 所以一切设计都围着「少一拍是一拍」:一拍连做(写两张台账顺手 finish)、 批量工具入参(一单所有 PN 一次传)、说明书把规则写死(判断不花拍)。 A1 的账:六步的活,常路 7-8 拍收口(起初 21 拍 —— 差距全在设计,不在模型)。
2. 四条铁律(违者拒装或被引擎拒)
| # | 铁律 | 机制承载 |
|---|---|---|
| ① | 只读世界:活动只能读台账、调读类工具、问模型、发卡问人 | 产物文本静态审:process.env / fetch / node:* / eval 命中即拒装;depcruise 双向隔离 |
| ② | 写只有草稿与意向:写台账 = 草稿层(不抛数据事件);外部动作(发邮件等)= 登记动作意向(入参冻结 + 幂等键)。登记那一刻世界上什么都没变 | write 行动只落草稿;写类工具调用自动转意向 |
| ③ | 终局唯放行:任务完成的唯一出口是人类验收通过 —— 那一刻才草稿合并、意向执行、输出口发射 | submit = 发验收卡挂起;放行落地六步(§11.4) |
| ④ | 活动不自转:推进由引擎逐拍驱动;预算(步数/token/时长/调人次数)与停滞熔断由 Harness 环外强制,活动不得自查自守 | 迁移拓扑把 Completed/Terminated 设为死胡同;AI 不得产生终局迁移 |
3. 开发流程(从 SOP 节点到 released)
- 读业务:
docs/MVP示例工作流.html找到你的节点 —— 任务定义卡(执行人/触发/目标)、 作业指导书(S1…Sn + 判断菱形)、判定表、留痕 · SLA。 - 列台账:这个节点的成品是哪几张表?每张的主键与列(§6.3)。留痕清单就是答案。
- 列判定表:图上的菱形与「规则 · 约束」区的表,一张不落进 DMN(§8)。
- 建案例模型:图上有几步就是几项(§7),案卷项 id = 任务视图键名。
- 写说明书:每步一段,写死规则、写明落点计划项与要写的台账列(§7.2)。
- 声明工具:需要什么就声明什么名字;系统没有的运行时会转人(§9)。
- 配报告模版:一个局面一张(§10.3)。
- 写清单其余区:契约七件对齐台账列(§6.2 的三条对齐线),样例 ≥1 条。
- 自测:
pnpm vitest run activities/<id>(四段,§12.1)。 - 构建安装:升
packageVersion→pnpm activities:install(同版重装 no-op!)。 - 试跑:登记触发起单(试跑一律用 AI 账号)→ 看任务轨迹逐拍对账 → 验收签字 → 查落地行与出口载荷。卡壳对照 §15 排障表。
- 调优:轨迹里每一条「行动被拒」都是设计欠账 —— 修模型或说明书,不怪模型。
4. 包结构与构建安装
activities/<activity-id>/
├── package.json # @activities/<id>,private,type:module
├── src/
│ ├── index.ts # export default defineActivity({ name });行为不在这
│ ├── manifest.ts # 清单六区 + 导出 DMN 常量(测试直接求值)
│ ├── case-model.ts # 导出 CMMN_XML 与 NARRATIVE(双表示同版本)
│ ├── skills.ts # 导出 Skill[](id 用 skillIdSchema.parse)
│ └── views/report.ts # 报告模版(viewShell/kvCard/tableCard/... 拼装)
├── __tests__/activity.test.ts # 自测四段;只准 import @sdk / @contracts / @sfeel
└── dist/{manifest.json,index.js} # pnpm activities:build 产出(index.js 现为源码文本,债 M5-3)
- 构建:
pnpm activities:build(新包要在tools/build-activities.ts的 PACKAGES 里登记)。 - 安装:
pnpm activities:install—— 只装活动包,不碰治理台配置。 别用db:seed装活动:它重写整套治理台配置。真活动包要在apps/api/src/app/seed-activities.ts的REAL_PACKAGES登记(经安装通道读文件,不经 import)。 - 版本纪律:改了任何内容必须升
identity.packageVersion—— 同版重装静默丢失(踩过)。 卸载只置状态不删行;资产播下一版不覆盖。 - 流程包节点用
activityId引用活动;同名旧槽位要退役卸载(库里两个同名活动会骗人)。
5. 隔离与守卫
活动包只准 import @sdk / @contracts / @sfeel(depcruise 双向隔离:activities 不进
apps,apps 也不 import activities —— 之间只有安装通道)。产物文本静态审黑名单:
process.env、require(、node:fs/net/child_process、fetch(、XMLHttpRequest、
eval(、new Function(。视图模版含 <script 或外链即拒(容器是 sandbox="" iframe)。
6. 清单六区(packages/contracts/src/manifest.ts)
6.1 identity
identity: {
activityId: activityIdSchema.parse('quote-intake'),
name: '客户RFQ受理', // 人读名
packageVersion: '2.4.0', // 改内容必升
category: '统一报价', // 活动库唯一的一维分类
sdkVersion: '0.1.0-m5', // 与 SDK 精确相等,否则拒装
checksum: 'dev-checksum-…', // 非空即可(开发期)
}
6.2 契约七件(contract7.ts)
| 件 | 写什么 | 硬规矩 |
|---|---|---|
| ① input | data(JSON Schema)+ messageAllowed / messageHint |
起单宽进:缺 required 不拒单,由基座发补全卡(§11.1);properties[k].title 写中文 —— 补全卡上给人看的就是它 |
| ② output | mandatory[] + selective[] |
mandatory 每次放行必发;selective 每组 ≥2 口、恰发一口,人在验收单上选,选中口名注入 outcome 供下游网关 |
| ③ requiredDeliverables | {key, label, ledger}[] |
ledger 必写(域.对象 台账地址):预检数本单草稿在该地址的行数,缺一不得提交放行 —— 不给地址就只能靠活动自报,自报等于没有门 |
| ④ acceptanceCriteria | {key, label, level: L1-L5, predicate?}[] |
L1/L2 必带 predicate(S-FEEL),提交时对任务视图当场机检;L3-L5 交人。能算的判据一律降到 L1/L2 —— 交给人的判据,人忙时会盲签(踩过) |
| ⑤ revisableAtRelease | {key, label, ledger}[] |
放行时人可当场改的值(如两枚去向旗);AI 拟值由基座放行前从台账现读,不让模型重述;改完落回声明的地址 |
| ⑥ prohibitions / authorization | 禁区短而硬;humanCallBudget = 调人预算 |
preauthorizedActions 是死代码,不要写 |
| ⑦ resources | tools / skills / models / roles / budget{steps,tokens,activeMinutes,humanCalls} |
budget 按实测给,留一倍余量 —— 给太紧等于让 Harness 杀正在干的活(A1:steps 60 / tokens 400k / 60min / 调人 4;实测常路 ~8 拍 5-8 万 token) |
三条对齐线(错位不报错,只在放行那一刻「取不到值」,查起来一层皮 —— 自测四段第 ④ 段钉死):
requiredDeliverables[].ledger的地址必须在declarations.ledger里声明过;- L1/L2 谓词引用的标识符必须是声明过的台账列(且用汇总列,见下);
- 出口载荷的标量字段必须有台账列承载 —— 载荷从落地事实推导:数组字段 ← 落地行整体,标量 ← 摊平事实(驼峰/下划线两认)。声明了没列承载的标量, 下游网关取不到值全流死路(踩过:两枚去向旗没有列,G1 全断)。
汇总列纪律:任务视图把草稿行摊平到顶层是「后写的赢」——拿逐行列(如
missing_key_fields)做谓词只会验到最后写的那一行,那不叫机检叫抽样。
所以验收谓词读一单一行的汇总对象(A1 的 quote_intake.verdict:line_total /
missing_key_total / untyped_total / type_a_total / alt_request_total…),
汇总列与逐行列故意不同名,免得互相盖掉。
派单对账模式(A1 实例,值得照抄):「C 类几条就该开几张替代评估请求」这类
一致性,把两边的条数都落成汇总列,谓词写 alt_request_total = type_c_total ——
机检,不交人。
6.3 declarations
declarations: {
models: ['deepseek-v4-flash'], // 自定义模型名;非空才有 LLM 五件上总线
tools: [{ name: 'crm.history-price-read' }, { name: 'quote-intake.classify' }],
ledger: [ { domain, object, label, fields: [{name, type, label, primaryKey, masked, required?}] }, … ],
listeners: [ { origin:'static', eventType, keyFilter, semantic:'observe'|'complete-step',
deadline:'P1D', escalation:['nudge','escalate'] } ], // 时限必配,悬空拒装
harness: { memoryWindow: 20, stallAfter: 'PT45M',
nudge: [{ after:'PT15M', action:'nudge' }], retry: { times:2, backoff:'PT30S' } },
}
- 台账声明就是授权面:运行时只准写这里声明的地址与列名,编出来的列当场拒。 它同时是喂给 Agent 的字典(不给字典,它只会照想象编列名)。
required: true= 这一列缺了就是活没干完(可选,不建 NOT NULL —— 草稿层的本分 就是过程中允许缺)。标了它,基座就数得出域_对象_missing(缺必填几行)与_missing_keys(缺在哪几行、缺哪一列)——机检判据该读这个,不读活动自报的汇总列。 栽过两次:自报的数要模型自己重算,漏算一次那道门就再也开不了(第二次是连跑十轮里三轮)。- 标识符小写字母开头、字母数字下划线。域归属:活动自持域用活动名
(
quote_intake);跨活动事实只有两条路 —— 输出口载荷,或共享域(shared/intel等)。 - 物化三律:最大化合并、最宽类型(integer→number→text 加宽链)、只增不破坏 (新增主键 = 冲突拒装)。
- 监听语义只有
observe(记观察交 Agent 裁量)与complete-step;「客户改需求要回炉」 这类业务反应写observe,回炉是 Agent 看到观察后自己的决定。 contract.resources.tools与declarations.tools是双份声明,运行时只读 declarations —— 两处写一致。
6.4 assets
assets: {
businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: INTAKE_DMN }, // 双表示同版本,空任一拒装
skills: INTAKE_SKILLS,
viewTemplates: [ { name:'intake-overview', html: … }, … ], // 一个局面一张(§10.3)
cardSchemas: {},
distillHooks: [ { on:'terminal', prompt:'归纳本单…经验' } ], // 封卷后蒸馏进活动记忆
}
6.5 samples · 6.6 entry
样例 ≥1 条(首装无历史时,静态审 + 样例集替代回放审);把常路与最长路径都给上:
samples: [
{ name:'三条料 A/B/C 各一', input:{ customerCode:'C001', rfqRef:'RFQ-…', subject:'…',
body:'LM358DR 12K/年 目标价8.0 交期8周;NE555P 5K/年 交期6周;TL072CDR 3K/年 …' },
expect:{ ports:['intake'] } },
{ name:'缺三要素 → 走追问补全', input:{ …, body:'LM358DR 报个价,数量交期没定' },
expect:{ ports:['intake'] } },
],
entry: { file: 'index.js', checksum: 'dev-entry-…' },
7. 案例模型(CMMN)
7.1 建模纪律 —— 每一条都是实测账
- 图上有几步就是几项,不多建一件。 A1 第一版把 6 步建成 12 项(自加两个阶段、 两个里程碑、一个裁量项、一个事件监听器)—— 业务一分没多,模型全程跟 harness 较劲: 阶段当落点、里程碑收不收、项先 start 还是先干、编不存在的 id……一单烧二三十万 token 还常收不了口。重写成扁平 7 项(S1…S6 + 放行)后零被拒。阶段/里程碑/裁量项是 CMMN 有的东西,「有」不等于「该用」—— 要能换来业务上说得出的好处。
- 案卷项 id 就是任务视图里的键名。 前提的 onPart 认的是「这个键有值了」——
起单入参(
rfqRef)与台账列(line_total/missing_key_total)都会摊平到任务视图 顶层,照它们命名。名字对不上 = 前提永不满足 = 第一个计划项都不 Enabled, 整案一步走不动,而每一层单看都「正常」(踩过)。 manualActivationRule只标触达世界与关键交付的项(A1 标了两项: 受理需求交人审定S3_confirm、受理放行T_release)。标了的项必须 Agent 显式start才能干活;别的项引擎就地过 Active —— 为一次草稿写单烧一拍去「按一下」是纯仪式。 凡触达世界的项一律不给doneWhen:给了就等于给引擎一把替人签字的钥匙。 3.5. 人的门要建成一项,不要挂成某一步的条件分支。 A1 栽过一次(2026-08-01 用户指出):「人确认受理需求」原本不是一项,而是归一那一步 收步条件里的半句 ——missing_minor_total = 0 or answered_total > 0, 意思是只在缺东西的时候才叫人。于是一封字段齐全的询价进来,这半句在拆完那一刻 就为真,引擎自己把那步收了,人一眼没看过受理需求,下一次被叫到已经是终局验收。 用户原话:「我都没确认需求 它就让我验收了」。 判据是「这件事办完了没有」,不是「要不要办」;要不要经过人是模型结构的事, 写进判据就成了「不缺就不必经过人」。做法:单独一项 + 人工激活 + 不给doneWhen, 并给它一个只有人的回复才产生的计数列(A1 是confirmed_total)当下一步的入场前提。repetitionRule标常来回的项(逐条登记、追问补全)——驳回重入靠它给新实例。- 判断菱形 = 两条互斥前提,不是一个计划项。 D1「判完整度」在图上是菱形,
在模型里是
s_incomplete(missing_key_total > 0 → S2 入场)与s_complete(line_total > 0 and missing_key_total = 0 → S3 入场)两个 sentry。 autoComplete="true":必做项全 Completed 且无 Active 项时,引擎不问模型直接提验收。 事件监听器不算必做项、不进缺口(它是等待岗不是成品)。
7.2 A1 的模型骨架(照抄改名即可)
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL">
<case id="intake">
<caseFileModel> <!-- id = 任务视图键名(纪律 2) -->
<caseFileItem id="rfqRef" name="询价单号(开案即有)"/>
<caseFileItem id="line_total" name="登记出的条目数"/>
<caseFileItem id="missing_key_total" name="缺关键字段条数"/>
<caseFileItem id="history_days" name="历史价判据"/>
<caseFileItem id="untyped_total" name="未打标条数"/>
<caseFileItem id="need_sourcing" name="去向旗"/>
</caseFileModel>
<casePlanModel id="plan" name="客户RFQ受理" autoComplete="true">
<sentry id="s_incomplete">
<onPart caseFileItemRef="missing_key_total"><standardEvent>update</standardEvent></onPart>
<ifPart><condition>missing_key_total > 0</condition></ifPart>
</sentry>
<sentry id="s_complete">…line_total > 0 and missing_key_total = 0…</sentry>
<sentry id="s_basis"><onPart caseFileItemRef="history_days">…</onPart></sentry>
<sentry id="s_typed">…untyped_total = 0…</sentry>
<sentry id="s_routed"><onPart caseFileItemRef="need_sourcing">…</onPart></sentry>
<humanTask id="S1_register" name="接收登记:逐条落统一受理记录,结算条数与缺口数">
<itemControl><repetitionRule id="r1"/></itemControl>
</humanTask>
<humanTask id="S2_ask_back" name="追问补全信息:按缺口清单发补全卡">
<entryCriterion sentryRef="s_incomplete"/>
<itemControl><manualActivationRule id="m2"/><repetitionRule id="r2"/></itemControl>
</humanTask>
<humanTask id="S3_normalize" name="…"><entryCriterion sentryRef="s_complete"/>…</humanTask>
<humanTask id="S4_history" name="…"><entryCriterion sentryRef="s_complete"/></humanTask>
<decisionTask id="S5_classify" name="…按三分类规则P-03打标" decisionRef="p03-type">
<entryCriterion sentryRef="s_basis"/>
</decisionTask>
<humanTask id="S6_route" name="定去向与派单…"><entryCriterion sentryRef="s_typed"/></humanTask>
<humanTask id="T_release" name="受理结论验收放行">
<entryCriterion sentryRef="s_routed"/>
<itemControl><manualActivationRule id="mR"/></itemControl> <!-- 触达世界,必标 -->
</humanTask>
</casePlanModel>
</case>
</definitions>
CMMN 子集封顶:计划项 8 型(humanTask / decisionTask / processTask / caseTask / stage /
milestone / timerEventListener / userEventListener),表外构件直接拒装。
ifPart 前缀 semantic: 的条件引擎不机算,标「待裁量评估」交 Agent(judge 行动)。
里程碑前提达成即达成(引擎自己收,不需要谁去按;无前提的里程碑不自达成)。
7.3 说明书(narrative)—— Agent 的作业指导书,最重要的一份资产
与 CMMN 同版本发布、不允许分叉。它是每一拍叙事面的第一段,写给 AI 执行看。
结构照 A1(activities/quote-intake/src/case-model.ts):
§0 开案:什么事、成品是什么、SLA。
【整条路就 N 步】一步一个计划项,别绕:S1 → D1 →(分支)S2 → S3 → …→ 提交放行。
【S1 … · 计划项 S1_register】干什么、写哪张台账哪些列、几条纪律。
【D1 判完整度 —— 判定表说了算,不是你临场判】…
…每步一段…
【提交放行】机检判据点名(line_total > 0、missing_key_total = 0…),先 start T_release 再 submit。
【怎么把一项干完 · 一拍连做】…(见下)
【一路上要说话】…(见下)
§Z 结案。
八条写作纪律(每条都对应一次实测翻车):
- 每步点名落点计划项(「计划项 S4_history」)—— 模型会把活挂错项,点名了才挂得对。
- 写哪张台账、哪些列,写死(
quote_intake.record的八字段、verdict的哪几个汇总数)。 汇总列不写,机检判据无米下锅,整案卡在半路。 - 规则写死,不留给模型判。Harness 已关模型思考换十倍速度(每拍 1.5-5s),代价是 模型不再细想边界 —— 所以边界规则必须写到不可能读歪:「**关键字段只有三个:PN、EAU、 交期。**目标价/厂牌/包装一律次要 —— 次要缺了不拦、不发补全卡」。凡判定表能表达的, 说明书里只写「照 F-01」,并把表的口径复述一遍。
- 一拍连做指引:「写 record、写 verdict、finish S1」是一拍不是三拍;调工具要看出参, tool 放串尾;发卡/提交会挂起,只能是串尾。写明常路拍数预期(「这一单常路 5-7 拍 该收口」)—— 模型会照着省。
- finish 语义:write / tool 不会自动把项收掉(一项常要两三个动作才交得出成果); 都做完了用 finish 宣告,再往下一步走。
- 补全走 ask 卡:「用补全卡(ask 行动),不是执行工单」;人答回的值出现在轨迹里, 看到了要自己 write 进台账并把汇总数改小 —— 系统不会替你写。
- 说话指引:「每一拍都带一句 say,对负责人说人话;局面变了用 view 换画面」+ 每个局面对应哪张模版点名(§10.3)。不写这段,对话区从头到尾空白。
- 禁臆造:「原文没写的不要补,缺就记缺」「拿不准一律降 B,不许臆断 A」—— 受理阶段编出来的数会一路流到报价单上。
7.4 拍数账(设计自查)
估算:每个计划项 ≈ 1 拍(写 + finish 同拍),每次工具调用 +1 拍(要看出参), 每次人环 +1 拍(回传后续跑)。A1:S1(1)+ S3(1)+ 工具 ×2(2)+ 打标写+finish(1)+ S6(1)+ start 放行 + submit(1)≈ 7 拍。估出来超过 10 拍,回头合并计划项。
8. 判定表(DMN)—— 能算的不问 LLM
export const P03_DMN: DmnModel['tables'][number] = {
id: 'p03-type', label: '三分类规则 P-03', hitPolicy: 'FIRST',
inputs: [ { name:'history_days', expression:'history_days' }, … ],
outputs: [ { name:'type_tag' }, { name:'play' } ],
rules: [
{ id:'p03-c', when:['-','-','"EOL","缺货","要求替代"'], then:['C','转案处理'] },
{ id:'p03-a', when:['<= 90','true','-'], then:['A','直接快答'] },
{ id:'p03-b', when:['-','-','-'], then:['B','重估后报'] },
],
};
- 单元格谓词 = S-FEEL:
'-'任意、'"A","B"'析取、'<= 90'比较、'[0..100]'区间。 - FIRST 的行序就是业务优先级:C(EOL)必须排最前 —— EOL 的料哪怕历史价很新也不该判 A。
- 求值:任一输入缺 → incomplete;命中留痕(inputs / hitRules / outputs),同输入必同结论、零 token。
- 纯工具求值器模式(推荐):把判定表做成
pure: true的功能工具 (quote-intake.classify,ownerActivityId私有作用域),模型只负责取齐判据、 调工具拿结论 —— 「不要绕过决策表自己判:规则表可回放可追责,临场判断不是」。
9. 工具
声明式:设计时只考虑需要什么(名字进 declarations.tools),不关心系统里有没有。
工具本体在系统侧注册(治理台 / seed-mvp.ts),契约含面向 AI 的 description 与
面向人的 guide{ask, steps?, standard?}。
运行时五分叉:命中读类直接执行;命中写类登记动作意向(放行才执行);
未命中转人 —— 开一张执行卡(卡面 = guide + 本次入参 + 按出参 Schema 生成的回填表单),
人填回的值就是这次调用的出参(sys.tool_handoff,同调用只发一张卡);
未白名单授权报错(越权不转人);已停用走失败路径。
设计惯例(拍数与正确性都靠它):
- 批量入参:一单几十条料,工具收数组(
pns: string[]/rows: object[]), 一次传完 —— 一条一调,每调一次多烧一拍。 - 出参键名与台账列同名(
type_tag/basis/history_days):模型拿回结果直接 write,不必自己改名 —— 改名这一步交给模型,它会漏。 - 判据类出参把「没有」也当判据回(无历史 →
historyDays: 9999, hit: false), 别回错误 —— 新料号无历史本就该命中 B 类。
系统内化、零声明即得(不进目录不进白名单):write(写台账草稿,授权面是台账声明)、
ask(补全卡)、human(执行/验收工单)、listen(立监听);声明了模型才有 LLM 五件
(extract/classify/summarize/judge/rewrite,提示词归系统,judge 只作参考不代放行)。
10. 人类工具与汇报
10.1 四类(前三类已通;决策卡 UI 在、Agent 侧未接)
| 类 | 形态 | 活动怎么用 |
|---|---|---|
| 执行 | 工单:ask / steps / standard / fields(8 种控件,可带 ai 拟值) | human 行动,workOrder:'execution';卡面填实(title/ask/basis/fields),裸卡拒发 |
| 补全 | 对话卡:ask + fields(就地填)+「没法填」两条路必走其一 | ask 行动 {kind:'ask', itemId, ask, fields:[{key,label,hint?}], basis};fields 空即拒 —— 空表单等于没问 |
| 验收 | 工单:判据表(L1/L2 已机检)+ 落地动作清单 + 可修订值 + 恰选一口 | submit 行动;判据/动作/拟值由基座从契约与台账装配,活动不重述 |
| 决策 | 对话卡:选择题 + 推荐 + 后果 + 自定义口径 | 暂不用(Agent 侧未接) |
调人预算(humanCallBudget)环外扣账;起单补全卡不占活动预算。
10.2 唤醒与挂起
三类同级唤醒:人对话输入、外部事件(数据/时间)、页面交互(答卡/办结工单)。
发卡即挂起(status awaiting),回传即续跑;在途验收先于一切(有未裁决验收卡时
回合原地等,不重发卡不问模型)。人答的值出现在下一拍轨迹里(回填 {...})。
10.3 报告模版:活动持有,一单一模版贯穿始终
- 一个局面一张,局面变了让 Agent 用
view换(A1 四张:overview / gaps / classified / release),不在一张里堆条件分支; - 在办与归档同一张:办结后指导与判据卸下,报告原样进「工作成果」;
- 容器是
sandbox=""iframe:无脚本、无外链、样式自带;数据由基座推送时填 (data-slot取值、data-rows逐行展开、data-class-of按值上色)。
10.3.0 三个区各放什么(2026-08-01 用户定,别再混)
| 区 | 放什么 | 不放什么 |
|---|---|---|
| 工作区 · 待处理 | 只放那份件(活动推的 HTML 模版),整篇铺开、可就地改 | 题头 / 编号带 / 依据带 / 判据表 / 作业指导书 / 提交钮 |
| 工作区 · 工作成果 | 只放那份件,不加编辑态 —— 同一份 HTML 就是归档件 | 同上 |
| 对话区的卡 | 作业指导书(ask / steps / standard)· 依据 · 判据逐条 · 去向 · 填报表单 · 提交与裁决 | 件本身(给一个「去工作区看」的入口就够) |
栽过的:工单报告组件自带一整套外壳(题头 / 元信息 / 指标带 / 章节头),再把活动推的那一屏 包在自己里面 —— 于是人看到的是「一份报告里嵌着另一份报告」,两套版面语言叠在一起。 用户原话:「你用你自己的报告格式嵌套了一层我要的报告格式」。
就地改怎么做的(view-render.ts 的 twinInputs + ui-kit/ViewContainer):
可改的格子在模版里挂 data-col="台账列名",所在的行容器挂 data-edit="域.对象";
渲染时每格多渲一个 <input class="jc-cell"> 孪生件,只读那份与输入那份由样式表二选一。
容器是 sandbox="allow-same-origin" 的无脚本 iframe —— 外层读得到也改得动里面的 DOM:
加 class、挂事件、读 input 全在父文档这一侧,iframe 里始终一行脚本都没有。
收上来的值按台账地址对上工单 blocks[i] 的 ledger/fields,仍走老的 table:N 回传,
服务端写回一个字都不用改。
默认锁着,按「编辑」才开(2026-08-01 用户定):每张可改的表各带一道闸
([data-edit-for="域.对象"],三个钮 data-edit-open / -cancel / -save)——
锁着只出「编辑」,开了换「取消 / 保存」;取消还原成按下那一刻的值,保存才交给外层缓冲
(真正落草稿层仍在提交那一下)。看与改是两件事:整份件一进屏就是编辑态的话,
人还没决定要改什么,眼前已经是一张待填的表(A1 那份 54 个输入框一起亮着)。
⚠️ 跨 realm 陷阱:父文档里写 event.target instanceof Element 判 iframe 里的元素,
恒为 false(Element 是父文档那个构造器,原型链不同)。症状极难查:监听挂上了、
事件也收到了,却每次都在第一行 return,而 class / CSS / 选择器逐个查都是对的。
认能力不认血统:有 closest 就当它是元素。同理,srcDoc 换文档的时机抓不准 ——
与其判断「挂过没有」,不如每次有机会就挂一遍(同一个 (type, listener) 重复 add 无副作用)。
为什么两份都渲、不渲成两套 HTML:同一份件既在待处理页给人改、又在工作成果里定稿归档, 两处是同一份。渲两套等于存两份,存两份就会分叉。
10.3.1 有目标件(设计稿)的时候:搬,不要照着做
A1 两份报告是 1:1 平移 ~/coding/JCAI_PRD/docs/活动执行页/ 的目标件 ——
样式表整段搬进 views/prd-style.ts(PRD_CSS + PRD_SPRITE,一个数值都没改),
标记逐块搬进 views/report.ts,只把写死的数据换成绑定。不用 @sdk 的拼装件:
那套外壳有自己的版面语言(编号章节头、摘要段、元信息带),拿它「照着做」出来的是第二种版面。
栽过三轮才认下来:块搬齐了就报「25 块到位」,而那是结构断言(某个 class 在不在), 对版面一点约束都没有 —— 人一眼就看出不是那份件。所以: 视觉目标件只能看,不能用 DOM 断言验收;搬完必须与目标件并排截图比对。
搬的时候要换掉的四样(原型是带脚本的演示页,这里一行脚本也跑不了):
| 原型怎么做 | 搬过来怎么做 |
|---|---|
| JS 造表、写死的行 | data-rows 行模版,一行一条从草稿层展开;序号交 CSS 计数器 |
| JS 算的计数 | 台账里的汇总列,data-slot 取 |
| JS 判空插「必填/选填」胶囊 | data-blank 让空值真空 + CSS td:empty::after |
JS 拼 class 上色 / 写 style="flex:N" |
data-class-of 把值(或条数)投影成 v-<值>,样式表按值分档、按数取宽(备一把 v-1…v-20 的梯子) |
搬不过来的如实空着 —— A1 空了两处:圆标里的首字母(得算)、「近 6 笔成交」迷你柱
(market.price_history 有列没数据、没有写入方)。编六根柱子是撒谎,画一条空轨像组件坏了,
所以那一格不画,把「为什么没有」写在卡脚。
拼装件(@sdk,A2–A7 仍在用):viewShell({title,titleRef,kicker,tone,icon,summary,meta,stamp,body,sign})
外壳,正文用 section 编号章节串起 letter / factCards / statsBand / tableOnly /
stagesOnly / noteOnly / verdict / actionList。
letter({from,subject,body,attachments})—— 来件原文,pre-wrap照录不摘要。 归一出来的每个字段都要回溯得到出处;原文摆在结果上面,人一屏之内核得完。factCards([{label,value,note,icon,tone,mono}])—— 要素带:摆「谁与什么」, 与指标带(摆「数」)分工。tableOnly(rowsPath, cols, { numbered })—— 列样式mono/num/pill/req/opt(以及req-mono/opt-mono/req-num/opt-num)。req/opt是分量声明: 空格子自己出「必填」红胶囊 / 「选填」灰胶囊,缺必填的整行浅红打底、行首红条 —— 哪几列缺了不许放行,是写清单时就该说清的事,不是让人对着一屏「—」自己猜。 序号列由 CSS 计数器排(模版里没有循环变量)。归档件不摆胶囊:到了那一屏, 空格子的意思不再是「你还能填」而是「客户没给」。actionList([{title,detail}])—— 放行那一刻系统替人做了什么,一条一行。
取数约定:mine:域.对象 = 本单草稿行(成果表只用它 —— 别单放行过的记录不混进来);
裸键 = 摊平字段;引擎注入的案情:case:stages / case:enabled / case:gaps ·
case:openedAt(本地时刻)/ case:origin / case:owner ·
case:routeKey / case:routePort / case:routeTo / case:routeWhy = 这一单算出来走哪一口。
放行那张必须摆出下游要读的关键值与去向—— 人签字前得看见这一单要往哪走。
模版不许算,但基座算过的可以摆。两件事别混:
- 报告自己算一遍(把判据抄成静态文字、拿宽度骗人)—— 不行。抄本与判据是两份东西:
改了
when忘了改报告,屏上那一行就一直指着旧规则。 - 摆基座算好的那一次—— 该做。去向由契约里各出口的
when按序求值(首个命中即出), 基座把结果注入case:route*,与放行时发口的是同一次判定; 报告只把它摆出来:壳上data-class-of="case:routeKey"→v-r1/r2/r3,对应那一行自己上色, 「命中 / 未命中」两句话都写在模版里、由样式表决定哪句现身。 算不出来(有一口没写谓词、或一条都不命中)三个键就是空的 —— 一行都不高亮,不冒充判过。
剧本的 view 决定每一拍推哪张:引擎按「还没干完的那一步」推,活步都收完就一帧不推 ——
最后摆上去的那张(放行页)就是归档件。所以放行那一步的 view 必须指向验收目标件。
11. 运行时参考(活动作者要知道的引擎行为)
11.1 起单与补全(宽进)
process.start 不按 Schema 拒缺项;任务照建,缺 required 顶层键(缺 = undefined|null|'')
→ 任务落 awaiting-human + 发补全卡(不占预算)。答卡只收还缺的键、只收非空字符串;
「没法填」给理由即带缺口开跑,收敛责任归活动。数据按字段名在流程里流动,零强制类型。
11.2 Agent 的行动型(一拍一串,封顶 6)
| 行动 | 形状 | 语义 |
|---|---|---|
start |
{itemId} |
启动标了人工激活的项;已 Active 再按是无事发生 |
plan |
{itemId} |
裁量项入场 |
disable |
{itemId, reason} |
此单不走这条 |
judge |
{criterion, satisfied, rationale} |
评估 semantic: 前提,留痕 |
write |
{itemId, address:'域.对象', rows:[{key,patch}]} |
写草稿。授权面是台账声明(未声明地址/列即拒;空转写 —— 一行都没变 —— 即拒);itemId 只是记账,挂不上照写不拦;patch 只带有值的列 |
drop |
{itemId, address:'域.对象', keys:[{主键}], reason} |
撤掉自己写错的那几行(2026-08-01 加)。只动本单草稿层,正式版碰不到、不抛事件 —— 不是第四种写。授权面与 write 同一道;主键要与写进去时一字不差,撤不到当场拒。门拦住一件办错的事却不给改正的手段,那扇门就只是个死胡同 |
tool |
{itemId, tool, input} |
读类即调(出参进轨迹);写类转意向;未命中转人挂起 |
ask |
{itemId, ask, fields, basis} |
补全卡,挂起 |
human |
{itemId, card, workOrder} |
执行/验收工单,挂起 |
listen |
{itemId, eventType, keyFilter, deadline} |
立监听,挂起;时限必配(缺省兜 P3D) |
finish |
{itemId, rationale} |
宣告一项完成;非人工激活的项就地过 Active 再收 |
submit |
{rationale, fields?} |
提交放行 → 预检 → 发验收卡挂起 |
串的纪律(引擎强制):按序执行,谁被拒停在谁那儿(前缀已生效不回滚,理由喂回下一拍);
tool 之后截断(出参没看见,后面是闭着眼计划的);挂起型收拍;超封顶丢弃留痕。
拍级附带 say{text, tag, view?} —— 不占行动位。
11.3 局面里有什么(Agent 每拍看到的)
说明书 + 活动记忆选摘 + 最近轨迹(窗口 = memoryWindow;含人答的值与工具出参,旧出参截短; 装配技能戳不进叙事)+ 当下可为(Enabled)+ 进行中(Active —— 有开着的活就不算断点)+ 目标缺口 + 待裁量前提 + 功能工具清单(带入参键名)+ 可写台账字典(地址/主键/列)+ 本单草稿(mine:*)+ 任务数据(摊平字段在前、正式版整表殿后截断)。
11.4 提交与放行
submit → 落地预检(必交付物按 ledger 地址数本单草稿行;写类意向 drafted→prechecked;
数据冲突查草稿基线)→ 预检不过不发卡,理由喂回下一拍 → 过了发验收卡
(判据机检结果 + 已验证动作清单 + 可修订值拟值 + 恰选一口)→ 人裁决:
pass → 草稿合并、意向执行(幂等键)、输出口发射、released;
redo/revise → 附意见驳回,环内回转。落地失败 → 落地失败卡,任务 blocked。
11.5 Harness 兜底(活动不用管,但要知道)
预算四项环外强制;停滞熔断:连续 5 拍行动被拒即停问模型,发卡把拒绝理由原话
摆给人;stallAfter 判停滞、nudge 催办;模型隐藏思考已关(reasoning:'off',
每拍 1.5-5s)—— 这就是说明书必须把规则写死的原因(§7.3 纪律 3)。
事件到达痕只记 task 层一条;对话区只投人话(narration / 回执 / 世界事件),
机器踪迹进工作日志不进对话。
12. 自测与安装
12.1 自测四段(照 activities/quote-intake/__tests__/activity.test.ts)
- 清单合规:过
activityManifestSchema;监听时限已配;L1/L2 有谓词;禁区非空、 调人预算 > 0;每件必交付物都有 ledger 地址; - 案例模型:双表示同在;触达世界的项标了人工激活;常来回的项标了可重复; 末尾有收口验收项;说明书把每一步都交代到(逐步关键词断言);
- 判定表逐行求值(用
@sfeel的evaluateUnaryTest写个 6 行的求值器): 每条规则、优先级序、同输入同结论; - 声明与契约对齐(§6.2 三条线)+ 模版纪律(局面数、无脚本、放行页摆关键值)+ 技能引用的工具都在声明内。
案例活动没有 run,自测不跑回合(那是 Loop 的事);mock 宿主对没有 run 的活动
如实报错,不给假 outcome。
12.2 安装两审
静态审(任一 error 拒装):signature-invalid / sdk-incompatible / schema-invalid /
model-name-unbound / dangling-listener / template-has-script / environment-capability /
dynamic-eval / ledger-type-conflict / acceptance-level-misreported;
tool-contract-missing 是 warn(声明了系统没有 → 运行时转人,是系统的缺口不是活动的错)。
回放审:历史成功样本仍须全过;首装以样例集替代。
12.3 试跑口径
试跑一律用 AI 账号(绝不派到用户工作台);起单走登记触发;逐拍对账看
sys.task_memory(每条「行动被拒」都是设计欠账);测完
pnpm --filter @app/api reset-runs 清运行数据(清跑出来的,留配置好的)。
13. 记忆与技能
- 任务记忆只增不减;终态封卷后
distillHooks蒸馏进活动记忆(跨任务、四层、 渐进披露、语义引擎不读)。 - 技能
{id, label, applicability, method, crystallized[], knowledgeRefs[], version}: 常驻上下文只有目录(label: applicability),正文按需加载。载方法不载权力 —— 结晶片段调工具仍过白名单。A1 的三条(读懂一封询价 / 打标前备齐判据 / 状况标不等于去向) 是好范本:方法里写锚点、纪律、批量惯例,不写成产品文案。
14. 排障速查表(症状 → 根因 → 处置;全部实测账)
| 症状 | 根因 | 处置 |
|---|---|---|
| 起单后第一个项都不 Enabled,整案不动 | 案卷项 id 不是任务视图键名 | §7.1 纪律 2:改成入参/台账列名 |
| 连续「行动被拒:…迁移非法/项不存在」,熔断卡出现 | 模型多半没错 —— 计划项建多了/嵌套了 | §7.1 纪律 1:扁平化,图上几步就几项 |
| 一单 20+ 拍才收口 | 说明书没写一拍连做/拍数预期;或计划项过多 | §7.3 纪律 4;§7.4 拍数账 |
| 验收机检判据全 false,可数据明明写了 | 谓词读了逐行列(后写的赢盖掉了) | §6.2 汇总列纪律:一单一行汇总对象 |
| 提交放行被拒「必交付物缺」 | deliverable.ledger 地址没写/写错;或活动确实没写那张表 | §6.2 对齐线 1;说明书点名台账 |
| 下游网关死路(取不到 needXxx) | 出口标量字段没有台账列承载 | §6.2 对齐线 3 |
| 放行后正式表没有某字段 | write 的列名不在声明里(被当场拒过,轨迹里有) | 对照台账字典改列名 |
| 补全卡答了,数据没进台账 | 系统不替 Agent 写 —— 说明书没写「答案要自己 write」 | §7.3 纪律 6 |
| 对话区从头到尾空白 | 说明书没写 say 指引 | §7.3 纪律 7 |
| 工作区画面不换 | view 名不在 viewTemplates 里(轨迹有「换画面失败」留痕) | §10.3 点名局面模版 |
| 该走「受理并追问」的次要缺字段被拦单发卡 | 关思考后模型不细想边界 —— 规则没写死 | §7.3 纪律 3 |
| 明明有工具却发卡问人 | 早期版本症状(工具清单已进提示词);若复现,查该工具是否在 declarations.tools | §9 |
| 收口前一两拍被熔断(预算耗尽) | budget 给太紧 | §6.2 ⑦:按实测留一倍余量 |
| 改了包重装没生效 | 同版重装 no-op | 升 packageVersion |
| 一次人答/工具出参在日志重复多行 | 旧版症状,已修(到达痕只记一条) | 升级后消失 |
| 机检判据全绿,而交付物本身是错的(条数对不上、单没开全) | 等式两边都由活动自报 —— 它数错一次就一起错、一起「对上」 | 一边改读基座数出来的 域_对象_rows(本单在那张表写下几行);会数数的交判定器/工具出参,别让模型在脑子里数 |
| 一条 L1/L2 判据永远绿不了,而数据本身完全合规 | 它读的是活动自报的汇总列 —— 人补了值、基座写回了草稿层,而那个数要模型自己重算,漏算一次门就再也开不了 | 台账字段标 required,判据改读基座现数的 域_对象_missing。机检只许读基座数得出来的数 —— 这一条已经栽过两次,第二次是十轮里三轮 |
| 台账里某个数被「改小」了,判据随之变绿 | 那条机检判据读的正是活动自己写的数 —— 门只接受「是」,模型就去改数(实测:missing_minor_total 从 1 改回 0) | 机检判据只许读基座数得出来的数;数不出来的,别假装机器判得了 —— 降 L3/L4 交人,并把那个数摆到放行页上让人看见 |
| 裁量判据(L3/L4)裁成「不满足」之后反复提交 | 旧提交门要求裁量项也「为真」—— 而裁量的本义就是可以裁出不满足 | 已修:裁量项裁过即可提交,裁断连理由随卡交给人;活动侧把每条 L3/L4 都在 T_release 那一步裁一次,别漏 |
| 同一条理由拒回十几次却不熔断,预算烧到 exhausted | 一拍一串行动,拒回标题带串位(「第 2 个」)—— 整条一比成了多堵墙;叙事窗只有 20 条也装不下几次 | 已修:比对削掉串位前缀 + 熔断另开 60 条窗。活动侧看到这种日志,先查是不是某个数一直不对 |
| 人在卡上填了值,台账里那一格还是空的 | doneWhen 写成「问过就算完」(asked_total > 0)—— 卡一发它就为真,人一提交这一步当场被自动收掉,模型没轮到把答案写回来 |
收步等的是答案落了表:另记一个 answered_total,写回台账时才 +1 |
| 「工作成果」里定稿归档的件不是人签的那份 | 收口后引擎还会跑一拍,那一拍推了固定模版名、退到「声明的第一张」,把放行页冲掉 | 已修:按剧本 view 推,活步收完不推;活动侧确保放行那一步的 view 指向验收目标件 |
| 制表符 BOM 拆错列(空格子被右边的值填上) | 连着的 \t\t 是中间那格空着;模型把后面的值往前挪 |
说明书写死:每行格子数必须等于表头列数,数不对整行重数;行尾连续制表符是末尾几列都空 |
| 数值字段被写成 0 而缺口数还记着缺 | 模型拿 0 表示「没有」 | 说明书写死:数值列没有就给 ""(基座按列型归型,空值=不写这一列);0 是一个真实的数量 |
| 同一颗料在成果表里出现两行 | PN 是主键,补写时换了个写法(补全了后缀)就是新开一行 | 说明书写死:回填按卡上那一列的 PN 对行;发现抄错交给人,别用新 PN 再写一行 |
| 判据拦住了,而模型改不动 —— 同一条理由连拒到熔断 | 门后没有路:办错的那件事没有对应的动作 | §11.2 有 drop(撤本单草稿层的一行);拒回的话必须指向一个做得到的动作,指错方向比不给指引更糟 |
| 行数判据对不上,模型只会改数字 / 重写 / 再提 | 拒回只给了两个数,「多的是哪一行」它数不出来 | 已修:读数连主键名单一起摆(域_对象_keys,封顶 12 行)。活动侧照名单撤那一行,别去改数 |
| 「要先 start」而项当下是 Completed,start 又被拒 | 拒回的话没按状态分开说,两堵墙对着撞 | 已修:收掉/停用的项改说「已办完,挂到当下这一步」并列出可为的项;start 已收的项无事发生 |
| 收了步却过不了验收,回头补写一张已经收掉的表 | 收步判据比验收判据松(读自报的数,验收读基座数的行) | 两处比同一件事:收步也读 域_对象_rows。松出来的那一截就是返工 |
| 该经过人的一步整个没发生,人一眼没看过就被叫去验收 | 「经过人」被写成了某一步收步条件里的半句(「缺东西才叫人」)—— 不缺就自动收了 | §7.1 纪律 3.5:人的门单独建成一项 + 人工激活 + 不给 doneWhen,下一步的入场前提取一个只有人回复才产生的计数列 |
一行没写的那张表,判据 域_对象_rows = 0 却过不去,连撞五拍熔断 |
读数从前只数「写过的表」——没写过 ≠ 写了 0 行,而 S-FEEL 里未绑定的名字参与比较求值为假 | 已修:按 declarations.ledger 把每张声明过的表先铺成 0。活动侧遇到「某类为 0 就卡住」的判据,先查读数在不在 |
15. 待实现与已知不一致
| # | 事项 | 现状 | 目标 |
|---|---|---|---|
| ④ | 受理分支误吞验收裁决 | deliver 的受理分支判 awaiting-human && human 不看 cardId(task-service.ts);「没法填」带缺口开跑的单再遇验收驳回会被误判成补全 |
受理分支按补全卡 cardId 匹配。开发侧规避:带缺口开跑的单尽早在环内补齐缺项 |
| ⑤ | 治理台白名单授权口 | 只有种子常量表(seed-mvp.ts),无 API 无 UI |
治理台按活动配白名单 |
| ⑦ | dist 真编译 | index.js 是源码文本拷贝(债 M5-3) |
build-activities 真编译 |
| ⑧ | 单拍路径的工具调用缝 | 工具清单已进提示词(只给功能工具);调用仍是自由字符串经 ToolInvoker,未走总线编译 | 开环转正或单拍接总线 |
| ⑨ | preauthorizedActions 死代码 |
无调用方 | manifest 里不要写 |
| ⑩ | SchemaValidator 双死依赖 |
task/process 声明了不再调 | 清理 |
| ⑪ | 评价卡(rate) | schema 在、链路未接 | 用户定:暂不做 |
小不一致(不阻塞):declarations.tools[].version 无消费者;contract.resources.tools
双份声明;replay 模式跳过白名单检查;注册路由造不出纯工具;工具 retries 声明了不重试;
转人答案不经出参 Schema 校验(债 M6-1);进程重启后动作级 trace 丢失(债 M6-2)。