Archived
**资料文件全链**(用户:审核资料这一步要真产出 word/excel/md/html,前端能渲、点资料名能开)
接手时这一步一个文件都产不出来 —— S3 剧本白纸黑字写着「file_ref 本 MVP 阶段可留空」。
文件是台账那一行的投影,不另存一份:
· 声明 `file:{titleColumn,bodyColumn,formatColumn}`,它同时是授权面(没声明就点不开)
· 现造真字节 —— docx/xlsx 是自己打的 OOXML 包(zip-write:store+CRC32,零依赖)
· 读回来渲 —— 解的就是造出来那份字节,渲得出来才证明文件是真的
· 件上 `data-file-of` → 工作区二级界面(Word 纸面/Excel 网格/渲好的 md/沙箱 html)+ 下载原件
放行落地前把每一份冻进 `sys.task_asset_file`(迁移 0029):归档件点开的必须是签字那一刻
那一份,而资料落在主数据上、试跑单不落正式表 —— 不存这一份,收工后一个都点不开。
工作成果里「产出的资料」自成一摞。Markdown 富文本渲染顺带销账(仍零依赖)。
**Harness**
· 画面按「这一拍办完之后」在办的那一步挑 —— 从前拿拍首的局面决定拍尾的事:
收掉 S4、发出 S5 的卡,而屏上还摆着 S4 的只读件,人无处可填(墙要配路第四次现形)
· 验收「重做」真能退回重做,且打回后台账没动就不许再提交(软硬两段,不锁死单子)
· 立监听的授权面是清单声明;发卡的授权面是调人预算,不是计划项
· 读工具同参重调有兜底;空转写身后还有行动就不废串;finish 的 doneWhen 按写完后的草稿算
**界面**
· 翻件与翻页各归各位(从前二选一,多卡多页时页翻不动);没得翻写「—」不写 0/0
· 工作成果详情能翻份;表宽超出能横滑(E04/I01 抄版式时把 overflow-x 抄丢了)
· 查看面灰底滚过一屏变白 —— 两条独立的路都治了(沙箱内不同色 / 弹性子项不长个)
· 测试台「取不到」不再当成「还活着」
**活动**
E04 关系覆盖、I01 品牌内容各自补到验收线:判据全读基座数、判据指向的东西摆上卡面、
样例满载且名字 ≤14 字;提交前要裁的判据搬进说明书(模型会压拍,压过之后只有说明书还在)。
验收:e2e i01 4/4、e04 3/3 全部 released,资料在办中渲得出、放行后存了档点得开;
门禁 typecheck/lint/depcruise/1628 测全绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEJX8Z7CHejvwmDQyPQuBX
130 lines
12 KiB
Markdown
130 lines
12 KiB
Markdown
# module: loop-engine
|
|
|
|
职责: loop 范式:CMMN 子集解析、标准生命周期推进、前提求值与 Enabled 集发布、
|
|
DMN 确定性求值与命中行留痕、双向驱动(前推 × 后拉 × 交集导航)、
|
|
Agent 回路的回合驱动(三型行动)
|
|
不负责: Agent 的推理本身(经 `AgentPort`,Mastra 在 app/adapter)、放行与落地(见 release)、
|
|
终态命名(见 task)、活动记忆的存取(经 `MemoryRecorder`)
|
|
|
|
公开出口: index.ts
|
|
- 类型:`LoopEngineService` `CaseModel` `CaseRuntime` `EnabledSet` `Navigation` `AgentAction`
|
|
`TurnOutcome` `DecisionResult` `DecisionTrace` `Gap` `ItemState` `CaseState`
|
|
- 错误:`CmmnParseError` `IllegalTransitionError` `ForbiddenTerminalError`
|
|
`AutomaticOnWorldTouchingError`
|
|
- 端口类型:`LoopStateStore` `Sfeel` `TaskDataGateway`(含 `writable` 授权面)`AgentPort` `ToolGateway`
|
|
`CardGateway` `ListenerRegistrar` `MemoryRecorder` `TaskContext`
|
|
- 用例工厂:`createTurnRunner(deps): LoopEngineService`
|
|
- 纯函数:`parseCmmn` `parseXml` `publishEnabled` `navigate` `evaluateCriterion`
|
|
`transitionItem` `occur` `achieveMilestones` `reenter` `autoCompleteReady` `canTransitionItem`
|
|
`mayCompleteCase` `mayTerminateCase` `evaluateTable` `evaluationOrder` `deduceGaps`
|
|
- adapter:`createLoopStateStore`;Drizzle 表:`loopStateTable`
|
|
|
|
层级: L5(编码规范 §4)
|
|
依赖: @contracts;@shared/db;skill-engine(L4);port 注入:@sfeel / ledger / toolbox /
|
|
human-tool / event / memory
|
|
被依赖: task
|
|
|
|
不变量(AM 04 / 12.2):
|
|
- **CMMN 子集封顶**:`parseCmmn` 遇到表外构件**直接报错**,不静默忽略 ——
|
|
扩构件 = 改编码规范 §4 那一行 + 建 ADR
|
|
- **语义忠实**:迁移拓扑写死在 `ITEM_TRANSITIONS` / `CASE_TRANSITIONS`,
|
|
引擎不得私改;活动与模型不得发明状态
|
|
- **Enabled ≠ Active**:引擎推进到 Enabled 为止;进入 Active 永远出自 Agent 的 manualStart 并留痕
|
|
- **引擎零 LLM**:前提求值与 DMN 都走 `@sfeel`;语义性前提标「待裁量评估」交上层,
|
|
引擎**不代裁量、不代把关**
|
|
- **能算的不问 LLM**:DMN 输入齐备即确定性求值,同输入必同结论;命中规则行必留痕
|
|
- **结论不可静默覆写**:`isBindingConclusion` 标出约束性输入
|
|
- **三型行动**:每步行动属于且仅属于「调工具 / 调人 / 立监听」之一;
|
|
调人 = 立监听听人;AI 对世界只有触达(动作,落在放行)与等待(监听)两种姿势
|
|
- **一拍一串行动**(2026-07-30,`AgentTurn.actions`,封顶 `TURN_ACTION_CAP` = 6):
|
|
拍是按模型调用计费的 —— 一拍只许一个行动时,每个 write / finish / start 都单烧一遍
|
|
完整上下文,实测 A1 六步的活恰好 21 拍(其中 19 拍是该约束下的最短路径)。
|
|
收成一串后同一单 8 拍收口、token 减半。执行纪律引擎强制:按序执行,谁被拒停在谁那儿
|
|
(**前缀已生效不回滚**,理由喂回下一拍);`tool` 之后截断(出参没看见,后面的行动是
|
|
闭着眼计划的);挂起型(ask/human/listen/submit)落地即收拍;超封顶丢弃并留痕。
|
|
- **写台账草稿是过程写的唯一形态**(2026-07-30 新增 `write` 行动,铁律②)。
|
|
从前这条路**根本不存在**:`TaskDataGateway.draftWrite` 有端口无调用方,
|
|
Agent 干出来的结论一处也放不下 —— 后果不是少个功能,而是必交付物恒零行、
|
|
验收判据求值取不到值、出口载荷推不出字段:跑得再热闹,放行那刻什么也交不出。
|
|
- 一拍可写**多行**:一份 BOM 一次写完,不一行一拍各烧一次模型;
|
|
- 地址与列名的把关在装配层(它认识活动清单)—— 未声明的地址与列一律拒写,
|
|
越域写与越权调工具同罪;拒了抛错,由回合收成「行动被拒」喂回下一拍,引擎不崩;
|
|
- **写的授权面是台账声明,不是计划项**(2026-07-30):`itemId` 只是记账,
|
|
挂得上就挂(项活性化),挂不上(已完成 / 阶段 / 不存在)照写不拦,
|
|
留痕注明「未挂到计划项」。从前绑死在项生命周期上,死锁过一单:S5 收完才发现打标
|
|
没写全,所有能挂的项都 Completed —— 每拍补写都被拒,连拒五拍熔断,而数据本身合规;
|
|
- 写完把该项收了(未 Active 的就地过一下 Active)。
|
|
- **人工激活的项:模型显式动它 = 启动它**(2026-08-02 拆墙,`readyToWork`)。
|
|
从前它要求先发显式 `start`,不发就拒;提示词与拒回都写全之后实测仍熔断
|
|
(A1 一单连拒五拍,五次全是「S3_confirm 要先 start」)—— 那道墙保护不了任何东西:
|
|
start 是**模型自己的**动作,同一串连发即过,拦下只是逼它多演一步学不稳的礼仪。
|
|
人工激活真正的分量原样保留:**引擎不因哨兵满足替它启动**(哨兵只推到
|
|
Available/Enabled),**也不给 doneWhen 替人收**(自动收步跳过人工激活项)——
|
|
收它的只能是模型自己的 finish。`start` 仍是合法行动,只是可省。
|
|
- **里程碑前提达成即达成**(2026-07-30,`achieveMilestones`):CMMN 的里程碑没有执行体,
|
|
也没有谁去按它。从前只有 `onEvent(ref = 里程碑 id)` 会 occur,而系统里没有任何东西
|
|
发这种事件 —— 于是里程碑永远 `Available`:既是必做项(堵死 autoComplete),
|
|
又常驻「当下可为」与「目标缺口」,让 Agent 每拍都看见自己按不动的东西。
|
|
现在引擎在发布前自己收(达成留痕);**无前提的里程碑不自达成** —— 那是建模漏了前提。
|
|
- **事件监听器不是交付物,是等待岗**(2026-07-30):不进 `gaps`,也不算 autoComplete 的必做项
|
|
(从前只排除 `timerEventListener`,一个 `userEventListener` 就让案子永远判不齐)。
|
|
- **一拍 = 一个行动 + 一句交代**(2026-07-30 `AgentTurn`)。Agent 从前**不会说话** ——
|
|
会写台账、调工具、调人、立监听,但没有一型行动是「对人说一句」。
|
|
实测:A1 跑十几拍、写七行台账、调两次工具,负责人的对话区从头到尾空白,
|
|
他只看得见「多了一个任务」。「人只在责任点与放行点出现」不等于「过程中人什么都看不见」。
|
|
- 交代落 `narration`(它进对话区),与 `thought`(给自己留的痕)分开;
|
|
- **不是一型行动,是每一拍的附带**:先做成 `kind:'say'` 试过 —— 它占一整拍又不推进局面,
|
|
预算下的 Agent 理所当然跳过它,对话区照旧空白。让说话与干活抢拍,
|
|
等于把「必须说」设计成了「有空才说」;
|
|
- **先交代再动手**:人看到的是「我要去查这三个 PN 的历史价」,紧接着才是结果;
|
|
- `say.view` 可顺手换局面模版 —— 局面变了,人既要听到为什么,也要看到新画面。
|
|
- **卡死了交回给人**(2026-07-30 `stalledTurns` + `STALL_LIMIT=5`)。预算会烧完,
|
|
但那是二三十万 token 之后的事;这期间界面上只是「进行中」一动不动 —— 那不是在办,是卡死了。
|
|
连续 5 拍行动被拒就不再问模型(再问也是同一个答案),发执行卡把**拒绝理由原话**摆给人:
|
|
是它跑偏、是行动型少了一种、还是案例模型建得不对 —— 三种处置完全不同,人得看得见证据。
|
|
- **补全卡是对话卡,不是执行工单**(`ask` 行动 / `CardGateway.ask`)。
|
|
「按缺口清单追问补全」要的是:缺口清单摆在对话区、带一张表单,人就地填齐。
|
|
从前 Agent **发不出对话卡**(只能选执行 / 验收两种工单),SOP 上画着补全卡的那一步
|
|
只能拿执行工单顶 —— 形似而神非:派一张工单去问几个字段,收卡的人得进待处理、
|
|
办完再回来,重得不成比例。一个格子都没有即拒发(空表单等于没问)。
|
|
- **决策卡也是对话卡**(2026-07-31 `decide` 行动 / `CardGateway.decide`)。
|
|
与补全卡的分工:补全卡问**事实**(缺哪几个字段,你填);决策卡问**处置**(往哪走,你选)。
|
|
从前决策卡**只有 UI 没有 Agent 侧** —— 卡面在对话区画得出来,行动型里却发不出它,
|
|
于是所有岔路口都被写成补全卡:人拿到一张要自己敲字的表单,而不是两个可点的选项。
|
|
少于两个选项即拒发(一个选项的「选择题」是通知,不是决策)。
|
|
- **视图推送回报推没推上**(`ViewGateway.push` → `{pushed, templates}`)。
|
|
从前引擎每拍推一个**硬编码**的 `case-overview`,而活动声明的模版未必叫这个名字
|
|
(A1 的四张一个都不叫)—— 模版查不到就静默 return,**每一帧都被悄悄丢掉**,
|
|
工作区从头到尾空白,日志里连一个字都没有。静默失败让「没画面」看起来像「没产出」。
|
|
现在:要的那张没有就退到活动声明的第一张,换不上则如实记痕。
|
|
- **人答了什么要看得见**:`MemoryRecorder.window` 带回 `detail`,回传值(`detail.values`)
|
|
随轨迹进叙事面。不给的话「执行工单回传」这一行读起来像事情办完了,而人填了哪几个值
|
|
Agent 一个字看不见 —— 既写不进台账,也只能再问一遍。
|
|
- **终局唯放行与 Harness**:引擎不把案计划置 Completed —— 那是 release 的事;
|
|
`mayCompleteCase` 只对 release 为真,`mayTerminateCase` 只对 harness / owner / governance 为真
|
|
- **提交放行 = 发验收卡**(2026-07-28 三修①):submit / 判齐(autoCompleteReady)
|
|
不再返回 `submitted` 空转 —— 发**验收卡**、立监听、在途验收入状态信封
|
|
(`loop_state.pending`,迁移 0015);判齐重入不发第二张卡;裁决到达后由下一拍分岔:
|
|
pass → `finished` + `released: true`(选口随放行带回);**非 pass = 打回重做,
|
|
不是终局**(2026-08-22 修死:从前非 pass 也走 `finished` 分支,task 层把
|
|
「finished 未放行」重新挂成 awaiting-human —— 人已经说了「重做」,却再没有事件
|
|
叫醒它,一单就此卡死)——意见(`PendingAcceptance.comment`,onEvent 从裁决载荷
|
|
捕入信封)以「验收被打回重做:…」进轨迹,在途验收清掉,**同一拍照常问模型**;
|
|
刚被打回的那一拍不走判齐快道(否则判齐恒真的活动会在意见没人读时原样再发一张卡),
|
|
模型修订后重新 submit,新发的验收卡才是下一次裁决的对象
|
|
- **onEvent 只记不跑**(三修②):事件到达只登记 occur / 收裁决入信封,存快照即返;
|
|
推进(可能含一次模型决策)统一发生在 tick —— steps/activeMs/tokens 一个预算口径
|
|
- **任务轨迹喂回叙事面**(三修③):`memory.window(taskId)` 的最近轨迹进
|
|
【本任务最近轨迹】段,窗口大小 = 活动声明的 `harness.memoryWindow`,Agent 每拍不失忆
|
|
- **驳回是迁移不是状态**:`reenter` 以 repetitionRule 新实例重新入场
|
|
- **打回重做之后,没改过东西就不许再提交**(2026-08-23):`raiseAcceptance` 一处把关,
|
|
模型 submit 与判齐快道两条路共用 —— 打回之后台账没动过(写/撤都算)就拒;
|
|
软硬两段:头一次拒并指路(改台账 / 发卡问清楚要改什么),已因此拒过一次再提交就放行
|
|
(墙要配路,不锁死单子)。起因:打回那一刻案例模型的项常常全是 `Completed`,
|
|
模型除了原样重新 submit 无事可做,6 秒就把同一份成果递回来
|
|
- **立监听的授权面是活动清单的 `declarations.listeners`**(2026-08-23):
|
|
`ListenerRegistrar.declared(taskId)` 给出声明过的那几种,没声明过的类型当场拒。
|
|
与「写的授权面是台账声明」「调工具的授权面是白名单」同一条 ——
|
|
立了没人叫醒的监听不是等待,是失联(实测:模型在依赖断点上临场立 data 监听,
|
|
而那个活动一个监听都没声明,单子永久挂在 `awaiting-event`)
|