对标 opsxshell/aiops 契约 14 与附图 UX:顶栏新会话与清屏; 忙时「工作中 N 秒」;排队卡支持立即/编辑/删除;Ask 亦单槽排队; run 世代防停止后旧 finally 清 busy。
295 lines
13 KiB
Markdown
295 lines
13 KiB
Markdown
# 16 - AI 会话产品层:状态机 · incomplete · Harness · 排队
|
||
|
||
> 更新:2026-10-15 · 承接 `docs/15-AI-MCP-Agent.md`(工具面已齐)
|
||
> 借鉴:`/Users/logan/devops/aiops/docs/ai-agent` 的 03 / 05 / 06 / 13 / 14
|
||
> 产品锁:MCP-first;默认只读;写需 `mode=read_write` + 确认;桌面单用户;**不做** Evolver / 审批流 / 跨产品共用执行器
|
||
|
||
本文定义 OpsxDB **会话产品层**契约:工具层(aitools / MCP)之上,侧栏 Agent 如何持久化、触顶、续跑、排队与安全确认。
|
||
|
||
---
|
||
|
||
## 1. 与 MCP / Agent 的关系
|
||
|
||
```
|
||
外部 Cursor/Claude ──stdio──▶ opsxdb-mcp ──┐
|
||
▼
|
||
侧栏 Ask(不调工具) aitools.Runtime(同源)
|
||
侧栏 Agent ──▶ ai.runAgent / Harness ──▶ Runtime.Call
|
||
│ │
|
||
▼ ▼
|
||
会话落盘(目标) mcp-audit.jsonl(含 run_id)
|
||
```
|
||
|
||
| 层 | 职责 | 不负责 |
|
||
|----|------|--------|
|
||
| **aitools.Runtime** | 白名单、只读/可写闸、危险 SQL、确认令牌、审计 | 多轮对话、预算、续跑 |
|
||
| **MCP stdio** | 把 Runtime 暴露给外部 Agent | 会话 UI、incomplete |
|
||
| **ai.RunAgent** | ReAct 循环、步骤事件、SSE | 连接增删、公网后端 |
|
||
| **会话产品层(本文)** | 落盘轨迹、RunOutcome、Harness 预算、排队、脱敏、确认绑定 | Evolver、审批流 |
|
||
|
||
**真·续跑**(对齐 aiops 13):必须同时满足 (1) 持久化含 `tool_calls` + `tool` observation(含 `tool_call_id`);(2) 下次 Run 把历史注入引擎。仅恢复气泡不算 resume。
|
||
|
||
---
|
||
|
||
## 2. 会话状态机
|
||
|
||
### 2.1 Session(会话)
|
||
|
||
| 状态 | 含义 |
|
||
|------|------|
|
||
| `idle` | 有历史或空会话,无进行中的 run |
|
||
| `running` | 正在执行一轮 Agent |
|
||
| `awaiting_confirm` | 写操作待用户确认(可与 running 同条气泡内嵌) |
|
||
| `incomplete` | 上一趟因预算/超时停住,轨迹可续 |
|
||
| `completed` | 上一趟正常结束 |
|
||
| `failed` | 不可恢复错误 |
|
||
| `cancelled` | 用户停止 |
|
||
|
||
桌面单用户:会话默认落 `~/.opsxdb/ai-sessions/<session_id>.json`(目标态;首版可仅内存 + 可选落盘)。
|
||
|
||
### 2.2 RunOutcome(单次运行结束,必填)
|
||
|
||
对齐 aiops `13-运行结果状态与可续跑契约.md`:
|
||
|
||
| outcome | 含义 | UI |
|
||
|---------|------|-----|
|
||
| `completed` | 预算内给出最终答案 | 正常 |
|
||
| `incomplete` | 未完成但有部分轨迹 | 「加预算继续」/「继续」 |
|
||
| `failed` | 协议/鉴权/致命工具错 | 失败 |
|
||
| `cancelled` | 用户 Abort | 已取消 |
|
||
| `awaiting_confirm` | 安全闸等待确认 | 确认条 |
|
||
|
||
### 2.3 IncompleteReason
|
||
|
||
| reason | 触发 |
|
||
|--------|------|
|
||
| `max_steps` | 触达 MaxRounds / MaxIterations |
|
||
| `context_budget` | trim 后无法安全继续 |
|
||
| `tool_timeout` | 单工具超时 |
|
||
| `llm_timeout` | LLM 超时 |
|
||
| `safety_abort` | 安全中止(非等待确认) |
|
||
|
||
**规则**:触顶步数 → `incomplete` + `max_steps`,**不得**伪装成 `completed`(现状 `agent.go` 触顶仍发 `done`,见 §6 审查)。
|
||
|
||
### 2.4 对外字段(API / SSE 目标态)
|
||
|
||
```text
|
||
run_id # 一次 Run 全链路 ID(审计 / 步骤共用)
|
||
session_id # 可选;有落盘时必填
|
||
run_outcome # completed | incomplete | failed | cancelled | awaiting_confirm
|
||
incomplete_reason? # 见上表
|
||
resumable # 是否满足真·续跑条件
|
||
steps_used / steps_budget
|
||
partial_summary? # 触顶时进展摘要
|
||
```
|
||
|
||
SSE:在现有 `step_start` / `step` / `delta` / `done` / `error` 上,目标增加显式 `incomplete` 事件(或 `done` 携带 `run_outcome=incomplete`)。首版代码已在 `AgentRes` / `AgentEvent` 挂上 outcome 字段。
|
||
|
||
---
|
||
|
||
## 3. Harness 预算与反馈
|
||
|
||
参考 aiops 03;OpsxDB 裁剪为桌面 DB Agent:
|
||
|
||
| 旋钮 | 默认 | 说明 |
|
||
|------|------|------|
|
||
| `MaxRounds` | 8(上限 16) | ReAct 轮数预算,非质量承诺 |
|
||
| `MaxToolResultChars` | 12000 | 回灌 LLM 前截断 |
|
||
| `MaxContextTokens` | 8000(目标) | trimMessages 预算 |
|
||
| `ToolTimeout` | 与 db 层对齐(目标显式化) | 单工具 |
|
||
| `SameCallLimit` | 3(目标) | 同工具同参循环拒 |
|
||
| `Checkpoints` | 第 4 / 6 轮(目标) | 提示换思路 / 收尾 |
|
||
|
||
### 3.1 trimMessages(目标行为)
|
||
|
||
1. 永远保留 system
|
||
2. 从最近向前保留,约 `MaxContextTokens * 90%`
|
||
3. 至少保留最近 4 条
|
||
4. 裁剪后按 `tool_call_id` 整组保留 assistant.tool_calls + tool 消息
|
||
|
||
代码:`ai.TrimMessages` 钩子已落地;Agent 主循环接入时机在「多轮续跑 / 长会话」打开时启用(单次短问可跳过)。
|
||
|
||
### 3.2 检查点与循环检测
|
||
|
||
目标:同参 ≥3 次拒绝并写入 tool observation;第 4/6 轮向 msgs 注入短 system/user 提醒。不做 Evolver。
|
||
|
||
---
|
||
|
||
## 4. 会话落盘与真·续跑
|
||
|
||
### 4.1 存储形状(目标)
|
||
|
||
```json
|
||
{
|
||
"id": "sess_…",
|
||
"title": "列出 demo 表",
|
||
"createdAt": "…",
|
||
"updatedAt": "…",
|
||
"status": "incomplete",
|
||
"messages": [
|
||
{"role":"system","content":"…"},
|
||
{"role":"user","content":"…"},
|
||
{"role":"assistant","content":"…","toolCalls":[{"id":"c1","function":{…}}]},
|
||
{"role":"tool","toolCallId":"c1","name":"opsxdb_list_tables","content":"…"}
|
||
],
|
||
"runs": [{"runId":"run_…","outcome":"incomplete","reason":"max_steps","stepsUsed":8}]
|
||
}
|
||
```
|
||
|
||
脱敏后落盘(见 §5)。密码、连接串明文、大结果全文不进会话文件。
|
||
|
||
### 4.2 API 契约(目标,大 UI 可后做)
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/api/ai/sessions` | 列表 |
|
||
| GET | `/api/ai/sessions/:id` | 含 messages |
|
||
| POST | `/api/ai/agent` | 现有;增加可选 `sessionId` / `continue:true` / `prior` |
|
||
| POST | `/api/ai/sessions/:id/continue` | 注入 prior + 可选提高 MaxRounds |
|
||
|
||
骨架:`server/internal/ai/session.go`(文件存储接口 + 类型;HTTP 路由可后挂)。
|
||
|
||
---
|
||
|
||
## 5. 脱敏与确认绑定
|
||
|
||
### 5.1 脱敏(落盘 / 审计 / 展示)
|
||
|
||
对齐 aiops 05,桌面范围:
|
||
|
||
| 类型 | 示例 |
|
||
|------|------|
|
||
| Bearer / API Key | `Bearer …`、`sk-…` |
|
||
| 密码 | `password=`、`pwd=` |
|
||
| 连接串 | `mysql://user:pass@host` |
|
||
| PEM 私钥块 | `BEGIN … PRIVATE KEY` |
|
||
|
||
`ai.RedactSensitive` 已提供;会话落盘与审计 summary 应过脱敏。
|
||
|
||
### 5.2 确认绑定(写操作)
|
||
|
||
写确认不得只靠 UI 气泡里的明文 SQL 再次提交而无绑定:
|
||
|
||
```text
|
||
confirm_bind = sha256(connection_id + "\n" + normalize(sql_or_command))
|
||
```
|
||
|
||
规则:
|
||
|
||
1. `needConfirm` 响应带上 `confirmBind`(或服务端会话内缓存)
|
||
2. `/api/ai/agent/confirm` 校验:connection_id + 语句 与绑定一致,否则拒绝并要求重确认
|
||
3. 用户改了 SQL → 哈希变 → 必须重新确认(防 TOCTOU)
|
||
|
||
首版:`aitools.ConfirmBindHash` + Runtime needConfirm 返回字段;confirm 端点校验(见代码)。
|
||
|
||
---
|
||
|
||
## 6. 忙时单槽排队
|
||
|
||
对齐 aiops 14;桌面侧栏:
|
||
|
||
| 动作 | 语义 |
|
||
|------|------|
|
||
| 排队 | running 时新输入进 `pending`(单槽;再提交覆盖) |
|
||
| 自动续发 | 当前 run 结束(任一 outcome)后若有 pending → 自动 continue |
|
||
| 立即 | 停止当前 → 立刻跑 pending |
|
||
| 编辑 / 撤销 | 拉回输入框 / 丢弃 |
|
||
|
||
与 incomplete 续跑可叠加:排队是同会话并发输入;incomplete 是上一趟未完成再继续。
|
||
|
||
**首版**:文档 + 前端状态契约;服务端仍单请求单 run(Abort 用 ctx cancel)。不做跨会话队列、不做长 FIFO。
|
||
|
||
---
|
||
|
||
## 7. 细节审查:现状 → 缺口 → 建议
|
||
|
||
### 7.1 Agent loop(`server/internal/ai/agent.go`)
|
||
|
||
| | |
|
||
|--|--|
|
||
| **现状** | ReAct:`ChatWithTools` → `execOneTool` → 回灌;默认 MaxRounds=8(帽 16);触顶文案后仍 `AgentEventDone`;无 session_id / prior;取消走 ctx → error 事件;NeedConfirm 回灌模型但不暂停 loop 为 `awaiting_confirm` outcome |
|
||
| **缺口** | 无结构化 `run_outcome`;触顶伪装 completed;无真·续跑;无同参循环拒;无中段检查点;确认不打断为等待态 |
|
||
| **建议** | 触顶 → `incomplete`/`max_steps` + `resumable`;`AgentRes` 带 outcome/run_id;可选 `Prior []Message`;NeedConfirm 可提前结束本 run 为 `awaiting_confirm`(UI 已有确认条,语义对齐即可) |
|
||
|
||
### 7.2 Harness 架构
|
||
|
||
| | |
|
||
|--|--|
|
||
| **现状** | 预算散落常量(`defaultAgentMaxRounds`、`maxToolResultFeedChars`);无独立 Harness 配置对象;无 trim / 循环检测 / checkpoint;SSE 事件无 outcome |
|
||
| **缺口** | 与 aiops 03 的「工程 Harness」不对齐;上下文窗口未 own;长会话必炸或静默丢配对 |
|
||
| **建议** | `HarnessConfig` + `TrimMessages` + `DetectRepeatedToolCall`;Run 开头生成 `run_id` 写入 Runtime.Audit;检查点用轻量 user 消息注入,勿上 Evolver |
|
||
|
||
### 7.3 MCP 同源 Runtime(`server/internal/aitools`)
|
||
|
||
| | |
|
||
|--|--|
|
||
| **现状** | MCP 与 Agent 共用 `Runtime.Call`;Policy 白名单 + 只读默认;P1 `read_write` + needConfirm;审计 JSONL 有 source;无 run_id;确认端点不校验语句哈希绑定;Agent 剥离模型 confirm_danger |
|
||
| **缺口** | 审计无法按一次 Agent run 聚合;确认存在「改 SQL 仍用旧气泡点执行」窗口;MCP 外部客户端若带 confirm_danger 可在 read_write 下写(设计允许外部 Agent 自带确认语义,但桌面应对齐绑定) |
|
||
| **建议** | `AuditEntry.RunID`;`ConfirmBindHash`;confirm API 校验绑定;MCP 文档写明:外部客户端的 confirm 等同操作者授权,桌面 GUI 必须绑定哈希 |
|
||
|
||
---
|
||
|
||
## 8. 落地节奏(本轮 / 下一刀)
|
||
|
||
| 优先级 | 项 | 本轮 |
|
||
|--------|-----|------|
|
||
| P0 | 文档契约(本文)+ 15 交叉引用 | ✅ |
|
||
| P0 | RunOutcome 类型、触顶 incomplete、run_id 进审计 | ✅ 代码 |
|
||
| P0 | TrimMessages / Redact / ConfirmBind 钩子 | ✅ 代码 |
|
||
| P1 | 会话落盘 + continue API + UI 恢复 | 文档契约 + session 骨架 |
|
||
| P1 | 单槽排队 UI | 文档;前端后做 |
|
||
| P2 | 同参循环拒、检查点、Ask 真 token 流 | 后置 |
|
||
|
||
---
|
||
|
||
## 9. 验收(会话层)
|
||
|
||
- [ ] `go test ./internal/ai/ ./internal/aitools/` 通过(含 outcome / trim / redact / bind)
|
||
- [ ] 触顶轮次:响应 `run_outcome=incomplete`,`incomplete_reason=max_steps`
|
||
- [ ] 审计行含 `run_id`(Agent 路径)
|
||
- [ ] confirm:改 SQL 后旧 bind 拒绝
|
||
- [ ] (P1)关窗再开:同 session 注入 prior 能继续调工具
|
||
|
||
---
|
||
|
||
*文档版本: v1.0 | 2026-10-15 CST | 先契约与小改,大 UI 后挂*
|
||
|
||
|
||
---
|
||
|
||
## 10. 仍未完清单(对照本契约 · 2026-10-15)
|
||
|
||
| 状态 | 项 |
|
||
|------|-----|
|
||
| ✅ | MCP 工具面、Ask\|Agent、SSE、P1 确认写、`run_id` / confirmBind、触顶 incomplete |
|
||
| ✅ | 设置 → **MCP** 子页(安装检测、只读/危险 SQL、白名单、多客户端片段) |
|
||
| ✅ | 官网 AI/MCP 块 + 文档 `/docs/mcp` |
|
||
| ✅ | 会话落盘 + HTTP 续跑(`GET/DELETE /api/ai/sessions`、`POST …/continue`)+ UI「加预算继续」 |
|
||
| ✅ | NeedConfirm 结束 run 为 `awaiting_confirm` outcome |
|
||
| ✅ | 同参循环拒 + 中段检查点(第 4 / 6 轮)接入主循环 |
|
||
| ✅ | 忙时单槽排队 UI(覆盖 / 编辑 / 撤销 / 立即 / 工作中计时 / 结束后自动续发;Ask+Agent) |
|
||
| ✅ | 新会话 / 清屏(侧栏顶栏;清屏丢 sessionId 防隐式续跑) |
|
||
|
||
### 侧栏 UX(对标 opsxshell / 附图)
|
||
|
||
| 控件 | 行为 |
|
||
|------|------|
|
||
| **新会话** | 停止当前 → 清空消息 / sessionId / 排队 / 输入 |
|
||
| **清屏** | 停止当前 → 清空消息与排队(草稿保留);丢掉 sessionId |
|
||
| **工作中 N 秒** | `aiBusy` 时列表底转圈计时 |
|
||
| **排队卡** | 单槽:`立即`(中断开跑)/ `编辑`(拉回输入)/ `删除`(撤销) |
|
||
| **忙时输入** | placeholder「继续输入以排队…」;发送=入队(再提交覆盖) |
|
||
|
||
| ⬜ | Ask 真 token 流式(非假打字)— 成本高:需 openai-chat / anthropic / responses 三套 SSE;本轮记缺口,Ask 仍一次性 Complete |
|
||
| ⬜ | MCP「断开」结束外部 Cursor 会话(当前仅 UI 提示) |
|
||
| ⬜ | 设置页一键 `npm install -g`(现为复制命令;避免桌面代跑需授权) |
|
||
|
||
### 10.1 本轮验收要点
|
||
|
||
- `go test ./internal/ai/ ./internal/aitools/ ./internal/server/`
|
||
- Agent 触顶 → `run_outcome=incomplete` + 气泡「加预算继续」→ `continue=true` 注入落盘 prior
|
||
- 写工具无确认 → `awaiting_confirm`;确认条仍走 `/api/ai/agent/confirm` + confirmBind
|
||
- Agent 忙碌时再发送 → 单槽排队条;结束后自动发或点「立即」
|
||
|
||
*本节随交付勾选;Ask SSE 与 MCP 杀外部进程后置。*
|