- 完善口径编辑、只读试运行、统一质量验证与执行语义指纹 - 增加可执行指标约束、有预算的主动调查和过程评测 - 提取发布检查、证据读取与起草服务,支持任务停止、重试和检查点恢复 - 增加版本传递影响分析,修复并发覆盖、敏感值遮罩、异常评分及页面状态问题 - 修复 SQLite 复合查询追溯和 microVM 可选依赖缺失处理 - 纳入当前设计文档及部署配置调整 验证:相关主回归 3822 项通过,4 项跳过、8 项预期失败;补充回归通过。 前端构建、文案检查及 git diff --check 通过。 全量检查仍有环境权限、PostgreSQL 可选依赖和既有部署配置、SQLite 文件基线差异导致的未通过项。
65 KiB
普通用户建应用:方案
设计文档 · 草案,待拍板 · 2026-10-15
- 本文档只定方案,不含代码改动。第五部分「实施组织」在本文签字后另行审批,再开工。
- 代码位置按 main@69c5c74(应用平台合入后)标注,三路只读调研逐条核过。
- 承接《应用平台与助手按需查看:总方案》(
docs/design/app-platform-and-look.md,下称「总方案」)。本文不改它,引用写作「总方案 §x.y」。- 文中出现的数据源名、表名、应用名、人名都是虚构的。本文不含本机开发环境的计数。
目录
- 摘要
- 第一部分 现状与问题
- 第二部分 关键决定(N1–N11)
- 第三部分 规格
- 第四部分 界面
- 第五部分 实施组织
- 第六部分 验证与验收
- 第七部分 风险与对策
- 第八部分 待拍板
- 附录 A 代码位置索引 · B 术语
摘要
问题。 应用平台已经能做相当复杂的应用:多级审批、会签、时限与升级、写回外部系统、看板、按新口径补算。但能做出来的只有懂编排的人。普通人卡在这几处:
- 必须先在画布上搭好并发布工作流,应用只能从已发布版本生成;
- 从问数据出发要跳四次:做成应用 → 提取工作流 → 去画布发布 → 回来做成应用;
- 满屏专家词:固定版本、处置、岗位绑定、视图来源、出具契约、口径卡、
cell(nodes.fetch, 0, 'gmv')、PT48H; - 发布前看不到真数,判断不了「这是不是我要的」;
- 两类最常见的需求做不了:上传文件,以及「填报登记」(每次提交存一行,再汇总着看)。
做法。 把「搭应用」改成「说需求 → 看蓝图 → 用真数试 → 确认」,用的就是项目的路线「模板定骨架、助手填关节」:
- 一条链路,两种终点(N1)。说需求、蓝图、模板填关节、试运行,两类人走同一条链路,只在最后一步分开:
- 搭建者:一次确认,发布成正式应用;
- 使用者:存成个人应用,只自己看得见、只读、跑探索运行,想给别人用就提交给搭建者审核发布。
- 应用蓝图(N2):用户的话和工作流、应用之间的中间件,用白话卡片呈现。卡片写的是看哪些数、谁发起谁审批、多久更新一次、要不要写回系统,用户逐张确认或修改。卡片的改动是确定性的操作,不是让模型重写。
- 场景模板(N3):首批五个——周期看板、审批流程、异常处置、填报登记、核对写回。骨架锁住,助手只能填模板声明的「关节」:取数的 SQL、口径卡的指标式;其余关节由蓝图确定性地填。
- 用真数试运行(N4):跑一次探索运行,草稿应用直接对着这次运行解析,印章写「试运行」。流程类的审批步骤派给试运行的人自己点,写入一律走测试环境或演练。
- 一次确认发布(N5):服务端按「发布单」编排——保存版本、发布工作流、建应用、绑岗位、建定时任务、授权、跑第一次——每步可续做。确认前给一页白话摘要;查出的问题分成「你来定」「助手可以修」「要找管理员」三类。
- 在页面上直接改(N6):应用页进入编辑状态后点哪改哪;改取数逻辑就回到蓝图,重走试运行和发布单,口径变不变用白话说清。
- 补两个能力缺口:
- 记录表(N8):平台托管、只许追加,用序号钉版本,按授权分行可见;
- 文件(N9):按哈希存,以引用进运行输入,可以交给 agent 读,也可以作为一期数据导入。
- 白话层(N10)和生成的耗时与稳定性(N11):所有检查问题有一份白话说法;补上问数据和画布两端「助手反问」的收尾。
已拍板(2026-10-14、10-15):
- 两类人都做,共用一条链路(N1);
- 填报登记要做行级可见(N8);
- 表单只许加字段,改字段或删字段就出新的表版本,旧记录照原样保留(N8);
- 试运行只给有管理权限的人看(N4);
- 先写设计文档,签字后实施;验收结束后实施(已结束)。
待拍板:见第八部分,共 8 项,每项附推荐。
第一部分 现状与问题
1.1 现在建一个应用要走的路
一个从零开始的看板应用,最少要按顺序做这些事,每步各自提交:
- 在画布上搭工作流并保存,形成版本快照;
- 发布工作流(受管级只有管理员能发);
- 建定时任务——必须在第 4 步之前,否则派生出来的视图不绑定时任务(
apps/derive.py:85-90); - 派生应用草稿(
POST /api/apps/derive,不落库); - 建应用(
POST /api/apps); - 绑岗位(
PATCH draft_bindings); - 授权(
PUT grants,立即生效); - 发布应用。
中途失败会留下半截状态:已发布但没有应用的工作流、按 cron 在跑的定时任务、停在草稿的应用。草稿应用的授权已经生效,使用者打开会得到 409「还没有发布」(api/apps.py:83)。这些步骤分散在画布、定时任务、应用编辑器三处。
1.2 普通人卡在哪
| 卡点 | 现象 | 出处 |
|---|---|---|
| 要会搭图 | 应用只能钉已发布版本 | 总方案 A1 |
| 术语 | 口径卡表达式、ISO 8601 时长、出具契约 JSON、视图来源、处置 | 端到端验收「普通用户视角」13 条 |
| 看不到真数 | 草稿预览只读钉住版本已有的封存运行,新应用没有 | apps/resolve.py:454-476 |
| 路径长 | 问数据做成应用要跳四次 | ChatPage.tsx:484-492 |
| 做不了 | 没有文件控件;没有平台托管的可写表 | apps/spec.py:35-38;data/table_versions.py:1-33 |
1.3 现有零件(直接复用)
-
生成管线:
Generation加Applier/Checker协议,新的生成目标插进来就能用(api/copilot_gen.py:567,look/base.py:87-110)。 -
按需查看:表、能力、工作流版本三类对象(
look/service.py)。 -
护栏:
- 润色边界
polish_problem(apps/ops.py:264); - 自动修复的
forbidden_changes(engine/autofix.py:1240)。
两者都是「只许改哪些」的确定性护栏,模板关节照这个写法。
- 润色边界
-
确定性派生:
derive_app(apps/derive.py)。 -
从运行提取工作流:
/copilot/from-run,带来源记录workflows.origin(api/copilot.py:2468-2550)。 -
数据目录:表的中文名、粒度、业务日期、列的含义和度量类型(
data/catalog.py:12-21),是把白话对到表的依据。 -
按运行解析:视图来源
instance已经能按给定的那次运行解析,不查类别和哈希(apps/resolve.py:541-546)。 -
所有运行都封存,探索运行也一样(
engine/runner.py:1013-1045);探索运行的写入走测试环境或演练(capabilities/endpoints.py:101)。 -
问数据对使用者的约束(
auth/chat_policy.py):- 节点和工具的白名单;
- 强加
MASK_RESULTS、READ_ONLY(api/runs.py:108-123); - 记忆隔离。
-
文件相关:
- 上传限额
read_upload(api/datasources.py:905-918); - 按哈希存原件
raw_store(data/raw_store.py:33-43); - 按配方导入「上传新一期」(
api/table_imports.py:160-486)。
- 上传限额
-
表版本链:快照的版本链,以及「改列不兼容」的判定
INCOMPATIBLE_KINDS(data/recipe_accumulate.py:38-41)。 -
数据版本:data_versions 进封存范围和印章(
engine/runner.py:242-262,apps/resolve.py:238-240)。 -
名录:
auth/directory.py,按登录方式核对人和组。 -
白话分类的依据:
- 检查问题已经有
code; - 自动修复有
kind: auto|choice|assist(engine/autofix.py:125-141); - 只能由人处理的有
APP_HUMAN_ONLY(api/copilot_gen.py:416-421)。
- 检查问题已经有
1.4 核实过的缺口
| # | 缺口 | 出处 |
|---|---|---|
| G1 | 没有带参数的模板,也没有「模板里哪些地方能改」的机制;GraphSpec 只有 nodes/edges/viewport/defaults;「以此模板新建」只是复制 | engine/schema.py:115;WorkflowPicker.tsx:354;api/workflows.py:249 |
| G2 | 应用视图只认正式运行;印章没有「探索」「试运行」 | apps/resolve.py:95,149,203-221 |
| G3 | 探索运行没有版本号和哈希,三种视图来源都取不到它 | engine/launch.py:166-174 |
| G4 | 跨步没有事务:工作流发布撤不回(只能退役),应用只能归档、不能删 | api/apps.py:273 |
| G5 | 检查问题只有技术说法,没有白话 | engine/governance.py:217-271 |
| G6 | 只问不改的一轮:问数据的 store 没有 ask 分支,可能一直停在「进行中」、问题不进历史;画布也不记这一轮 | store/chat.ts:1433;store/studio.ts:2149 |
| G7 | 使用者不能建应用;应用视图、印章、读权限都只认正式运行 | api/apps.py:191;auth/access.py:74-96 |
| G8 | 工件库只存 JSON;运行输入不能引用文件;入口提交是 JSON | core/artifact_store.py:45-115;engine/nodes/io.py:14-33 |
| G9 | 平台托管的表都是不可变快照,只能整期替换或按期累积;没有逐行追加,也没有行级可见 | data/table_versions.py:1-33;engine/masking.py |
| G10 | 没有按名字检索的口径库;复用口径只能逐级下拉选 caliber_from | engine/schema.py:1460;Inspector.tsx:1065 |
| G11 | 组件没有「隐藏」字段,只能删掉或写可见性表达式;改动要先保存才看得到 | apps/spec.py:97-107;apps/resolve.py:632 |
| G12 | 生成没有并行、没有提示缓存;问数据校验不过时前端整轮重来 | look/service.py:192;store/chat.ts:1241 |
1.5 理念对照
本方案不加新理念,把总方案的八条用到「创建」上:
- P1 照实说:试运行、探索、个人应用的数都在印章上写明来历;检查问题用白话说清是什么、为什么、谁来改。
- P2 留痕:
- 蓝图每一次改动都留痕;
- 发布单每一步的结果都留痕;
- 生成的工作流记下出自哪个蓝图、哪个模板。
- P3 不挡生成:模型失败时,模板照样能按蓝图确定性地实例化,缺的关节标「待填」交给人。
- P4 只读与遮罩:
- 个人应用只读、结果遮罩;
- 记录表按授权分行可见;
- 文件只给读得到那次运行或那一行的人。
- P5 一种说法:白话层只有一份,按问题编号映射。
- P6 模板定骨架:模板的节点和连线锁死,助手只填声明过的关节。
- P7 界面层不放逻辑:卡片的修改是对蓝图的确定性操作;页面上直接改走的仍是应用操作。
- P8 生效可查可对账:记录表的写入走动作(生效记录、幂等键);文件的哈希进封存。
不可回退决定的对照见 §2.12。
第二部分 关键决定
每个决定写四样:决定、理由、否决的做法、代价。
2.1 N1 一条链路,两种终点
决定
链路是:说需求 → 蓝图 → 选模板并实例化 → 试运行 → 终点。
- 起点三个,都落到同一个蓝图:
- 说说你要什么:开一段蓝图对话,助手先问清楚,再整理成卡片;
- 从场景模板开始:选一个模板,卡片按模板预置;
- 从已有的开始:从问数据的一轮回答(运行)开始,或者从一个已发布的工作流版本开始。前者用 from-run 提取,后者直接导入图,模板记为「自由形」。
- 终点两种:
- 搭建者:发布单(N5)一次确认,产出已发布的工作流、正式应用、定时任务、授权;
- 使用者:存成个人应用(N7),自己用。想给别人用就「提交审核」,搭建者打开同一个蓝图,走试运行和发布单。
理由:一条链路只维护一份,「问 → 建」的沉淀路径是现成的(P2)。使用者沉淀出来的东西不绕过发布门禁:个人应用永远是探索,正式应用只能由搭建者发布(不可回退决定:正式运行只从已发布版本发起)。
否决的做法
- 给使用者一个简化版画布:等于让使用者搭图,权限和门禁都要另开口子;
- 使用者的应用也走正式发布:让 user 角色产出承诺,违背角色分层。
代价:个人应用和正式应用是两种对象状态,界面要说清区别。
2.2 N2 应用蓝图 agentlab.blueprint/1
决定
- 定位:蓝图是用户的话和工作流、应用之间的结构化中间件,存成一个可变对象
Blueprint。它有:- 拥有者、可见范围(个人 / 共享);
- 状态:起草中 / 试运行过 / 已发布 / 个人应用 / 审核中;
- 来源:对话、模板、运行、工作流版本。
- 一张蓝图由几张「卡片」组成,每张卡片对应用户能理解的一件事:
| 卡片 | 内容 | 落到哪里 |
|---|---|---|
| 目的 | 一句话:这个应用解决什么问题、给谁用 | 应用名称、说明 |
| 场景 | 选定的模板(N3) | 骨架 |
| 看哪些数 | 每一项:白话名、对到哪个库哪张表(或复用哪张已发布的口径卡)、粒度、业务日期、筛选条件、单位 | SQL 关节、口径卡关节 |
| 多久更新 | 每天、每周一、每月 1 日……时区;统计哪一期(本周、上周、上月) | 定时任务、入口字段的模板值 |
| 谁来做 | 发起人、各步处理人(人、组、岗位)、几个人同意算通过、时限、超时给谁 | 人工节点配置、岗位绑定 |
| 填什么 | 表单字段:名称、类型、必填、上下限、选项、文件 | 入口表单、待办表单、记录表的列 |
| 写回哪里 | 目标能力(从能力登记里选)、字段对应、要不要人确认;能力没登记查询的,再定结果不明时谁去核实(只由人定,同「谁能看」) | 调用工具节点、应用的核实岗位 |
| 谁能看 | 使用、查看全部、管理三档的人和组 | 授权 |
-
每一项都带状态,照数据目录项的写法
{value, source, status}:source:用户填的 / 助手提的 / 模板默认 / 从目录或口径卡对到的;status:已确认 / 待确认 / 缺。
试运行前,所有「缺」都要补齐;「待确认」可以先试运行,但发布时必须确认。
-
助手只提议:输出
bp_*操作(bp_set、bp_add、bp_remove、bp_ask),由BlueprintApplier确定性落地。用户在卡片上改,走的也是同一组操作,只是source记成用户。 -
对到数据靠 look:助手看「看哪些数」时,先查数据目录(表的中文名、粒度、业务日期、列的度量类型),再查已发布工作流里的口径卡(N2 新增 look 类别
caliber,补 G10)。对到已有口径卡时优先复用(caliber_from),不重写指标式。 -
反问:缺的信息由助手用
bp_ask问,一次问一件事、给出候选(例如「销售额按下单日还是支付日算?」并附两个选项)。这一轮就此结束,回答是下一轮(G6 一并修好,N11)。
理由
- 卡片是用户能判断对错的颗粒度(P1);
- 模型产出的是提议,落地是确定性的(P7);
- 每一项都有来源和状态,不会把模型猜的当成用户说的(P2)。
否决的做法
- 让模型直接生成工作流再给用户看图:普通人看不懂图;
- 一问一答的长表单向导:问题顺序固定,答不上来就卡住;
- 蓝图只存在前端:刷新就丢,也没法交给搭建者审核。
代价:多了一种对象和一套操作;模板变了,蓝图的卡片要跟着兼容(蓝图记下模板版本)。
2.3 N3 场景模板 agentlab.template/1:骨架锁住,关节可填
决定
- 模板是一份登记,和组件目录同一种做法(总方案 A2):
- 后端
app/apps/templates/,每个模板一个模块; - 开发者扩展时,模板定义、实例化函数、测试三样都要给,缺一样启动即报错。
- 后端
- 一个模板包含:
- 骨架:带占位的图(节点和连线固定);
- 关节:骨架里允许填的位置,每个都有类型、来源、校验;
- 应用骨架:页面布局,用派生规则加模板自带的组件排布;
- 卡片映射:蓝图的哪张卡片填哪些关节;
- 说明和适用条件:给用户和助手看。
- 关节分两种:
-
确定性关节:由蓝图直接填,不经模型。包括:
- 表单字段 → 入口节点 fields;
- 处理人和策略 → 人工节点 assignee/policy/due/on_timeout;
- 金额线这类规则 → 分支节点的比较条件,只许「字段 比较 常量」;
- 更新频率 → 定时任务;
- 写回目标 → 调用工具节点的 tool 和参数映射。
-
生成关节:只有两类交给模型——
- 取数的 SQL(
sql); - 口径卡的指标式(
metric_expr,只许cell()取格加口径卡的算术)。
模型在
TemplateApplier下工作,只收fill_joint操作,而且只收这个模板声明过的关节 id,别的一律拒绝并照实交回(照polish_problem的写法)。 - 取数的 SQL(
-
- 护栏:实例化后的图和骨架逐节点比对,只许关节位置不同,用
forbidden_changes的同一套比对。之后照常走 validate、GraphChecker(SQL 猜列、数据源范围)和发布前检查。 - 来源记录:生成的工作流写
workflows.origin = {blueprint_id, template: "<id>@<version>", run_id?},补上「只写不读」的现状:WorkflowOut 和版本历史显示「出自蓝图 X、模板 Y」。 - 首批五个(详见 §3.3):周期看板、审批流程、异常处置、填报登记、核对写回。外加「自由形」:从已有的运行或工作流导入,没有关节,改图只能回画布。
- 模型失败时(P3):生成关节留空、标「待填」,蓝图卡片上提示「这一项助手没填上,请补充或换个说法」;确定性关节照样落地。
理由
- 模板定骨架、助手填关节,正是项目路线;
- 关节越少,生成越快、越稳(N11),检查也越有效;
- 锁骨架就锁住了治理关注的结构:出具契约、人工节点、写入位置。
否决的做法
- 模板只是一张样例图,复制了让助手随便改:等于自由生成,护栏全靠事后检查;
- 用模板引擎在 JSON 上做字符串替换:类型和校验都丢了;
- 一个模板覆盖所有场景、用大量开关:组合爆炸,测不过来。
代价:每个模板要写实例化和测试;用户的需求落在模板之外时,只能走「自由形」,或者由搭建者新增模板。
2.4 N4 用真数试运行
决定
-
试运行是一次探索运行,跑的是蓝图实例化出来的、还没发布的图,来源记为
origin_kind=trial、origin={blueprint_id}。 -
草稿应用对着这次运行解析:
- 新增解析模式
resolve_page(draft=True, trial=run); - 草稿里所有视图来源(最近封存、按需、本实例)都指向这次运行;
- 钉住项记成
{workflow_id, version: null, graph_hash: <这次运行的图哈希>}。
沿用
instance分支「按给定运行解析」的路径(apps/resolve.py:541-546)。不改agentlab.app/1的视图来源种类,试运行只是解析时的旁路参数。 - 新增解析模式
-
印章:
- 来源写「试运行」;
- 注明「数据是真的;这不是正式结果,发布后才会按定时任务出数」;
- 出具档位照实算;没封存的照实写。
趋势只有一个点时写「试运行只有一次,没有趋势」,不报错(补
_series拿不到钉住项就报错的问题,apps/resolve.py:813)。 -
输入:
- 看板类:按「多久更新」卡片算出最近一期(例如上一 ISO 周),用户可以改;
- 流程类:由用户填一份示例表单。
-
流程类的试运行:
- 人工步骤派给试运行的人自己(来源记为
trial),按钮写「以试运行身份同意/驳回」; - 写入一律走测试环境;没有测试环境的演练,不发送;
- 时限按真实设置显示,但试运行不触发升级——调度跳过
origin_kind=trial的待办。
- 人工步骤派给试运行的人自己(来源记为
-
谁能看:只有对这个蓝图有管理权的人(拍板项):搭建者看自己的、有 manage 的看共享蓝图的;个人应用的试运行只有拥有者看。试运行是探索运行,遮罩按看的人的角色照常处理。
-
发布时对齐:发布单按图哈希判断「发布的版本就是试运行过的那张图」:
- 一致:钉住项直接换成版本,节点 id 一一对上;
- 不一致(试运行之后又改过):摘要里写明「发布的内容和最近一次试运行不同」,建议再试一次,不强拦。
理由:先看真数再决定(P1);不发布就不产生正式运行(不可回退决定);写入不出测试环境(总方案 A5)。
否决的做法
- 先发布一个隐藏版本再试:版本表里满是试验版本,退役和口径升版的判断都被污染;
- 用假数据预览:普通人恰恰要看真数才判断得了。
代价:试运行要花一次模型或查询的钱;流程类的试运行要人点一遍。
2.5 N5 一次确认发布:发布单
决定
- 发布单(
PublishPlan)是服务端的编排对象:先算出要做的每一步和白话摘要,用户确认后按序执行,每步的结果记下来;失败了停在那一步,修好后从那一步接着做。 - 步骤(顺序就是为了让中途失败时留下的状态无害):
- 保存版本:按图哈希判断,已有相同的版本就复用;
- 全量预检(只读):工作流发布前检查 + 按将要生成的版本跑应用检查 + 定时任务预览 + 名录核对。任何 error 都在这一步停下,什么都没写;
- 发布工作流(受管级只有管理员);
- 建定时任务(停用状态),这样派生时视图能绑上它;
- 建应用、派生、写岗位绑定;
- 发布应用:钉住项升版时按蓝图里的处置;第一次发布没有处置;
- 启用定时任务;
- 授权:最后一步,避免使用者先看到一个打不开的应用;
- 跑第一次(看板类默认勾选):立即运行一次,应用一打开就有数。
- 白话摘要:每一步一句「会发生什么」,例如:
- 「发布『门店周报』:此后每周一 09:00 按这一版自动出数」;
- 「张三、李四可以使用;财务组的人负责审批」;
- 「口径变了:以前的周报按新口径重算(recompute),旧数据留底」。
- 问题分三类,按问题编号走白话层(N10):
- 你来定:岗位没绑人、处置没选;
- 助手可以修:autofix 的 auto/assist、应用检查里不在
APP_HUMAN_ONLY的; - 要找管理员:受管级、降级、内网端点、能力治理。
- 补偿:
- 第 3 步之后的失败不撤回已发布的工作流(发布本来就撤不回),摘要照实说「工作流已发布,应用还没有」;
- 停用的定时任务在应用发布前不会跑,不产生无主的正式运行;
- 应用停在草稿时不授权,使用者看不到。
- 权限:执行发布单的人要有其中每一步的权限,预检时就按角色算好(例如搭建者发不了受管级,摘要里直接说)。
理由:一次确认、照实摘要(P1);每一步留痕(P2);中途失败的状态无害、可续做。
否决的做法
- 一个大事务把八步包起来:跨定时任务、应用、工作流的事务在 SQLite 单进程下做得到,但出错后整体回滚会吞掉已经通过的检查结果,用户得从头来;而且发布动作本身就带审计,不应该回滚;
- 前端按顺序调现有接口:刷新页面就丢进度,权限和补偿都在客户端,不可靠。
代价:多一个对象和一张表;现有各接口的校验要能被发布单按「只检查不提交」的方式调用。
2.6 N6 在页面上直接改
决定
- 改呈现:
- 有 manage 的人在应用页点「编辑」进入编辑状态,ViewRenderer 叠一层编辑层:按
data-node选中组件(apps/ViewRenderer.tsx:75-84),就地改文字、隐藏、上下挪、删除。 - 落地走现有的应用操作
app_update、app_move、app_remove,自动存草稿,然后按草稿重新解析。
- 有 manage 的人在应用页点「编辑」进入编辑状态,ViewRenderer 叠一层编辑层:按
- 加「隐藏」:组件加
hidden: true字段(补 G11),解析时跳过、编辑状态下照常画成半透明。这和可见性表达式不是一回事:可见性是按运行时条件显示,隐藏是作者不要它。 - 加一张数据卡片:候选从钉住版本里列,用同一份派生规则
_results(口径卡指标、报告、取数节点、趋势),新接口GET /api/apps/{id}/candidates。用户挑一个,就落成一个app_add。 - 改取数逻辑(「销售额要去掉退款」):
- 不在页面上改,回到蓝图:改「看哪些数」卡片 → 重新实例化 → 试运行 → 发布单;
- 发布单在摘要里用白话说清口径变了、以前的数怎么处理(处置),沿用总方案的口径升版规则。
- 改完要发布:呈现的改动只进草稿,用户点「发布」走应用发布(不动工作流),沿用现有规则。
理由:普通人改看得见的东西最直接(P1);改取数会动口径,必须经过试运行和处置(不可回退决定:口径升版须处置)。
否决的做法
- 页面编辑器允许拖拽任意布局:像素级设计不是目标(总方案 §1.3);
- 页面上直接改 SQL:绕过试运行和处置。
代价:编辑状态和查看状态要共用一套渲染,交互细节要打磨。
2.7 N7 个人应用
决定
-
对象:
- 个人应用是
scope=personal的 App,拥有者是创建它的使用者; - 它的图不进工作流表(使用者没有工作流权限),存在蓝图上,带图哈希;
- 没有应用版本,改了就是改了,每次刷新按当前的图跑。
- 个人应用是
-
能用的东西,与问数据同一份白名单(
auth/chat_policy.py):- 节点:input、llm、agent、report、output、retrieve、transform、branch、metrics、merge;
- 工具:只读内置工具和只读数据源查询;
- 运行时强加
MASK_RESULTS、READ_ONLY; - 不能有人工节点、不能写入、不能调写入能力。
能用的模板因此只有:周期看板、异常处置的「只看」部分、填报登记的「只看自己的」汇总。
-
刷新:
- 打开时用最近一次成功的刷新;
- 点「刷新」发起一次探索运行(
origin_kind=personal_app),身份是拥有者; - 受每日上限约束,和按需视图共用设置项
apps.on_demand_daily_limit,按人计。 - 是否允许每天自动刷新,见待拍板 Q4。
-
视图来源:新增
exploratory,只在个人应用里合法——取这个应用、这个拥有者、这张图哈希下最近一次成功封存的探索运行。印章来源写「探索 · 未经发布审核」,档位照实写。 -
谁看得见:只有拥有者。管理员在设置里看得到「谁有几个个人应用」,可以归档,但不看内容(P4)。
-
提交审核:
- 拥有者点「提交审核」,选一位搭建者,或者交给搭建者组;
- 开一条待办(种类
review,派给对方); - 搭建者打开时得到这个蓝图的一份共享副本(原个人应用不动),在副本上走试运行和发布单;
- 发布后通知提交人,提交人自己决定个人应用留不留。
-
开关:管理员设置
apps.user_personal_apps,默认关(同问数据的开关,待拍板 Q3)。
理由:使用者能自助(产品目标);个人应用永远是探索、只读、遮罩(P4,不可回退决定:没发布的图只能跑探索);共享必须经搭建者发布(承诺由搭建者签)。
否决的做法
- 给使用者开「个人工作流」:工作流表的权限、门禁、定时任务全要另开一套;
- 个人应用允许分享给别人:等于绕过发布门禁。
代价:视图来源多一种、印章多一种;个人应用的图和工作流的图是两种存法。
2.8 N8 记录表(填报登记)
决定
-
存储:每个记录表一个平台托管的 SQLite 文件(
data/records/<表 id>.db),只许追加。- 每行带系统列:
_seq(自增序号)、_id、_submitted_by、_submitted_at、_run_id(哪次运行写的)、_supersedes(更正时指向被更正的行)、_schema_version。 - 业务列由「填什么」卡片生成。
- 每行带系统列:
-
作为数据源:登记成
origin=records的只读数据源,看板照常用db_query__<名字>查,数据目录照常起草。 -
写入只经动作:
- 新增平台内置能力
records.append(效果:写入;允许无人值守,因为平台自己的表、幂等键就是动作键); - 填报登记模板的骨架是:入口 → [可选的审批] → 调用工具节点
records.append→ 成果; - 生效记录、幂等、审计全部沿用总方案 A5,一次提交只落一行,重放不会重复写。
- 新增平台内置能力
-
更正不改原行:更正是一次新的提交,带
_supersedes。默认查询看「当前有效」的行(没被更正过的),更正历史可查。 -
钉版本:
- 运行开始时,在 data_versions 里记下这张表的高水位
_seq; - 查询时连接上建同名的 TEMP VIEW(SQLite 解析不带库名的表名时,先找 temp 库),只露出
_seq ≤ 高水位的行。
于是同一次运行查几遍都是同一份数,印章写「数据截至第 N 条」,按需缓存键也带上它(P8)。
- 运行开始时,在 data_versions 里记下这张表的高水位
-
行级可见:
- 同一套临时视图再加一条按人过滤:
use只看_submitted_by = 自己,observe和manage看全部(拍板项); - 过滤在查询工具的连接上做,模型和 SQL 都绕不过去。
- 看板的汇总:看板读的是正式运行的封存结果,正式运行以服务身份跑、看到全部行,所以汇总是全量的。这是作者的选择,发布单摘要里写明「使用者能看到全部提交的汇总数」。
- 逐行列表:用新组件
records_list,每次打开按看的人实时过滤(同instance_list),不走封存。
- 同一套临时视图再加一条按人过滤:
-
表单变了:
- 只加可选字段:
ALTER TABLE ADD COLUMN,同一表版本; - 改类型、删字段、加必填字段:出新的表版本(新文件
<表 id>.v2.db),旧记录照原样保留。不兼容的判定沿用INCOMPATIBLE_KINDS。 - 应用版本钉住表版本;看板要跨版本汇总时,由模板生成
UNION ALL的视图,列对不上的补空。
- 只加可选字段:
-
遮罩:业务列可以在「填什么」卡片里标「敏感」,进数据源的遮罩列表(
mask_columns)。
理由:只追加加序号,在单进程 SQLite 下就能做到可复现(P8,不可回退决定:不上 Postgres、不加节点锁);写入走动作,审批和对账都是现成的;行级可见在连接层做,模型和 SQL 都绕不过去(P4)。
否决的做法
- 每次提交出一个新快照(复用 table_versions):一次提交一个文件,量一大就扛不住;
- 允许原地改行:数就不可复现了;
- 行级可见交给模型在 SQL 里加 where:靠不住。
代价:
- 多一种数据源和一个写入服务;写入走单进程锁,和导入的锁分开;
- 高频填报(每秒多次)不是目标场景,写在 README 里。
2.9 N9 文件
决定
- 存储:
- 按 sha256 内容寻址存,复用
raw_store的做法:data/files/<前 2 位>/<sha>,文件只读(0444); - 新表
files记 id、sha256、原名、大小、类型、上传人、上传时间、绑定的运行或记录(可空)、用途; - 大小限额沿用
max_upload_mb;类型白名单见待拍板 Q5。
- 按 sha256 内容寻址存,复用
- 上传两步走:
POST /api/files带上下文(应用 id 加入口,或者待办 id),先上传,拿回引用{file_id, name, size, sha256, type};- 再提交表单。表单字段
type: object、x-widget: file,值就是引用。
- 提交时绑定:服务端核对引用属于提交人、还没被别的提交用过,然后把它绑到这次运行(一次性),引用连同 sha256 进运行输入,随封存一起固定(P8)。没绑定的上传 24 小时后清理。
- 怎么用文件:
- 交给 agent 或报告读:新增模板函数
file_text(input.附件),运行时按类型抽文本(PDF、Word、表格、纯文本),沿用知识库上传的抽取,有长度上限,超出照实截断并标出; - 作为一期数据导入:平台内置能力
table_import.new_period(source, file),按这个上传源已有的配方导入,效果是写入(数据变了),走动作和确认; - 附在记录上:记录表的文件列存引用。
- 交给 agent 或报告读:新增模板函数
- 下载:
GET /api/files/{id},只给读得到绑定的那次运行、或那一行记录的人(复用can_read_run和记录的行级可见)。文件不做遮罩,界面在上传处写明「能看到这条记录的人都能下载附件」。 - 使用者:个人应用里可以上传文件交给 agent 读(只读),不能导入。
理由:内容寻址加封存,证据站得住(P8);上传和提交分开,表单仍是 JSON,人工节点的表单也能用;读权限跟运行走,没有新的权限面(P4)。
否决的做法
- 文件放进工件库:工件库只存 JSON,按运行归属,上传时还没有运行;
- base64 塞进表单值:撑爆输入和事件流。
代价:多一张表、一个清理任务;抽文本的质量受文件格式影响,照实标「抽取不完整」。
2.10 N10 白话层
决定
- 新增
app/plain.py:问题编号 →{title 一句话, why 为什么要紧, who: you|assistant|admin, action 怎么办}。覆盖:- 工作流发布门禁(
contract.*、governed.*、action.*、process.*); - 应用检查(
app.*); - 发布单的步骤失败。
- 工作流发布门禁(
- 没登记白话的问题照旧显示技术说法,并在检查脚本里报「有问题编号没有白话」,防止遗漏。
- 前端的发布摘要、蓝图卡片、试运行页都用白话;画布和应用编辑器保留技术说法,另加一行白话。
who的判定照这三样:自动修复的kind、APP_HUMAN_ONLY、需要管理员的问题编号。
理由:P5 一种说法;P1 照实说给听得懂的人。
否决的做法:让模型临场解释——每次说法不同,还可能说错。
代价:每加一个问题编号都要写白话,检查脚本会提醒。
2.11 N11 生成:耗时与稳定性
决定
- 调用次数的预算:
- 起草蓝图:1 次主调用,加最多 2 次查看;
- 填关节:每一组独立的关节(例如两组互不相干的指标)各 1 次调用,并行执行(新增,补 G12),各自最多 1 次查看;
- 其余全部是确定性的。
- 模型:蓝图和关节都用设置里的助手模型;首次使用时提示「推荐用快速模型」。验收按真实模型记录每一步的耗时(第六部分 W4)。
- 反问收尾(补 G6):
- 问数据:store 加 ask 分支——这一轮记成「助手反问」,问题进历史;输入框提示「回答助手的问题」;
- 画布:这一轮照样记下,问题进历史;
- 蓝图对话:问题和候选画成可点的选项。
- 问数据校验不过时:不再整轮重来,改为把问题交回同一轮的自查修正(Checker 已有),省一次完整生成。
理由:普通人等不了几分钟(验收里有模型在画布写 SQL 首个操作要 486 秒);反问是蓝图的核心交互,不能丢。
否决的做法:一次调用生成整张图加应用——慢、不稳、难修。
代价:并行调用增加瞬时并发,受模型服务的限流约束,失败时退回串行。
2.12 和不可回退决定的对照
| 不可回退决定 | 本方案 |
|---|---|
| 正式运行只从已发布的不可变版本、按 graph_hash 发起 | 试运行和个人应用都是探索运行;发布单发布后才有正式运行;按图哈希对齐试运行和版本 |
| 口径升版必须处置 | 改取数回到蓝图,发布单要求处置,摘要用白话说明 |
| 叙述层不做算术 | 关节里的指标式只许 cell() 加口径卡算术;卡片上的数来自绑定 |
| 没发布的图只能跑探索 | 个人应用永远是探索;印章写明 |
| 不上 Postgres、不加节点锁 | 记录表单文件 SQLite、只追加、单进程锁;临时视图钉版本 |
第三部分 规格
3.1 数据模型
新表
| 表 | 主要字段 |
|---|---|
blueprints |
id、name、owner、scope(personal / shared)、status(drafting / trialed / published / personal / in_review)、spec(agentlab.blueprint/1)、template_id、template_version、source(conversation / template / run / workflow_version,带 id)、workflow_id、app_id、last_trial_run_id、graph(个人应用的图)、graph_hash、copied_from(审核副本指向原蓝图)、created_at、updated_at |
blueprint_events |
blueprint_id、seq、actor、verified、op(bp_*)、before、after、at——蓝图的留痕 |
publish_plans |
id、blueprint_id、actor、steps(每步 kind、status、result、error_code、at)、summary(白话摘要)、level、status(planned / running / stopped / done)、created_at |
files |
id、sha256、name、size、mime、uploaded_by、uploaded_at、context(app_id、entry 或 task_id)、bound_run_id、bound_record(表 id、_id)、purpose |
records_tables |
id、app_id、name、source_id(登记的数据源)、version(表版本)、columns(列定义 + 敏感标记)、created_at;表版本另起一行,旧版本只读保留 |
新列(走 _COLUMN_MIGRATIONS)
apps.scope(默认 shared)、apps.owner、apps.blueprint_id;runs.origin_kind新增取值trial、personal_app(总方案 X3 的列表加两项,仍只能由服务端写);tasks.kind新增review(个人应用提交审核);- 组件 spec 加
hidden(apps/spec.py的组件模型,extra=forbid下显式加字段)。
3.2 蓝图 agentlab.blueprint/1
{
"format": "agentlab.blueprint/1",
"purpose": {"value": "每周看各门店销售额、订单数和退款率", "source": "user", "status": "confirmed"},
"template": {"id": "periodic_board", "version": 1},
"data": [
{"id": "gmv", "label": {"value": "销售额"}, "unit": {"value": "元"},
"table": {"value": {"source": "shop", "table": "orders"}, "source": "catalog", "status": "proposed"},
"measure": {"value": "sum(amount)", "source": "assistant", "status": "proposed"},
"date": {"value": {"column": "paid_at", "rule": "按支付日"}, "source": "catalog", "status": "confirmed"},
"filter": {"value": "不含已退款", "source": "user", "status": "confirmed"},
"reuse": null}
],
"cadence": {"value": {"preset": "weekly", "weekday": 1, "time": "09:00", "tz": "Asia/Shanghai", "period": "last_iso_week"}},
"people": [], "fields": [], "writeback": null,
"access": {"use": [{"kind": "group", "name": "门店"}], "observe": [], "manage": []},
"joints": {"sql_main": {"status": "filled", "by": "assistant"}, "metric_gmv": {"status": "pending"}}
}
- 每一项都是
{value, source, status};source取 user / assistant / template / catalog / caliber;status取 confirmed / proposed / missing。 measure是白话或简单聚合的提议,不是最终表达式;最终的 SQL 和指标式在关节里,由TemplateApplier落地并经过检查。reuse:对到已发布口径卡时写{workflow_id, version, node_id, metric_id},实例化成caliber_from。
操作:bp_set(path, value)、bp_add(path, item)、bp_remove(path)、bp_ask(question, options[])、bp_template(id)。路径用 JSON 指针。模型和用户共用这组操作;BlueprintApplier 按卡片的白名单校验(比如 people[].policy.kind 只能是 any / all / quorum)。
3.3 模板 agentlab.template/1 与首批五个
模板定义(后端模块):
id, version, label, description, applies_when(给助手看的适用条件)
cards: 用到哪些卡片、各自的约束
skeleton(bp) -> GraphSpec # 带关节占位
joints: [{id, kind, node_id, field, from_card | generated, check}]
app_skeleton(bp, graph) -> agentlab.app/1 # 在 derive_app 的结果上按模板排布
schedule(bp) -> 定时任务参数 | None
tests: 每个模板至少一张完整蓝图的实例化 + 检查 + 运行
首批五个
| 模板 | 骨架 | 生成关节 | 确定性关节 | 应用页 |
|---|---|---|---|---|
周期看板 periodic_board |
入口(统计期)→ 取数(tool)→ 口径卡 → 成果(指标出具) | SQL、指标式 | 统计期字段、定时任务、出具契约的必需指标 | 印章、指标块、趋势、明细表、「按条件查看」(按需) |
审批流程 approval |
入口(表单)→ [按金额线分支] → 人工(一级或多级)→ [写回] → 已通过 / 已退回 | 无 | 表单、金额线、处理人与策略、时限与升级、写回目标 | 发起按钮、我发起的、实例进度、待办 |
异常处置 exception_handling |
定时:入口 → 取数(异常清单)→ 分支(有 / 无)→ 人工(负责人填处置说明,表格附在待办上)→ [复核] → 成果 | 异常的 SQL | 负责人、时限、复核策略 | 异常数指标、本期异常表、处置进度 |
填报登记 records |
入口(表单)→ [审批] → records.append → 成果;另配一张汇总看板(周期看板的骨架,查记录表) |
汇总的 SQL、指标式 | 表单与表结构、审批、行级可见 | 填报按钮、我的记录(records_list)、汇总看板 |
核对写回 reconcile_writeback |
定时:取数 → 口径卡(核对差异)→ 人工复核(确认要写回的)→ 写回 → 成果 | 取数的 SQL、差异的指标式 | 复核人、写回目标与字段对应、确认 | 差异指标、待复核清单、写回记录 |
- 每个模板都能被个人应用用到的部分(只读)标出来:周期看板全部、异常处置去掉人工步骤只看清单、填报登记只看汇总和「我的记录」。
- 「自由形」:从已有运行或工作流版本导入,没有关节,蓝图只有目的、谁能看、多久更新三张卡片;要改图只能回画布(搭建者)。
3.4 关节类型
| kind | 填什么 | 谁填 | 检查 |
|---|---|---|---|
sql |
调用工具节点的 args.sql |
模型(look 表) | 只读守卫、SQL 猜列、数据源范围、目录检查(GraphChecker) |
metric_expr |
口径卡一个指标的 expression |
模型 | 只许 cell()、口径卡算术、比较;静态追到原始数据(engine/lineage.static_stops) |
fields |
入口或人工节点的表单 | 蓝图「填什么」 | agentlab.form/1 校验 |
assignee / policy / due / on_timeout |
人工节点配置 | 蓝图「谁来做」 | 流程检查(engine/lint_process.py)、岗位派生 |
threshold |
分支条件「字段 比较 常量」 | 蓝图 | 表达式解析器,禁止算术 |
capability |
调用工具节点的 tool 和参数映射 | 蓝图「写回哪里」 | 能力登记、写入只在调用工具节点(总方案 A5)、参数按声明类型 |
contract |
成果节点的出具契约(必需指标) | 蓝图「看哪些数」 | 出具契约检查(含指标出具) |
TemplateApplier.apply(op) 只收 fill_joint {joint, value},joint 必须是这个模板声明的生成关节;TemplateChecker.issues() 跑上表的检查加骨架比对,交回模型改(最多 2 轮,沿用 Generation 的修正阶段)。
3.5 试运行解析
POST /api/blueprints/{id}/trial {inputs?}:实例化 → 保存草稿图 → 发起探索运行(origin_kind=trial)→ 返回 run_id;结束后蓝图状态记 trialed、last_trial_run_id。GET /api/blueprints/{id}/preview?page=…&run=…:派生(或沿用)应用草稿,调resolve_page(draft=True, trial=run):- 所有视图来源解析到这次运行;趋势、按需视图只给这一个点;
- 印章
from = 试运行、trial = true、notes 加「这不是正式结果」; - 流程组件(实例进度、待办)对着这次运行;待办卡按钮写「以试运行身份…」。
- 权限:蓝图的 manage(共享)或拥有者(个人)。
3.6 发布单
POST /api/blueprints/{id}/publish-plan {level}:只读,返回{steps[], summary[], problems[{code, plain, who, fix?}], ok}。POST /api/publish-plans/{plan_id}/run:按步执行;每步结束写steps[i];某步失败时状态 stopped,返回白话原因。POST /api/publish-plans/{plan_id}/resume:修好后从停下的那一步接着做;已完成的步骤按结果里的 id 核对,不重做。- 每一步调用现有服务函数(发布、定时任务、派生、应用发布、授权),不经 HTTP;预检阶段这些函数以「只检查」方式调用。
- 审计:发布单的开始、每步、结束各写一条(
publish_plan.*),和各对象自己的审计互相引用。
3.7 记录表
- 写入服务:
records/store.py。append(table, values, run_id, actor, action_key):在单进程锁内校验列、写一行、返回_seq和_id;- 同一个
action_key第二次调用返回第一次的结果(幂等)。
- 能力
records.append:- 内置登记;参数是
{table, values}; - 效果是写入,允许无人值守,查询能力是
records.lookup(action_key)。
- 内置登记;参数是
- 查询连接:
data/engine.py打开origin=records的源时,在连接上建 TEMP VIEW:每张业务表一个同名视图;- 视图条件是
_seq <= 高水位,加_superseded = 0,加行级过滤; - 高水位来自运行的 data_versions;行级过滤取自运行的发起人和这个应用的授权:正式运行以服务身份跑、不过滤;
records_list按看的人过滤。
- 表版本:「填什么」卡片变化时,按
INCOMPATIBLE_KINDS判定,兼容的就 ADD COLUMN,否则新建版本文件并登记一行records_tables。
3.8 文件
- 接口:
POST /api/files(multipart;context 必填)→ 引用;GET /api/files/{id}(按绑定对象核对读权限);DELETE /api/files/{id}(只能删自己还没绑定的)。
- 控件:
x-widget: file,字段type: object,属性{file_id, name, size, sha256, type};accept、max_mb进x-file。前后端校验同一份:apps/forms.py、lib/schemaForm.ts。 - 模板函数
file_text(ref, max_chars=…):运行时抽文本,结果记进运行事件(字数、是否截断),抽取的文本按工件存,进证据。 - 清理:调度循环每小时清一次 24 小时前上传、没有绑定的文件。
3.9 个人应用
POST /api/blueprints/{id}/save-personal:校验图满足chat_policy(节点、工具、无人工、无写入),建或更新scope=personal的 App,图存在蓝图上。POST /api/apps/{id}/refresh(个人):发起探索运行(origin_kind=personal_app,身份是拥有者,强加MASK_RESULTS、READ_ONLY),受每日上限约束。- 视图来源
exploratory:只在scope=personal的应用里合法(应用检查拦),解析时取最近一次成功封存的personal_app运行。 POST /api/apps/{id}/submit-review {to}:开review待办;搭建者处理时调POST /api/blueprints/{id}/copy得到共享副本。can_read_run加一条:拥有者能读自己个人应用的运行(auth/access.py:74-96)。
3.10 接口一览
| 接口 | 权限 |
|---|---|
POST /api/blueprints(起点:对话 / 模板 / 运行 / 版本) |
builder;个人:user + 开关 |
GET/PATCH /api/blueprints/{id}(PATCH 收 bp_* 操作数组) |
拥有者或共享蓝图的 manage |
POST /api/copilot/blueprint-stream |
同上 |
POST /api/copilot/fill-joints-stream |
同上 |
POST /api/blueprints/{id}/trial、GET …/preview |
同上 |
POST /api/blueprints/{id}/publish-plan、`/api/publish-plans/{id}/run |
resume` |
POST /api/blueprints/{id}/save-personal、/api/apps/{id}/refresh、/submit-review |
user + 开关,拥有者 |
POST /api/blueprints/{id}/copy |
builder |
GET /api/templates |
登录即可 |
GET /api/apps/{id}/candidates |
manage |
POST/GET/DELETE /api/files… |
见 §3.8 |
GET /api/apps/{id}/records(records_list 用) |
use(只看自己的)/ observe |
3.11 事件
- 运行事件:
run.origin的 kind 加 trial、personal_app;file.extracted(文件抽文本:字数、是否截断);records.appended是动作事件(action.*)的一种,不另起。 - 蓝图事件存
blueprint_events,不进运行事件。
3.12 权限矩阵(开登录时)
| 操作 | admin | builder | user |
|---|---|---|---|
| 建共享蓝图、试运行、发布单(已发布级) | ✓ | ✓ | ✗ |
| 发布单(受管级、降级) | ✓ | ✗ | ✗ |
| 建个人应用、刷新、提交审核 | ✓ | ✓ | 开关开着时 ✓ |
| 审核个人应用(得到共享副本) | ✓ | ✓ | ✗ |
| 看别人的个人应用内容 | ✗ | ✗ | ✗ |
| 上传文件 | 有对应入口或待办的人 | 同左 | 同左 |
| 下载文件 | 读得到绑定的运行或记录的人 | 同左 | 同左 |
| 记录表逐行查看 | 全部 | 全部(有 observe) | 只看自己的(use) |
关登录时全部放行,个人应用的开关照样生效(关着就没有入口)。
3.13 新增检查
- 蓝图(
bp.*,蓝图卡片和发布单预检里显示):bp.missing:缺必填卡片项;bp.unconfirmed:待确认项未确认就发布;bp.data_unmapped:看哪些数没对到表或口径;bp.people_unbound:岗位没有人;bp.reconciler_unbound:写回的能力没登记查询,结果不明时没人核实(同上,只挡发布;发布单上应用检查说同一件事的app.reconciler_unbound和核实岗位的app.slot_unbound不再重复列);bp.template_mismatch:卡片内容超出模板能力。
- 模板(
tpl.*):tpl.skeleton_changed:实例化的图改了骨架;tpl.joint_unfilled:生成关节没填;tpl.joint_refused:模型想改非关节的位置。
- 应用:
app.view_source_personal:exploratory来源出现在共享应用里,error;app.hidden_bound:被隐藏组件是唯一绑定某个视图的组件,warning。app.records_rows_exposed:看板的表格绑定的查询直接列出记录表的逐行(没有聚合),warning。正式运行看得到全部行,这样等于所有使用者都能看到别人的记录,绕过了行级可见;逐行查看请用records_list。
- 个人应用:
personal.policy,图里有使用者不能用的节点或工具(沿用chat_graph_problem的说法)。 - 白话:以上全部登记进
app/plain.py。
第四部分 界面
4.1 新建应用
- 入口:应用首页右上「新建应用」。使用者在开关开着时也有这个入口,只能建个人应用。
- 三个起点卡片:
- 说说你要什么:大输入框加例句;
- 从场景模板开始:五张模板卡,每张写适用场景、会产出什么、要准备什么;
- 从已有的开始:最近的问数据回答,加上已发布的工作流(后者只给搭建者)。
- 问数据的「做成应用」按钮:改为「做成应用(蓝图)」,直接带着这次运行进蓝图,不再跳画布发布框。
4.2 蓝图页
- 左侧:对话。助手的反问画成可点的选项,「这一轮在等你回答」常显。
- 右侧:卡片列,每张卡片头上标状态(已确认、待确认、缺)。卡片里每一项都可以点开改,改完标来源「你」。
- 对到数据的项:显示对到的表、中文名、粒度、业务日期,带一句「为什么对到这张表」,来自 look 和目录。
- 底部:「用真数试一次」(缺项时置灰,写清缺什么);试过之后才有「发布」(搭建者)、「存成我的应用」(使用者)。
4.3 试运行页
- 就是应用的样子,顶部横幅写「试运行:数据是真的,但这不是正式结果」,配试运行的输入和「换一期再试」。
- 流程类:右侧列出试运行里的待办,自己点同意或驳回;写入步骤标「测试环境」或「演练」。
- 出错时用白话说哪一步、为什么,加「交给助手修」或「回到卡片改」。
4.4 发布摘要
- 一页:每一步一句白话。问题分三栏:你来定、助手可以修、要找管理员。
- 确认按钮写清结果,例如「发布并每周一 09:00 自动出数」。
- 执行时逐步打勾;停下时写在哪一步、为什么、修好后「接着做」。
4.5 页面上直接改
- 应用页右上「编辑」(manage)。
- 编辑状态下,组件悬停描边,点中出小工具条:改文字、隐藏、上移、下移、删除。
- 页底「加一张数据卡片」,弹出候选列表,按口径卡、报告、明细、趋势分组。
- 顶部「改取数」回到蓝图。
- 改动自动存草稿,顶部写「有未发布的改动」和「发布」。
4.6 个人应用
- 应用首页「我的应用」分组,卡片角标「个人」。
- 印章写「探索 · 未经发布审核」,顶部有「刷新」(写明今天还剩几次)和「提交审核」。
- 审核中写状态;审核发布后写「已有正式版本:去看看」。
4.7 新组件和控件
- 文件控件:拖放或点选;显示文件名、大小、上传进度、类型和大小限制;上传失败照实说。
records_list:列表、筛选、按时间排序;每行可以展开看全部字段和附件;被更正过的行显示「已更正」,可以展开看历史;使用者只看自己的,标题写「我的记录」。
4.8 文案与检查脚本
- 文案:
termsApp.ts新增蓝图、模板、试运行、发布单、个人应用、记录、文件各组说法;后端白话层app/plain.py。 - 新检查脚本:
check-blueprint:起点、卡片、反问、试运行、发布摘要;check-personal:使用者建、刷新、提交审核;check-records:填报、行级可见、更正、表版本;check-files:上传、绑定、下载权限。
- 要更新的:
check-apps(编辑状态、隐藏、候选)、check-chat(反问收尾、做成应用改道)、check-studio(反问记录)。 - 检查脚本的新约束:每个问题编号都要有白话。
第五部分 实施组织(供下次审批,这一步不执行)
-
分支:从最新 main 开集成分支
feat/normal-user-apps,所有包在同一分支、同一工作区。 -
第 0 步契约骨架:编排者一次提交,包括:
- 新表和新列、
agentlab.blueprint/1和agentlab.template/1的 pydantic 模型; - 关节类型和操作的白名单、
app/plain.py的框架; - 新路由的桩(501)、
records.append和table_import.new_period的能力登记; - 前端类型和接口桩、占位路由。
之后冻结热点文件:
db/models*.py、db/base.py、core/events.py、typesApp.ts。 - 新表和新列、
-
一个 workflow,六个包,每包一个完整代理,然后一个统一复核、一个集中修复:
| 包 | 范围 |
|---|---|
| B1 蓝图与模板(后端) | Blueprint 对象和操作、模板登记和五个模板、实例化和骨架比对、BlueprintApplier/TemplateApplier 与 Checker、两个生成入口、look 新类别 caliber、并行填关节 |
| B2 试运行与发布单(后端) | 试运行解析(trial 旁路)、派生自图、发布单的预检 / 执行 / 续做、白话层的内容、反问收尾的服务端部分 |
| B3 记录表与文件(后端) | 记录表存储与连接层的临时视图、records.append、表版本、records_list 解析、文件存储 / 接口 / 绑定 / 清理、file_text、table_import.new_period |
| B4 个人应用(后端) | scope 与拥有者、exploratory 视图来源与印章、刷新与上限、提交审核与副本、can_read_run、开关 |
| F5 创建链路(前端) | 新建应用三个起点、蓝图页、试运行页、发布摘要、问数据「做成应用」改道、反问在问数据和画布的收尾 |
| F6 页面编辑与新组件(前端) | 编辑层、隐藏、候选、文件控件、records_list、个人应用的界面 |
- 合并顺序:B1 → B2 → B3 → B4 → F5 → F6,编排者做集成修正。
- 验证分层(沿用):代理只跑相关测试和 tsc、check-copy;完整关口由编排者在合并后跑一次。
- 真实模型验收和端到端验收:都在关口之后跑。
- 合入 main 之前:先问用户。
第六部分 验证与验收
-
W1 看板(搭建者,从一句话开始):「每周一看各门店上周销售额、订单数、退款率,不含退款单」。
- 反问一次(按下单日还是支付日);
- 卡片对到正确的表和业务日期;
- 试运行出真数,点数字追到单元格;
- 发布摘要一页,确认后 9 步全部完成,应用打开就有数;
- 从说完需求到看到试运行结果,记录用时和模型调用次数。
-
W2 审批(搭建者,从模板开始):报销审批,金额线 5000,财务组全员同意,48 小时升级给总监,通过后写到假外部系统。
- 试运行里自己点完审批,写入走测试环境;
- 发布后真实走一单;
- 中途故意让岗位没绑人:发布单停在预检,白话说「谁来做」缺人。
-
W3 填报登记:门店每日报损。
- 两位使用者各提交几条,彼此看不到对方的记录;观察者看全部;汇总看板是全量的;
- 更正一条:原行保留,汇总按更正后的算;
- 改表单:加一个可选字段不出新版本;把数字字段改成文字,出 v2,旧记录还在。
- 同一次运行查两遍,数一样(高水位)。
-
W4 真实模型:本机助手模型、deepseek-v4.1-flash、glm-5.3,跑 W1、W2 的蓝图和关节,记录:
- 调用次数、查看次数、首个操作时间、总用时;
- 反问是否发生、关节检查的修正轮数。
目标:
- 起草蓝图 ≤ 3 次调用;
- 填关节在快速模型上一分钟内完成;
- 卡片和关节出错时,用户能看懂、能改。
-
W5 个人应用(使用者):
- 开关打开后,使用者从问数据的一次回答建「我的退款周报」;
- 刷新受每日上限约束;印章写「探索」;结果遮罩;
- 提交审核后,搭建者在副本上发布,提交人收到通知;
- 管理员看不到个人应用的内容。
-
W6 文件:
- 报销单附发票 PDF,agent 读出金额;
- 门店上传本周报表 Excel,作为一期导入(需确认);
- 不能读那次运行的人下载 403;没绑定的上传 24 小时后被清理(用缩短的时间测)。
-
关口:后端全量 pytest、tsc、check-copy、泄露扫描、
check-all --stacks 2(含新增的四个检查)、check-auth;真浏览器端到端按 W1–W6 截图复核。
第七部分 风险与对策
| 风险 | 对策 |
|---|---|
| 蓝图对错数据(白话对到错的表) | 对到的项默认「待确认」,卡片上写为什么对到这张表;试运行看真数;数据目录越完整越准,卡片上提示补目录 |
| 模型慢或不稳 | 关节少、并行、确定性兜底(P3);验收按真实模型记录耗时 |
| 模板覆盖不了需求 | 「自由形」加搭建者回画布;模板登记可扩展;收集落在模板外的需求 |
| 发布单中途失败 | 步骤顺序让失败无害;可续做;摘要照实说停在哪 |
| 记录表并发写 | 单进程锁、事务短;README 写明不面向高频写入 |
| 行级可见被绕过 | 在连接层建临时视图,模型和 SQL 都看不到底表;测试覆盖直接写 main.<表> 的 SQL 被只读守卫拒 |
| 看板把记录逐行列给所有人 | 正式运行看全部行是设计如此;应用检查对「逐行列出记录表」的表格给警告(app.records_rows_exposed),发布摘要写明使用者能看到什么 |
| 个人应用变成「影子正式应用」 | 永远探索、印章写明、不能分享;共享只能经审核发布 |
| 文件泄露 | 下载按绑定对象核对读权限;界面写明谁能下载;不做公开链接 |
| 白话说错 | 白话层只有一份,按编号映射,检查脚本防遗漏 |
第八部分 待拍板
| # | 问题 | 推荐 |
|---|---|---|
| Q1 | 记录表的存储 | 平台托管 SQLite、只追加、序号高水位钉版本、连接层临时视图做行级可见(N8) |
| Q2 | 填报提交走不走工作流 | 走:模板里 records.append 写入节点,可以加审批,沿用动作的幂等和对账 |
| Q3 | 使用者建个人应用的开关 | 管理员开关,默认关(同问数据) |
| Q4 | 个人应用的刷新 | 只手动刷新,加每日上限;不做定时自动刷新(自动刷新等于给使用者开定时任务) |
| Q5 | 文件的类型和大小 | 白名单:PDF、Word、Excel、CSV、图片(PNG/JPG)、纯文本;大小沿用 max_upload_mb(默认 50MB);未绑定的 24 小时清理 |
| Q6 | 首批模板 | 五个:周期看板、审批流程、异常处置、填报登记、核对写回,外加自由形 |
| Q7 | 流程类试运行的审批 | 派给试运行的人自己点;写入走测试环境或演练;不触发超时升级 |
| Q8 | 发布单里「跑第一次」 | 看板类默认勾选,流程类不跑 |
附录 A 代码位置索引(main@69c5c74)
| 位置 | 内容 |
|---|---|
backend/app/api/copilot_gen.py:567 |
Generation;:589 起草、:593 修正、:38 修正轮数 |
backend/app/look/base.py:22-27,87-110 |
查看上限;Applied / Applier / Checker 协议 |
backend/app/api/copilot.py:1679 |
/generate-stream;:1744 只问不改的一轮;:2364 /app-stream;:2468-2550 from-run 与来源记录 |
backend/app/apps/ops.py:264 |
polish_problem(护栏的写法) |
backend/app/engine/autofix.py:1240 |
forbidden_changes(骨架比对的写法) |
backend/app/apps/derive.py:85-90 |
派生时视图绑定时任务 |
backend/app/apps/resolve.py:82-108,454-476,541-546,203-221,813 |
最近封存的规则;草稿解析;按运行解析;印章来源;趋势无钉住项报错 |
backend/app/apps/spec.py:35-42,47,82,97-107 |
表单控件、视图来源、组件模型 |
backend/app/engine/launch.py:95-176 |
运行类别的判定 |
backend/app/auth/chat_policy.py:25-73 |
使用者能用的节点和工具 |
backend/app/api/runs.py:108-123 |
使用者运行的限制 |
backend/app/auth/access.py:74-96 |
can_read_run |
backend/app/data/catalog.py:12-21 |
数据目录项的形状 |
backend/app/data/raw_store.py:33-43 |
按哈希存原件 |
backend/app/data/table_versions.py:1-33,344-360 |
表的版本模型和存放 |
backend/app/data/recipe_accumulate.py:38-41 |
不兼容的列变化 |
backend/app/engine/runner.py:242-262,1013-1045 |
data_versions 的固定;封存 |
backend/app/api/workflows.py:427-503 |
发布 |
backend/app/api/schedules.py:461,scheduler/service.py:39-56 |
建定时任务;模板变量 |
backend/app/api/apps.py:186,203,251,370,474,273 |
建应用、派生、草稿、发布、授权、归档 |
frontend/src/store/chat.ts:1241,1433 |
问数据整轮重来;没有反问分支 |
frontend/src/apps/ViewRenderer.tsx:75-84 |
组件包在 data-node 里 |
frontend/src/pages/ChatPage.tsx:479-523 |
问数据的「做成应用」 |
附录 B 术语
- 蓝图:用户的需求被整理成的一组白话卡片,是生成工作流和应用的依据。
- 卡片:蓝图里用户能判断对错的一件事(看哪些数、谁来做……)。
- 场景模板:一类应用的骨架,带可填的关节。
- 关节:模板骨架里允许填的位置;确定性关节按卡片填,生成关节由助手填。
- 试运行:跑一次还没发布的图,用真数看应用的样子;不是正式结果。
- 发布单:一次确认发布的编排,逐步执行、可续做。
- 个人应用:使用者自己建、只有自己看得见的应用,永远是探索。
- 记录表:平台托管、只许追加的表,填报登记的每次提交是一行。
- 高水位:一次运行开始时记录表的最大序号,这次运行只看到这之前的行。
- 白话层:问题编号到白话说法的唯一映射。