This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-MVP/docs/活动开发规范.md
T
ChaiHaoandClaude Opus 5 d1ddf5f481 活动开发规范:给 AI 开发活动查阅的一份现状如实、差距列账的规范
用户要开始实现活动和工具调用逻辑,先把「活动怎么开发」写死成 docs/活动开发规范.md。
四条产品语义(本轮问答确认):
  ① 活动只读世界,一切写(台账/发邮件)必须经人类验收才落地;
  ② 活动间数据无强制类型,活动有启动 Schema —— 机械检查为门(required 缺项发补全卡)
    + Agent 案内随时可补;
  ③ 工具调用完全声明式 —— 未注册转人类执行卡+作业指导书(指导段写在工具声明里),
    未授权报错不转人;
  ④ 评价卡暂不做。
另补:工作台视图逻辑以 PRD 目录为语义权威(统一建模:AI Loop 自主、阶段可重复、
过程成果逐个验收;工作事项两类执行/验收、决策在对话区、三类同级唤醒事件、气泡二元制)。

写法纪律:**现状与目标分开说** —— 已就位的机制带 file:line(三路并行探查 + 关键行
逐条回源复核),产品要求但代码未到的标 ⚠️ 并汇总第 12 章待实现清单(11 条主账)。

**编排层确认满足**语义②:起单宽进、flatScope 零类型铺平、节点入参全铺下游自取、
出口载荷从落地事实推导、闭合只查结构+装载 —— 各有代码与测试证据,已写进第 5 章。

探查顺带钉出的两条要账(debt.md 已记,指向规范第 12 章):
  · 写类动作意向登记后**永不执行**(submitForRelease 无调用方,意向永停 drafted);
  · 工具未命中现状是抛错+拒装,转人链路整条待建。

本轮只出文档,不改一行代码。五闸绿(纯文档,跑闸当回归保险)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-30 02:56:36 +08:00

32 KiB
Raw Blame History

活动开发规范

2026-07-30 定稿。给 AI 开发活动时查阅的规范:开发一个活动要写什么、能用什么、 什么会被拒、跑起来是什么样。基于代码现状如实记述 —— 每条「已就位」的断言都带 file:line;产品要求但现状还不满足的,标 ⚠️待实现 并汇总在第 12 章。

相关文档:docs/编码规范.md §8(活动包平台契约)、各模块 CONTRACT.md(模块级不变量)、 docs/产品设计.md(三面一体)。本文与它们冲突时,以代码现状为准,并把冲突记进第 12 章。


1. 读者与用法

  • 读者:开发活动(activities/<id>/)的 AI 或人。你不需要读 apps/api 的源码 —— 活动的全部世界是 SDK 基座六件(第 3 章)与清单六区(第 4 章)。
  • 用法:开发前通读 2–4 章(边界与结构);写案例模型看第 8 章;写交互看第 9 章; 提交前对着第 11 章自测清单过一遍。
  • 标注约定:✅ = 现状已就位(带 file:line);⚠️ = 产品已定、代码未实现(汇总第 12 章)。

2. 总纲与铁律

活动 = 任务的定义。 编排引用一个活动,运行时就长出一个任务(恰一名负责人)。 活动在系统外开发、经安装通道入库(registry.install),版本恒新增、同版重装 no-op、 在途任务按启动时版本跑完(apps/api/src/modules/activity-registry/CONTRACT.md)。

统一建模(语义权威:JCAI_PRD/workbench/src/app/(proto)/_data/model.ts 头注,2026-07-25):

一个活动完全由 AI Loop 自主完成;Loop 按阶段推进(阶段可重复),阶段产出 过程成果(Deliverable:草稿 → 待验收 → 已放行 / 已驳回)。一个任务可能有一或多个 过程成果,每个成果都要调人类工具放行。

活动不再分范式(2026-07-29 用户定):业务模型一律案例模型(CMMN + 说明书 + DMN), 业务分类是活动库唯一的一维(packages/contracts/src/manifest.ts:20-23)。

四条铁律

