Web Owner Console前端未写,先设计读模型
为WebOwnerConsole设计readmodel,先定义结构化DTO而非直接复用QQ文本输出,确保数据契约稳定、安全边界清晰,避免前端依赖文案解析。当前只读设计覆盖Dashboard、任务、审批等页面,不涉及写操作,为后续前端开发提供可靠数据基础。
上一轮做完了MainAgent owner侧runtime与QQ adapter的拆解,一个更清晰的方向自然浮现:既然`/agent`已经越来越像主人控制台,那是不是可以开始做Web Owner Console?但这次没有直接写前端,也没有接HTTP API,而是先做了一份更枯燥但更关键的设计——Web Owner Console read-model。
简单说,就是先决定未来Web控制台要读哪些数据、这些数据应该长什么样、从哪里来,以及哪些东西绝对不能因为有了Web入口就绕过安全边界。这篇文章记录的是,为什么没有直接复用现在的QQ文本输出,又为什么要提前设计结构化DTO。
## 当前系统已经有一个QQ版主人控制台
现在`/agent`已经不只是一个“问项目上下文”的命令了。它可以做很多主人侧只读查询:
```
/agent 看看最近错误
/agent 角色卡列表
/agent 模型配置
/agent 访问控制
/agent RAG 索引详情
/agent MainAgent 最近观测
/agent RootGraph 最近观测
```
也可以看任务和审批:
```
/agent 任务工作台
/agent 下一步
/agent 任务详情 最新
/agent 审批状态
/agent 审批详情 最新
```
写操作也已经有审批门控:
```
/agent 删除摘要 41
/agent 清空图片缓存
/agent 把群 123456 加入群白名单
/agent 确认 最新
```
这些能力组合起来,已经很像一个“QQ里的Owner Console”。但它的输出形态是QQ消息。也就是说,现在后端大体是这样:
```
结构化数据 / 服务结果-> QQ 文本 formatter-> str-> matcher.finish(reply)
```
这对QQ很合适——一条消息就应该是一段人能直接读的文本。但如果未来要做Web Owner Console,直接拿这段文本塞进网页,就会埋下很多坑。
## 现有QQ输出的三种后端形态
先把现有QQ输出形态梳理一遍,它其实不是完全乱拼字符串,背后有三类来源。
第一类是`list[str]`。比如:
```
role_card_list_lines()
model_config_status_lines()
access_overview_lines()
rag_index_detail_lines()
main_agent_observation_lines()
root_graph_observation_lines()
```
这些函数返回一组展示行,然后`owner_read_runtime`用换行拼成QQ文本。
第二类是Graph execution的`reply_text`。比如:
```
run_diagnostics(...)
run_memory_retrieval(...)
run_memory_admin(...)
```
Graph里已经聚合了状态,但最后给`/agent`的仍然是一段可发送的文本。
第三类是任务和审批formatter。任务和审批底层其实已经有结构化对象——`AgentTask`、`AgentTaskEvent`、`AgentApproval`——但QQ入口会立刻把它们送进formatter:
```
format_agent_task_list(tasks) -> str
format_agent_task_detail(task, events, approvals) -> str
format_agent_approval_list(approvals) -> str
format_agent_approval_detail(approval, task, events) -> str
format_agent_task_workbench(...) -> str
```
这些formatter产出的内容适合QQ,例如:
```
Agent 审批详情卡 #19
审批ID:#19
状态:待确认
任务ID:#18
工具:owner_write_command
风险:medium
原因:owner write command requires approval
输入摘要:...
```
但它不是Web后端接口。Web如果想做表格、筛选、排序、状态标签、详情页、按钮禁用态,就不应该去解析这段文本。
## 为什么不能直接复用QQ文本
最省事的做法当然是:Web页面请求一个接口,后端调用现有`/agent`逻辑,拿到`str`,前端原样展示。这个方案第一天看起来很快,第三天就会开始难受。
### 1. 文案变化会变成接口破坏
比如QQ文本里有一行:“审批ID:#19 [待确认] 任务ID:#18 owner_write_command / medium”。如果Web前端靠解析这行拿`approval_id`、`status`、`task_id`,那么以后只要把文案改成“审批 #19:待主人确认,关联任务 #18”,前端解析就坏了。文案应该是展示层,不应该成为数据契约。
### 2. Web页面天然需要结构
任务列表不是一段文本,它至少需要:`task_id`、`title`、`goal_preview`、`status`、`status_label`、`created_at`、`updated_at`、`latest_event_summary`、`pending_approval_ids`。审批列表也不是一段文本,它需要:`approval_id`、`task_id`、`task_title`、`tool_name`、`risk_level`、`reason_preview`、`status`、`created_at`、`expires_at`、`decided_at`。这些字段到了前端以后,才能自然变成表格列、筛选条件、状态badge、详情链接、风险颜色、空状态、禁用按钮、原因提示。如果后端只给一段文本,前端要么只能做一个大文本框,要么就开始写脆弱的正则解析。
### 3. 安全脱敏需要字段级控制
审批详情里有`tool_input_json`,它可能包含工具参数、记忆内容、摘要ID、名单、目标模型配置相关信息。在QQ里,可以用短文本摘要展示:“输入摘要:{"command": "delete_session_summary", "summary_id": 41}”。但Web以后可能会有折叠详情、复制按钮、JSON预览。这时如果后端只给一段formatter文本,脱敏边界就很难说清楚。更稳的形式是read model里明确写:`tool_input: {preview_json: str, redacted: bool, truncated: bool}`。这样前端知道自己看到的是预览,不是完整原文。
### 4. 未来按钮状态不能靠前端猜
Web Console以后可能会展示审批确认按钮。但按钮能不能点,不能靠前端根据文案猜:“如果文本里有‘待确认’,按钮就可点”。这是危险的。正确做法是后端read model给出只读的actionability metadata:`can_approve`、`can_reject`、`resume_enabled`、`blocked_reason`、`future_operation_only`。v0阶段这些字段只用于描述未来UI状态,不执行操作。真正实现时,它们也必须由后端根据审批状态、tool registry和`approval_resume_enabled`计算。前端不应该自己判断某个工具能不能恢复。
## DTO在这里到底是什么意思
这里说的DTO,不是说马上要写一堆复杂的类。它更像是一个明确的数据契约:这个页面需要什么字段,这些字段来自哪里,哪些字段是展示摘要,哪些字段是脱敏预览,哪些字段只是未来操作提示,哪些能力当前明确不可用。
比如任务列表可以设计成:
```
OwnerConsoleTaskList
generated_at
filters
total_visible
rows
boundary
OwnerConsoleTaskRow
task_id
title
goal_preview
status
status_label
result_preview
created_at
updated_at
latest_event_kind
latest_event_summary
pending_approval_ids
next_action
```
审批详情可以设计成:
```
OwnerConsoleApprovalDetail
generated_at
approval
tool_input
task
recent_events
actionability
boundary
```
这类结构化DTO有几个好处:第一,前端不关心QQ文案;第二,后端可以继续复用已有查询函数;第三,安全边界可以被字段显式表达;第四,后续HTTP API、CLI、甚至新的Owner Console入口都可以共享同一个read model。
## read model不是数据库表
这里还有一个容易混淆的点:read model不等于新表。这次P2.6明确不改数据库schema。任务和审批已经有持久化数据:`AgentTask`、`AgentTaskEvent`、`AgentApproval`。read model做的是组装:从`agent_tasks`查询任务和审批,从`DiagnosticsGraph`拿诊断摘要,从访问控制对象拿黑白名单,从配置和角色卡读取设置摘要,从owner runtime service复用现有只读依赖。也就是说,read model是给界面看的“读模型”,不是新的存储模型。这能避免为了做一个页面就急着加表。对于一个还在演进中的Agent项目,这很重要。
## 页面范围为什么要分层
这次设计里,把Web Owner Console v0页面分成两层。
第一层是v0必须清楚的:Dashboard、Tasks、Task Detail、Approvals、Approval Detail、Diagnostics、Access Control。这些页面最能验证前面Runtime service解耦的价值,因为它们主要依赖:任务/审批持久化查询、`owner_agent_runtime`、`owner_read_runtime`、diagnostics只读能力、access control只读能力。
第二层是v0浅层快照:Memory、Settings。Memory很重要,但它牵涉MemoryRAG、MemoryAdmin、摘要长期记忆、隐私正文、ProjectDoc RAG隔离。Settings也很重要,但它牵涉模型配置、base_url脱敏、API Key是否配置、角色卡列表、功能开关、Vision/TTS/MemoryRAG配置。如果一上来把这两个页面做深,P2.6会失控。所以v0只设计浅层快照,不做修改能力。
## 为什么现在不写前端
直接写前端当然更有成就感。但对这个项目来说,现在先写前端反而可能把错误的后端形态固定下来。如果今天先做一个页面,为了快,很可能会这样接:Web -> 调用现有QQ文本输出 -> 前端展示文本。然后明天想加表格,就开始解析文本;后天想加按钮,就开始根据文本判断状态;再后天想加脱敏,就发现原始数据和展示文案已经混在一起了。
所以宁愿先慢一步:先定义read model,先确认页面范围,先写清只读边界,先写清未来审批操作不能绕过现有链路。这样后续真正写Web时,前端接的是稳定数据结构,而不是一段“刚好能看”的QQ文案。
## 最关键的安全边界:Web不能绕过审批链路
Web Owner Console最容易犯的错误,是把“有按钮”误解成“可以直接调用函数”。比如未来审批详情页里有一个确认按钮,危险做法是`Web button -> clear_image_cache()`、`Web button -> add_access_item()`、`Web button -> delete_session_summary()`。这相当于Web入口绕过了现有审批恢复机制。
正确路线应该是:Web approval decision -> 与`/agent`确认/拒绝相同的审批决策服务 -> `decide_agent_approval(...)` -> `resume_agent_approval(...)` -> approval resume tool registry -> `approval_resume_enabled=true`检查 -> `owner_write_runtime` -> agent task event幂等记录。也就是说,Web未来可以成为审批入口,但不能成为绕过审批的写入口。
P2.6 v0更保守:只读,不确认审批,不拒绝审批,不恢复工具,不调用`owner_write_runtime`。但设计文档里仍然写了未来升级路线,因为安全边界最好在设计阶段就写清楚,而不是等前端按钮出现后再补。
## 未来模块为什么先只给建议名
设计文档里提了两个未来建议名:`owner_console_read_runtime.py`和`owner_console_read_models.py`。但这次没有创建代码文件。原因是P2.6还只是设计阶段。现在真正要确定的是边界:Web read model层不依赖MessageEvent,不调用`matcher.finish`/`bot.send`,不执行写操作,不解析QQ文本。至于最后是一个文件还是两个文件,后续实现时可以再根据代码量决定。更在意的是这层不要消失。如果没有这层,Web很容易直接接到QQ adapter或formatter上,那前面做Runtime service解耦的价值就被浪费了。
## 当前P2.6的结果
这次P2.6最终落地的是一份设计文档:`docs/web-owner-console-read-model-design.md`。它主要写了这些内容:Web Owner Console v0定位、现有QQ文本输出后端形态、页面地图、每个页面需要的数据、read model结构草案、现有Runtime service如何复用、暂不做的能力边界、从只读到审批操作的升级路线。
这次仍然不做:不写前端,不接HTTP,不做登录,不新增数据库表,不改变`/agent`,不改变普通聊天,不改变审批恢复,不改变MemoryRAG,不改变Diagnostics,不改变QQ命令行为。从功能角度看,它“什么也没新增”;从架构角度看,它把下一步怎么接Web Owner Console说清楚了。
## 一点工程感受
做Agent项目时,很容易被“下一步做个界面”吸引。界面很直观,也很有反馈。但如果后端还没有数据契约,界面会反过来逼你做很多脆弱连接:解析文本、猜状态、复制业务判断、绕过安全链路、展示未脱敏内容。这次想避免这个方向。
对这个项目来说,Web Owner Console的第一步不是按钮,也不是页面,而是read model。希望未来每个入口都很清楚:QQ adapter负责QQ,Web adapter负责Web,owner runtime负责主人侧业务运行时,read model负责结构化只读数据,write runtime必须在审批恢复链路之后才执行。这样系统能力可以继续长,但边界不会跟着糊掉。
## 下一步
如果P2.6设计确认稳定,后续可以小步实现:第一步,新增`owner_console_read_runtime.py`,只做只读builder;第二步,优先实现Tasks/Approvals/Dashboard;第三步,Diagnostics、Memory、Settings先保留summary_text/display_lines过渡;第四步,等read model稳定后,再考虑HTTP API;第五步,最后才是Web前端。
这个顺序看起来慢,但它更适合一个长期运行的Agent系统。因为想要的不是“能打开一个网页”,而是:每个页面背后都有清楚的数据契约,每个写操作背后都有审批链路,每个入口都知道自己不能做什么。这也是为什么选择先做结构化DTO,而不是直接把QQ文本搬到Web上。
来源:https://juejin.cn/post/7659963731099893803
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。
相关推荐
补充同频道和同主题内容,方便继续浏览更多相关内容。
同类最新
继续查看同栏目最近更新的文章。
Hy3+WorkBuddy组合国产顶级Agent附完整提示词
五个 Case 跑完,总结一下整体体验。工具使用能力:能不能自己开网页、找信息、标出处。规划能力:能不能把多约束需求拆成可执行步骤。长程执行能力:跨多步任务时会不会丢状态。复杂推理能力:能不能先推导、再写代码、再执行验证。WorkBuddy+Hy3 的表现完全符合预期。趁着 WorkBuddy 里的
AI Agent是什么?一文理解大语言模型、记忆、技能、工具、MCP、工作流与上下文
智能体并非单一模型,而是由大语言模型、记忆、工具、工作流等模块协同构成的自主系统。它通过理解目标、检索记忆、调用工具、构建上下文、推理决策,并基于反馈闭环持续迭代,最终自主完成复杂任务。
DeepSeek决定自研芯片打造人工智能全新算力芯脏
被“逼”出来的第三条路。 一直以模型技术见长的DeepSeek,这次在算力供应链上迈出了让所有人侧目的一步。 2026年7月7日,路透社援引三位知情人士消息,DeepSeek正在悄然开发自有AI芯片。值得玩味的是,这颗芯片的定位非常精准——专攻推理,不涉足训练。消息人士称,项目大约启动于一年前,目前
开闭源模型实测:横评Sonnet 5与四大国产开源模型编程能力
今天,我们来深入实测一组闭源与开源模型的编程能力。Sonnet 系列上次更新停留在今年2月,随着Fable 5的解禁,Sonnet 5终于迎来升级。根据Claude官方数据,其整体表现优于Oups 4 6和4 7,略逊于Oups 4 8,但实际运行时,Sonnet 5的调用成本甚至比Opus更高。
SAM3与FastAPI实战:搭建智能图像标注工具
基于SAM3与FastAPI构建智能图像标注工具,解决传统标注耗时痛点。SAM3支持开放词汇分割,输入文本即可自动分割。后端封装SAM3Service实现懒加载、特征缓存及多种分割模式,掩码可视化由后端生成PNGbase64。前端采用react-konva实现画布交互,API遵循RESTful风格,支持批量标注与导出。
