Files
opsxdb/docs/16-AI-Session-Harness.md
T
logan 9c2b623ce4 feat(ai): 新会话/清屏 + 工作中转圈 + 可编辑单槽排队
对标 opsxshell/aiops 契约 14 与附图 UX:顶栏新会话与清屏;
忙时「工作中 N 秒」;排队卡支持立即/编辑/删除;Ask 亦单槽排队;
run 世代防停止后旧 finally 清 busy。
2026-10-15 14:28:57 +08:00

295 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 杀外部进程后置。*