# 铁律 机制依据
① 只读世界:活动只能读台账、调读类工具、问模型、加工表单 基座六件即全部能力(§3);环境能力静态审拒装(§11)
② 写只有草稿与意向:写台账 = 草稿层(不产生数据事件);外部动作(发邮件等)= 登记动作意向(入参冻结 + 幂等键),登记那一刻世界上什么都没变 ledger.write 只有草稿写(base.ts:55-60);写类工具自动转意向(apps/api/src/app/tool-bus.ts:206-209)
③ 终局唯放行:任务完成的唯一出口是人类验收通过 —— 那一刻才草稿合并、意向执行、输出口发射;验收不过 = 不完成 raiseAcceptance 是唯一提交口(turn-runner.ts:207-242);land() 六步(§7.3)
④ 活动不自转:run(base) 是一个回合的推进,由引擎驱动;不自设步数、不产生终局迁移;预算与时限由 Harness 在环外强制 define.ts:22-25;迁移拓扑表把 Completed/Terminated 设为死胡同(loop-engine/internal/domain/lifecycle.ts:29-38)

3. 能力边界:基座六件就是全部世界

packages/activity-sdk/src/base.ts(权威,100 行,逐行有注释)。 「基座即笼子:内容归活动,笼子归系统。活动代码无环境能力,一切能力经调用契约注入, 越权在结构上不可能。」

件 句柄 API 要点
① 契约 base.contract activityId / version / data / message / outputPorts{mandatory,selective} / prohibitions / discretion 只读,启动前冻结;message 是委托消息,不得携带扩大授权的效力
② 交互 base.interaction pushView(blocks) / pushHtml(html) / say(text, tag?) / issueWorkOrder(order) / issueCard(card) 视图模版过净化门(含脚本即拒);工单两模式、对话卡三族(§9)
③ 记忆 base.memory window(size) / recall({layers?, limit?}) 任务记忆窗口是活动侧唯一记忆接口;活动记忆渐进披露,语义引擎不读
④ 台账 base.ledger read(address, where?) / write(address, key, patch) 读 = 任务视图(正式版 + 本任务草稿层,读己之写);写 = 只有草稿写,不产生数据事件
⑤ 工具 base.tools invoke(tool, input) / intend(tool, input, label) / listen({eventType, keyFilter, deadline}) AI 对世界只有触达(动作)与等待(监听)两种姿势;监听时限必配
⑥ 模型 base.llm generate({model, prompt, system?, outputSchema?}) model 是自定义模型名(逻辑名),不触厂商与密钥;给 outputSchema 即要求结构化出参并校验

outcome 三态(define.ts:10-16):{kind:'submit', deliverable, rationale}(提交放行)/ {kind:'await', reason}(等人或等事件)/ {kind:'blocked', reason}(推不动,如实报)。

禁区(安装静态审黑名单,activity-registry/internal/domain/static-audit.ts:144-159): process.env、require(、node:fs、node:net、node:child_process、globalThis.fetch、 fetch(、XMLHttpRequest、eval(、new Function( —— 产物文本扫描命中即拒装 (environment-capability / dynamic-eval)。另有 depcruise 双向隔离(activities 不得 import apps,反向也不得)与 eslint 禁 fetch/process 全局。

SDK 三道守卫(本地 mock 宿主即可测出,packages/activity-sdk/src/host.ts):

  1. 视图模版无脚本(host.ts:64):<script 即抛 —— 容器无脚本执行权;
  2. 裸调用拒发(host.ts:71):issueWorkOrder 的 basis 为空即抛 —— 调用必附上下文;
  3. 监听时限必配(host.ts:116):deadline 空串即抛 —— 无死等。

如实说明:SDK 面是本地开发面(线上 .js 产物只当数据存,不 import 不 eval, ADR-004)。线上真正的闸是工具总线 + ToolInvoker 白名单 + card-rules 机检 —— 与 SDK 守卫同一套规矩,两头都得过。

4. 包结构与清单六区

4.1 目录树(照 activities/quote-intake,六包结构一致)

activities/<activity-id>/
├── package.json            # @activities/<id>,private,type:module,exports "." → ./src/index.ts
├── src/
│   ├── index.ts            # 代码入口:export default defineActivity({...})
│   ├── manifest.ts         # 清单六区 + 导出 DMN 常量(供测试直接求值)
│   ├── case-model.ts       # 导出 CMMN_XML 与 NARRATIVE(双表示,同版本发布)
│   ├── skills.ts           # 导出 Skill[](id 用 skillIdSchema.parse 品牌化)
│   └── views/report.ts     # HTML 视图模版常量(viewShell/kvCard/tableCard/noteCard 拼装)
├── __tests__/activity.test.ts   # 自测四段(§11.2)
└── dist/{manifest.json,index.js} # 构建产物(⚠️ 现为过期占位,债 M5-3)

入口写法(activities/quote-intake/src/index.ts):只 import @sdk;台账地址提成 顶部常量;export default defineActivity({ name, async run(base) {...} });典型 run 体 = ledger.read 取数 → pushView 推块 → 分支 say + return {kind:'await'} 或 return {kind:'submit', deliverable, rationale};本地纯函数放文件末尾。 ⚠️ defineActivity 目前仍要求 paradigm 参数 —— 陈旧残留,统一填 'loop',待清理(第 12 章⑥)。

4.2 清单六区(packages/contracts/src/manifest.ts:163-172)

区 内容 硬规矩
① identity activityId / name / packageVersion / category / sdkVersion / checksum / signature? 无 paradigm;sdkVersion 与 SDK 精确相等否则拒装;改了包内容必须升 packageVersion(同版重装 no-op,改了不升版 = 静默丢失)
② contract 七件契约(§4.3)
③ declarations models[] / tools[] / ledger[] / listeners[] / harness 运行时只读这里的工具与模型声明;监听时限必配否则 dangling-listener 拒装
④ assets businessModel{cmmnXml, narrative, dmn} / skills / viewTemplates / cardSchemas / distillHooks CMMN 与说明书同版本发布不允许分叉(空任一即拒装)
⑤ samples {name, input, message?, expect:{ports[], payloadShape?}}[] 至少 1 条;首装无历史时以静态审 + 样例集替代回放审
⑥ entry {file, checksum} 指 .js 产物

declarations.harness(manifest.ts:33-45):memoryWindow(默认 20)/ stallAfter (ISO-8601 时长,默认 PT30M —— 停滞熔断的判据)/ nudge[] / retry。

台账声明(packages/contracts/src/ledger.ts:59-76):{domain, object, label?, fields[]}, 字段 {name, type, label?, primaryKey, masked};标识符只准小写字母开头、字母数字下划线。 域的归属纪律:活动自持域用活动名;跨活动的事实只有两条路 —— 输出口载荷,或共享域 shared。物化三律(ledger/internal/domain/schema-merge.ts):最大化合并(同名字段 masked/primaryKey 取或)、最宽类型(integer→number→text 等加宽链,json 与标量互斥即冲突)、 只增不破坏(只产出建表/加列/加宽,新增主键即冲突 → ledger-type-conflict 拒装)。

4.3 契约七件(packages/contracts/src/contract7.ts:129-140)

件 写什么 硬规矩
① input data(JSON Schema)+ messageAllowed / messageHint data 即启动 Schema(§5);message 不得携带扩大授权的效力
② output mandatory[] + selective[] mandatory 每次放行必发(AND);selective 每组至少 2 口、恰发一口,选口即分支结论(§7.4)
③ requiredDeliverables {key, label, schema?}[] 缺一不得提交放行;验收工单「成果清单」的来源
④ acceptanceCriteria {key, label, level: L1-L5, predicate?, modelRef?}[] L1/L2 必带 predicate(S-FEEL)或 modelRef,否则 acceptance-level-misreported 拒装 —— 验收等级不得谎报
⑤ prohibitions {key, statement}[] 短而硬;负面清单优于正面枚举
⑥ authorization discretion[] / preauthorizedActions[] / humanCallBudget humanCallBudget = 调人预算(环外强制);⚠️ preauthorizedActions 现为死代码,暂不使用(第 12 章⑨)
⑦ resources tools[] / skills[] / models[] / roles[] / budget{steps, tokens, activeMinutes, humanCalls} 预算四项由基座环外强制,不得由活动代码自查自守;models 是自定义模型名

坑位:contract.resources.tools 与 declarations.tools 是双份声明,运行时只读 declarations(engines.ts:443-447)——两处要写一致,安装审只查 declarations。 出口字段要有台账列着落:出口载荷从放行落地的事实推导(§7.5),声明一个没有任何 台账列承载的标量出口字段,下游网关会因取不到值而死路(route-dead-end)。

5. 启动与补全(活动输入无强制类型)

5.1 编排层确认结论(✅ 全部成立)

用户语义「活动间流转数据没有强制类型、活动输入可接受任何类型」在编排层已成立:

  • 起单宽进:process.start 不按调用接口拒缺项(process-service.ts:270-315, 注释写死「门口不设缺项闸,收敛点在活动」;start-guard.test.ts:109-115 守着)。
  • 数据按字段名流动:flatScope 铺平作用域(起单入参垫底 → 各输出口字段依次盖上 → 刚发射的载荷最后盖,后发生的赢,零类型检查,routing.ts:32-41);节点入参 = 整个铺平作用域,下游按自己的契约取,无契约裁剪(self-hosted-runtime.ts:256)。
  • 闭合校验不查字段:只剩结构闭合(dangling-node / multi-default-edge)与活动装载 (unknown-activity)四条(closure.ts:33-81)。字段够不够是活动自己的事。

5.2 机械门:required 缺项 → 补全卡(✅)

任务启动时(task-service.ts:88-134):

  1. missingRequired(inputSchema, data):只查启动 Schema 顶层 required, 「缺」= undefined | null | ''(0/false/[] 都算有);标签取 properties[key].title ?? label ?? key(给人看「客户码」不是 customerCode)。 不做类型校验 —— 这是机械门的全部(语义级判断归 Agent,§5.4)。
  2. 缺项照建单,任务落 awaiting-human,引擎照常装载;
  3. 发补全卡(elicitation 对话卡,engines.ts:715-736):字段 = 缺的 {key, label}; 不占活动调人预算(补全是受理的事,不是活动的打扰);挂 P2D 人事件监听。

答卡合并(task-service.ts:262-299)四条硬规矩:

  • 只收当前还缺的键(白名单),契约外的键、已填过的键静默丢弃;
  • 只收非空字符串 —— 补全卡回传一律按字符串,数字/布尔会被静默丢弃(设计表单时注意);
  • 补齐(或「没法填」)→ 置 running 并立即 tick;答一半 → 只再派一张只含剩余项的卡,引擎一步不走;
  • 「没法填」:回传 reason 非空即放行带缺口开跑 —— 收敛责任从此归活动内部。 两条路必走其一(答值或给理由),空答被 card-rules 拒收。

5.3 前端呈现(✅)

补全卡渲染在对话区(desk-task/internal/chat-bubbles.tsx:214-251,题头「请你补全 · N 项」); 「没法填?告诉 AI 原因」是显式按钮;有未答的补全/决策卡时输入框锁定 ——「先办卡,再说话」。

5.4 Agent 案内随时可补(✅ 机制在,活动要自己用)

开跑后发现「字段在但内容不像话」(机械门查不出的语义级不满足),活动应:

  • 小信息:interaction.issueCard({k:'elicitation', ask, fields, basis}) —— 对话区就地要几个小信息;
  • 要人干活:interaction.issueWorkOrder({mode:'execution', ...}) —— 执行工单 (PRD 统一建模里「补全」并入执行事项的那条路,§9.2)。

⚠️ 边界隐患(第 12 章④):走过「没法填」的任务,若后续验收被驳回(runtime 回到 awaiting-human),到达的 human 事件会被受理分支误吞(它不看 cardId)。在修复落地前, 活动拿到「带缺口开跑」的单,应尽早在环内把缺项补齐或显式置值,别让缺口一路挂到验收。

6. 工具调用:完全声明式

6.1 设计口径

设计活动时只考虑需要什么工具:工具名 + 输入 + 输出(+ 给人看的指导段⚠️)。 不关心系统里有没有实现 —— 声明的工具进 declarations.tools(只有名,版本字段现无消费者)。

已注册工具的契约形状(toolbox/internal/domain/tool-contract.ts:13-39): name / displayName(中文名)/ version / description(**面向 AI** 的适用说明,≥8 字)/ input / output(JSON Schema)/ effect: 'read'|'write' / errors[] / idempotent(写类必须 true)/ timeoutMs / retries / connectionKeys[](只声明键,值在治理台)/ capabilities.validate? / pure? / ownerActivityId?。 ⚠️ 增加 guide 字段(面向人的作业指导段):工具未命中转人时,执行卡上的作业指导书 从这里来 —— 声明式、可审、不临场生成(第 12 章②)。

6.2 运行时五分叉

情形 目标行为 现状
命中 · 读类 直接执行 ✅ tool-invoker.ts 执行(超时 race;入参不合 schema 回 {ok:false, code:'input-invalid'} 不抛)
命中 · 写类 登记动作意向,放行后由系统执行(附幂等键) ✅ 登记(app/tool-bus.ts:206-209「现在世界上什么都没变」);⚠️ 但落地链路断着 —— 意向永停 drafted,放行时不执行(§7.3、第 12 章①)
未注册(系统里没有) 生成人类执行卡 + 作业指导书:卡面 = 工具 guide 段 + 本次输入数据 + 按输出 Schema 生成的回填表单;人填回的表单值就是这次工具调用的出参 ⚠️ 现状抛 ToolNotFoundError(tool-invoker.ts:43),单拍路径 catch 后记忆留痕「行动被拒」回合不崩 —— 转人待实现(第 12 章②)
已注册 · 未白名单授权 报错(越权是治理配置问题,不静默转人) ✅ ToolNotAllowedError(tool-invoker.ts:45-51,文案「越权在结构上不可能」)
已停用 失败路径(活动按失败处理) ✅ ToolDisabledError

配套现状(如实记):

  • 工具总线 = 声明 × 整张目录,编译时未命中收进 missing[] 如实回报、写观察进任务 记忆、不阻断(compileToolBus,tool-bus.ts:184-213;engines.ts:449-455)—— 这半边已经是「声明式」的形状,缺的是运行时把 miss 转人;
  • ⚠️ 安装静态审现把 tool-contract-missing 判 error 拒装(static-audit.ts:108-111)—— 与「声明式工具、系统没有就转人」正面冲突,需降级为 warn(第 12 章②);
  • ⚠️ 白名单授权现状只有种子常量表(seed-mvp.ts:109-117),无治理台授权口(第 12 章⑤);
  • 缺省单拍 Agent 路径不经工具总线(工具名是自由字符串,唯一闸门是 ToolInvoker); JCAI_AGENT_OPEN_LOOP=1 的开环路径才吃总线的声明×目录编译(第 12 章⑧)。

6.3 系统自带的工具(不用声明/只需声明模型)

  • human.ask 恒在(tool-bus.ts:52-75):调人执行卡,零声明即得,不进目录不进白名单 —— Harness 给的能力;每发一卡计 humanCalls: 1。
  • LLM 五件(tool-bus.ts:84-159):llm.extract / classify / summarize / judge / rewrite, 仅当 declarations.models 非空才上总线(没声明模型却问模型是越权,用量无从归因); 提示词归系统,活动不写也改不了;llm.judge 只作参考,不代替人的放行裁决。

7. 写动作与验收(所有写必须经人类验收)

7.1 写三分(✅,ledger/contract.ts:85-114)

写法 谁用 事件
systemWrite 系统(受理落件等) 即时抛数据事件
draftWrite 活动过程写的唯一形态(base.ledger.write) 不抛事件;写时记基线值
releaseWrite 放行那一刻(系统调) 先查冲突(草稿基线 ≠ 当前正式版即 DraftConflictError)→ 事务合并 → 读回落地行 → 此刻才抛事件 → 清草稿

7.2 动作意向(✅ 登记,⚠️ 执行断链)

写类工具调用(base.tools.intend,或 invoke 写类工具被自动转意向)→ 登记 sys.action_intent:入参冻结 + 幂等键 + 状态机 drafted → prechecked → landed/failed/abandoned。 登记那一刻世界上什么都没变 —— 这是铁律②的机制承载。

7.3 放行落地六步(release-service.ts:144-244)

① releaseWrite 草稿合并(原子、过冲突检查)→ ② 记痕「放行写合并 N 行」→ ③ 只取 prechecked 的意向逐个执行(附幂等键 ${taskId}:${intentId},已成动作不回滚)→ ④ 裁定(有失败即 blocked)→ ⑤ 受阻:发落地失败卡、口一个不发、任务落 blocked 终态 → ⑥ 收敛:恰选一口校验(兜底)→ 发射输出口。

⚠️ 重大现状洞(第 12 章①):把意向从 drafted 推到 prechecked 的 submitForRelease(预检 + 已验证动作清单上卡)没有任何调用方 —— 活路径 (turn-runner.raiseAcceptance)绕过了它。后果:写类动作登记后永不执行、 验收卡的 actions 恒空、criteria 恒为硬编码单条 L5、幂等键登记/落地两套不一致。 实现工具调用逻辑时这是第一优先级。

7.4 验收工单与恰选一口(✅)

验收工单 schema(card.ts:328-350):deliverable / criteria(L1-L5 逐条,pass 标记)/ actions?(通过即执行的动作清单)/ fields(报告可修订)/ portChoice?(选择组)/ passLabel?。 回传(card.ts:473-491):verdict: pass | redo | revise + values + comment? + selectedPorts。

机检三规矩(card-rules.ts:101-150):驳回必附意见;恰选一口在 pass 时强制 (未选 / 选组外即拒收,主闸在回传收单,落地 validateSelection 兜底);工单只增不改。 producesCompletion 仅 acceptance + pass —— 验收通过是任务完成的唯一来源。

选中的那一口就是这单的分支结论:放行注入 facts.flat.outcome = 选中口名 (engines.ts:642-644),下游异或网关的条件读的就是它(outcome = "won" 等)。

⚠️ 前端链两端断(第 12 章③):验收工单的 fields 前端不渲染、values 恒传 {} (人工修订报告落不下来);驳回 UI 只有 redo,引擎侧把 redo/revise 都塌成 reject。

7.5 出口载荷从落地事实推导(✅,契约设计的硬约束)

outputPortsOf(manifest, landed)(engines.ts:535-574):数组字段 ← landed.rows 整体; 标量字段 ← landed.flat[field](驼峰/下划线两认);landed.flat 还会全量兜底铺进每个口。 推论:出口声明的标量字段必须有台账列承载(活动放行前写进草稿的行),否则下游取不到 —— 这是活动契约与台账声明之间必须对齐的一条线(历史事故:两枚去向旗没有列,G1 全流死路)。

8. 案例模型(CMMN + 说明书 + DMN)

8.1 CMMN 子集封顶(loop-engine/internal/domain/cmmn-parse.ts)

计划项 8 型:humanTask / decisionTask / processTask / caseTask / stage / milestone / timerEventListener / userEventListener;结构标签 23 个(sentry/onPart/ifPart/planningTable/ discretionaryItem/itemControl/…)。表外构件直接拒(assertSubset 抛错,不静默忽略)—— 子集封顶要能被机器证明。

记号 语义 纪律
manualActivationRule Enabled ≠ Active,要按一下才动;Agent 发 start 来按 触达世界与关键交付的项必须标(AM 4.1 原理一)
repetitionRule 项可重复(阶段可多轮) 撒网/追问这类常来回的事要标
planningTable / discretionaryItem 裁量项:Agent 临场规划入场 「看情况做」的事放这里
entryCriterion / exitCriterion sentry = onPart(听任务数据/项迁移)+ ifPart(S-FEEL) 条件读任务数据字段
ifPart 前缀 semantic: 语义性前提:引擎不机算,交 Agent 裁量 「回价是否可信」这类判断这样写
autoComplete 齐了即收(必做的都完成、没有进行中的) 案计划级与阶段级都可标;齐了不问 Agent 直接提验收
timerEventListener 时限(ISO-8601 时长) 催办/过半提醒
userEventListener 人事件(客户改需求等) 外部变化的入口

AI 不得产生终局迁移:迁移拓扑表把 Completed/Terminated 设为死胡同, Available → Completed 不在表内 —— Agent 想跳步会吃 IllegalTransitionError, 被回合循环捕获成「行动被拒」留痕,不崩(turn-runner.ts:363-372)。

8.2 说明书(narrative)

与 CMMN 同版本发布、不允许分叉(空任一即拒装)。写法照 activities/quote-intake/src/case-model.ts: §0 开案(什么事、成品是什么)→ S1..Sn(各阶段怎么办、判断口径、宁可空着不许臆造这类 纪律)→ §Z 结案(什么算办完、交出去什么)。说明书是 Agent 每一拍叙事面的第一段 —— 写给 AI 执行看的,不是给人读的产品文案;判断口径要写死(「吃不准降 B,不许臆断 A」)。

8.3 DMN(能算的不问 LLM)

{drd[], tables[]};表 = hitPolicy(默认 FIRST)/ inputs(name+expression)/ outputs / rules。 单元格谓词 = S-FEEL:'-' 或空串任意匹配、'"A","B"' 析取、'<90' 比较、'[0..100]' 区间。 求值(dmn.ts:33-76):任一输入缺 → incomplete(带 missing);零命中 → no-match; 命中留痕 DecisionTrace{inputs, hitRules, outputs}。决策表在 decisionTask 上以 decisionRef 引用;同输入必同结论、零 token —— 规则能写死的判断一律进 DMN。

9. 工作台视图逻辑(PRD 为语义权威)

语义权威:JCAI_PRD/workbench/src/app/(proto)/_data/model.ts(统一建模)与 _components/{ViewRegion,TaskChat}.tsx;WOS 平移件:apps/web/src/features/desk-task/internal/ {ViewRegion,TaskChat,chat-bubbles,work-order-report}.tsx。卡族 schema 前后端同源: packages/contracts/src/card.ts。

9.1 三栏分工(结构性定调)

栏 承载 铁律
视图区 全部结构化交互:HTML 交互界面(表单读写 + 图形展示)+ 工作事项 + 过程成果 待处理翻页不排队(队列是活动内部的事);五页签:待处理/沟通记录/工作成果/任务详情/工作日志
对话区 只承载非结构化消息 + 决策选择题;系统事件 = 灰头像 气泡二元制:纯文本气泡或完整表单卡,不允许中间态;补全/决策卡吐出期间 composer 锁定 ——「先办卡,再说话」
任务日志 全部任务记忆的结构化呈现 归侧四类(机器产出/人产出/人裁决/世界等待)= 度量口径,零埋点

三类同级唤醒事件:①对话输入 ②外部事件(数据/时间/控制)③页面交互(含工单回传)。

9.2 工作事项(WorkItem):人类工作清单

AI 决定、只增不减,只有两类(决策不是工作事项):

类 字段(PRD model.ts:106-133 ↔ WOS card.ts:279-350) 用途
执行 ask(工作要求)/ steps[](作业指导书分步要点)/ standard?(完成标准)/ basedOn?(报告正文取自哪份过程成果 —— 人要在数据上作判断,就得把数据摆在表单旁)/ blocks? / fields[](回填表单,字段可带 ai 拟值)/ submitLabel? 做 AI 的工作要求 —— 原「补全」并入此类;⚠️ 工具未命中转人也走这张卡(第 12 章②)
验收 deliverable / criteria(L1-L5)/ actions?(待落地动作意向,读/写)/ portChoice? / passLabel? 收 AI 的成果:放行 / 驳回(必附意见)

对话区另有三张卡:决策(选择题,recommend + 后果说明)、fill 补全卡(就地要几个 小信息;「没法填的理由」是另一条必走之路)、rate 评价卡 —— 暂不做(schema 在,不接)。

9.3 视图模版

活动推视图两条路:pushView(blocks)(结构化块,11 型:callout/kv/badges/table/bars/donut/ mail/cards/waiting/files,card.ts:103-201)与 pushHtml(html)(HTML 模版)。 模版纪律(activity-sdk/src/view.ts):容器是 sandbox="" iframe,无脚本执行权、 样式必须自带;数据由基座推送时填(data-slot 取值、data-rows 逐行展开); SDK 给了 viewShell / kvCard / tableCard / noteCard / stagesCard 拼装件,照 activities/quote-intake/src/views/report.ts 用。一个局面一张模版(受理中/已分类/待放行)。

9.4 运行时的卡怎么到人(✅ 链路)

活动 issueWorkOrder/issueCard → card-rules 机检(裸调用拒发/字段齐备)→ 调人预算 环外扣账 → 落 sys.work_order / sys.chat_card → 到达面两路:收件人是负责人 → 直接长在任务对话流;他人 → 系统消息要素。人答卡 → 校验(§7.4)→ human 事件 → 任务 deliver → 引擎下一拍收裁决。在途验收先于一切:有未裁决的验收卡时, 回合原地等,不重发卡、不问模型(turn-runner.ts:274-287)。

10. 记忆与技能

  • 任务记忆:只增不减;memory.window(n) 取最近轨迹(n = declarations.harness.memoryWindow); 终态封卷后不能再追加;蒸馏钩子(distillHooks:on terminal / on milestone + prompt) 在封卷后跑,失败不掀翻终态。
  • 活动记忆:跨任务沉淀,四层;memory.recall({layers?, limit?}) 选摘;语义引擎不读 (它只进叙事面)。
  • 技能(manifest.ts:117-131):{id, label, applicability, method, crystallized[], knowledgeRefs[], version}。渐进披露:常驻上下文只有目录(id label:applicability), 正文 Agent 说要才加载 —— 一次塞全部正文会把局面淹掉。载方法不载权力:技能里的 结晶片段调工具,仍过活动自己的白名单,技能不带来新授权。

11. 开发流程与自测

11.1 本地开发(mock host)

createMockHost({contract, tools?, models?, ledger?, memory?}) → {base, recording, run}。 recording 十通道:views / htmls / says / workOrders / cards / toolCalls / intents / listeners / llmCalls / writes —— 断言活动这一回合做了什么。工具未 mock 回 {ok:false, code:'not-mocked'}(活动要能按失败路径走);模型未 mock 直接抛。

11.2 自测四段(照 activities/quote-intake/__tests__/activity.test.ts)

  1. 清单合规:过 activityManifestSchema;监听时限已配;L1/L2 判据可机检;禁区非空 + 调人预算 > 0;
  2. 案例模型:CMMN 双表示齐备;触达世界的项全标 manualActivationRule;末尾有收口验收项;
  3. DMN:逐规则求值断言(同输入必同结论、优先级、任意格);
  4. 入口:mock host 跑 run —— 各局面的 outcome、推了什么视图、说了什么话、守卫真的拦 (无脚本/裸调用/时限必配)。

11.3 安装两审与版本纪律

  • 静态审 11 code(static-audit.ts):signature-invalid / sdk-incompatible / schema-invalid / tool-contract-missing(⚠️ 待降级)/ model-name-unbound / dangling-listener / template-has-script / environment-capability / dynamic-eval / ledger-type-conflict / acceptance-level-misreported。任一 error 即拒装。
  • 回放审:历史成功样本现在必须仍过(regressions 硬门);首装无历史以样例集替代。
  • 版本纪律:安装恒新增、同版重装 no-op(改了内容必须升 packageVersion,否则静默 丢失 —— 实测踩过);卸载只置状态不删行;资产(业务模型/DMN/技能)播下一版不覆盖, 覆盖只能是显式治理回滚。
  • 白名单授权:装入 ≠ 能调 —— 授权是另一步(⚠️ 现只有种子常量表,第 12 章⑤)。

12. 待实现清单

按对四条产品语义的阻塞度排序。实现时逐条销账,销一条更新本表。

# 事项 现状(证据) 目标
① 写类意向落地断链(阻塞语义①) submitForRelease/precheck 无调用方(release-service.ts:119-141 全仓无引用);活路径 raiseAcceptance 绕过预检;land() 只执行 prechecked(:156)→ 意向永停 drafted,写类动作永不执行;验收卡 actions 恒空、criteria 恒为硬编码单条 L5(engines.ts:301);幂等键两套(engines.ts:360 vs precheck.ts:93-95) 提交放行走 submitForRelease(预检 → 意向置 prechecked → 已验证动作清单与契约 acceptanceCriteria 上卡);幂等键统一为 ${taskId}:${intentId}
② 工具未命中转人执行卡(阻塞语义③) 未注册抛 ToolNotFoundError(tool-invoker.ts:43);静态审 tool-contract-missing = error 拒装(static-audit.ts:108-111);工具契约无面向人的指导段 未注册 → 生成执行工单(卡面 = 工具 guide 段 + 输入数据 + 按输出 Schema 生成的回填表单,回传值即工具出参,幂等键沿用);静态审降级为 warn;工具契约加 guide 字段。未授权仍报错不转人
③ 验收报告修订链两端断 验收工单 fields 前端不渲染(work-order-report.tsx:75)、values 恒传 {}(chat-bubbles.tsx:500,519);驳回 UI 只有 redo,引擎把 redo/revise 塌成 reject(turn-runner.ts:432) fields 渲染 + values 回传(AI 拟值可改);驳回两式贯通到引擎
④ 受理分支误吞验收裁决 deliver 的受理分支判 awaiting-human && kind==='human' 不看 cardId(task-service.ts:262-264);「没法填」带缺口开跑的单再遇验收驳回,human 事件被误判成补全 受理分支按补全卡 cardId 匹配
⑤ 治理台白名单授权口 唯一入口是种子常量表(seed-mvp.ts:109-117),无 API 无 UI;安装不授权 治理台按活动配工具白名单(读走 read、改走 govern)
⑥ defineActivity 去 paradigm schema 已删,SDK 仍要求(define.ts:21);六包里还有 'flow' 字样 SDK 签名去掉;六包清理
⑦ dist 构建产物真编译 dist/manifest.json 是过期 flow 形状、index.js 是源码文本拷贝(债 M5-3) build-activities 真编译 + manifest 同步
⑧ 缺省单拍路径无工具总线 只有 JCAI_AGENT_OPEN_LOOP=1 走总线(engines.ts:464-491);缺省 tool 是自由字符串 工具调用逻辑实现时统一走总线(开环转正或单拍接总线)
⑨ preauthorizedActions 死代码 dispositionOf/ToolInvoker.disposition 无调用方;两包 manifest 里声明了但不生效 暂不启用:manifest 里不要写;将来要么接线要么删字段
⑩ SchemaValidator 双死依赖 task 与 process 都声明了 schemas 端口但不再调(宽进后退役) 清理端口与注入
⑪ 评价卡(rate)暂不做 schema 在(card.ts:424-438),链路未接 用户定:暂不做 —— 不设计、不接线

其余记录在案的小不一致(不阻塞,修到顺手时清):declarations.tools[].version 无消费者; contract.resources.tools 双份声明;replay 模式跳过白名单检查(tool-invoker.ts:58-61 在 loadContract 之前);注册路由造不出纯工具;工具 retries 声明了不重试; activity-registry/contract.ts:54「两维管理」注释陈旧;chat-bubbles.tsx:6 / work-order-report.tsx:6「当前未挂载」注释已过期(实际都已挂载)。