72 KiB
ISS-014 单体 ReAct Agent、Harness 与 ACI 工具瘦身
状态:已完成(阶段 0-7 已验收并归档)
严重程度:高
发现时间:2026-07-20
目标分支:refactor/chat-single-react-harness
基线分支:refactor/mvp1.0
关联:ISS-003、ISS-012、ISS-013、executor-evidence-attribution-hallucination
1. 摘要
当前 Chat 诊断链路将一次完整 ReAct 生命周期拆成 Planner、Executor、Verifier、Composer 等多个 LLM 角色,再通过服务方法、Hook、ThreadLocal、结构化协议和多轮重试串联。该设计把 Agent 本身已经具备的“思考、行动、观察、继续行动、最终回答”重复提升为外层编排,造成职责分散、上下文重复、失败语义不一致和 Token 成本失控。
本 Issue 将后续实现收敛为:
一个负责实际工作的 Diagnosis ReAct Agent
+ 一个负责确定性控制的 Harness
+ 一个上下文隔离的 SemanticGuard Agent
Diagnosis ReAct Agent 和 SemanticGuard Agent 都通过同一个 Harness 执行边界运行;不为 SemanticGuard 再复制一套 Harness,也不引入新的 Graph 或编排器。
同时重构 Agent 可见 Tool Contract,使 RAG、日志和新增 MySQL Tool 遵守 ACI 原则,并通过工具结果清洗与投影层控制上下文体积、敏感数据和证据引用。
本 Issue 是后续实现的总设计来源。ISS-012 继续保留 Token/预算问题背景,ISS-013 继续保留入口/SSE 问题背景;实现范围和阶段顺序以本 Issue 最终确认版本为准。
2. 根因判断
2.1 ReAct 被重复实现
原有角色实际对应一个完整 ReAct 生命周期:
Planner -> 内部规划与推理
Executor -> 工具调用与观察循环
Verifier -> 证据充分性自检
Composer -> 最终用户回答
将这些职责拆成多个 Agent 后,需要额外维护:
- 多套 Prompt 和输出 Schema;
- Agent 间 JSON 转换;
- 多套重试状态;
- 证据和上下文在不同角色之间重复搬运;
- 失败、低置信度和 Fallback 的分支组合;
- 主链路与审计链路之间的隐式 ThreadLocal 状态。
2.2 工具返回面向开发者而不是面向 Agent
当前 Tool 结果混合了:
- Agent 真正需要的证据;
- 检索/查询内部实现细节;
- 调试 Trace 和 Rerank 数据;
- 重复正文和 ContextPack;
- 原始大结果;
- Session 隐式记忆;
- 无结果、错误和去重等含义不一致的状态。
这违反 ACI 的清晰、稳定、有界、可恢复和可继续操作原则,也是 Agent 上下文膨胀的重要来源。
2.3 安全约束与业务编排耦合
证据真实性校验属于确定性执行约束,不需要成为独立 LLM 节点或业务编排角色。语义推导校验确实需要独立上下文,但它不应拥有工具、记忆、ReAct 循环或回调主 Agent 的能力。
3. 目标架构
POST /api/chat (SSE)
-> Chat Application Use Case
-> Intent Router(一次轻量 Chat)
-> SYSTEM_CHAT
-> KNOWLEDGE_QUERY
-> DIAGNOSIS
SYSTEM_CHAT
-> 无 Tool 普通 Chat
KNOWLEDGE_QUERY
-> 单次 lookup_knowledge
-> 普通 Chat 基于 RAG Evidence 回答
-> Harness 校验证据引用
DIAGNOSIS
-> Harness 启动 Diagnosis ReAct Agent
-> Agent 接收当前原始 Query 和可选 previous_turn
-> Agent 内部规划
-> Agent 自主调用受控 Tool
-> Harness 在 Tool 边界校验、记录、清洗和投影
-> Agent 内部检查证据是否足够
-> Agent 输出结构化诊断草稿、Analysis Items、Conclusion 和 Tool Call Refs
-> Harness EvidenceGuard(0 LLM)
-> Schema 校验
-> 当前 Run 证据所有权校验
-> tool_call_id 当前 Run 归属和调用结果校验
-> SemanticGuard Agent(独立隔离上下文)
-> 接收原始 Query、完整 Draft 和已物理验真的精确证据
-> 判断整份用户报告是否语义成立且没有超出证据范围
-> Harness Release Policy
-> SUPPORTED:释放完整答案
-> UNSUPPORTED:安全降级
-> TIMEOUT/ERROR:不释放未验证草稿
业务层不使用 StateGraph。底层 ReactAgent 框架内部是否使用 Graph 属于框架实现细节,不构成项目业务编排。
4. 已确认设计决策
4.1 单体 Diagnosis ReAct Agent
主 Agent 负责:
- 接收当前原始 Query 和 Harness 组装的可选极简 previous_turn;
- 理解诊断目标;
- 在内部形成排查方向;
- 选择并调用只读证据 Tool;
- 观察 Tool 结果并决定是否继续;
- 检查证据是否足以支持准备输出的结论;
- 形成结论先行的结构化诊断草稿、Analysis Items 和 Tool Call Refs;
- 证据不足时明确输出无法确认,不补造事实。
主 Agent 不负责:
- 意图路由;
- Session/Run 生命周期;
- Token、工具、超时和取消预算;
- 数据库存储和 Trace;
- HTTP/SSE 协议;
- 证据物理真实性校验;
- 调用其他 Agent;
- 外层重试状态机。
不要求 Agent 输出 Thought 或 Chain of Thought。内部规划不形成对外协议,也不持久化为业务事实。
4.2 Harness
Harness 负责确定性执行控制,而不是业务推理:
Run 控制
- 创建和传播
sessionId + runId; - 建立请求 Deadline 和取消信号;
- 限制模型调用、工具调用、Token 和总耗时;
- 客户端断开时取消后续执行;
- 保证 Run 进入明确终态。
Model 边界
- 包装 ChatModel;
- 记录 input/output/total Token;
- 应用调用次数和上下文预算;
- 不修改 Agent 的业务判断。
Tool 边界
- Pre-Tool 参数 Schema、权限、只读和预算校验;
- 执行真实 Tool;
- 接收框架在 Tool 执行请求中提供的
tool_call_id,并在执行前完成校验; - Tool 返回后将请求、原始响应、状态和时间写入 Redis;当前版本暂不做持久化脱敏;
- 调用对应
ToolResultProjector生成规范化、聚合、截断后的agent_result,并写回同一 Redis 调用记录; - 只把有界
agent_result返回 Agent,禁止透传raw_response; - 清洗失败时返回 Tool ERROR,禁止透传未清洗原始结果。
Redis 中的完整调用记录至少包含:tool_call_id、run_id、tool_name、request、raw_response、agent_result、调用状态和起止时间。tool_call_id 使用框架提供的 Tool Call 协议 ID,是一次 Tool 调用在当前 Run 内的唯一引用;Harness 不另造第二套调用 ID,并可按 runId + toolCallId 取得完整记录。
Redis 是 Harness 内部基础设施,只允许 Harness 访问。任何 Agent 都不能获得 Redis Client、Redis Tool、key、连接信息或完整调用记录。本 Issue 不提供历史来源召回 Tool;Redis 只用于 Harness 的证据验真、短期调用追踪和最终引用生成,不作为 Agent 记忆。当前诊断若需要新的日志或数据库事实,由 Diagnosis Agent 通过当前受控 Tool 重新查询。
Redis 存储契约固定为:
key = superbiz:harness:tool-call:{runId}:{toolCallId}
status = PROJECTING | READY | ERROR
evidence_status = EVIDENCE_FOUND | NO_EVIDENCE | ERROR
- 一次 Tool Call 对应一个 Key,不建立额外 Evidence Index;Key 不包含 Query、Session 内容或 Tool 参数;
status表示调用/投影生命周期:Tool 原始响应写入后为PROJECTING,ToolResultProjector成功并写入agent_result后为READY,执行或清洗失败为ERROR;evidence_status表示结果语义,只有status=READY时才允许为EVIDENCE_FOUND或NO_EVIDENCE;失败记录为ERROR;- EvidenceGuard 只接受当前 Run 下
status=READY且存在agent_result的记录;evidence_status=ERROR不可引用,NO_EVIDENCE只能用于负向观察; - TTL 默认 2 小时并可配置,必须大于最大 Run 耗时、SemanticGuard 总耗时和安全缓冲;
- TTL 在创建时设置,后续读取或更新不得续期;取消和失败记录保留到 TTL,首版不永久归档;
- 首版默认
max-record-bytes=1MB、max-agent-result-bytes=64KB、max-run-bytes=5MB,均由 Harness 配置并根据运行数据校准; raw_response超过单记录上限时不静默截断,调用进入ERROR/RESULT_TOO_LARGE;agent_result可按投影预算截断并标记truncated=true;- 使用 UTF-8 JSON,首版不压缩、不分片、不使用 Java 原生序列化或对象存储;
- Redis 使用独立 Key 前缀和 ACL,应用日志不得输出
request/raw_response。
数据分层术语固定为:
- Redis canonical invocation:短期保存
request/raw_response/agent_result的完整调用记录;当前版本允许未脱敏,但受 Harness-only ACL、TTL 和容量上限保护; - Agent projection:
ToolResultProjector生成的有界agent_result,经过 Tool-specific 聚合、截断和必要敏感字段清洗,只向 Diagnosis Agent、EvidenceGuard 和 SemanticGuard 的隔离快照开放; - Durable audit:应用日志或业务审计表中的长期记录,只保存调用元数据、脱敏参数、耗时、状态和有界结果摘要,不保存 Redis
raw_response。
harness:
evidence:
key-prefix: "superbiz:harness:tool-call"
ttl: 2h
max-record-bytes: 1MB
max-agent-result-bytes: 64KB
max-run-bytes: 5MB
Output 边界
- 校验主 Agent 输出 Schema;
- 校验 Analysis Items 与 Tool Call IDs 的绑定;
- 执行 EvidenceGuard;
- 组装 SemanticGuard 的隔离输入;
- 根据验证结果决定最终释放或降级。
SSE 与审计
- 按最小 SSE 契约发送 metadata、脱敏 status/tool progress、最终 content 或 failure,以及 done;
- 记录模型调用、工具调用、验证结果、预算消耗和降级原因;
- 不输出内部 Prompt、Thought、原始 Tool 载荷和内部堆栈。
预算配置
所有暂时无法准确估算的预算先集中提取为 Harness 配置,不在本 Issue 中伪造固定数值:
- 主 Agent 最大模型调用次数、总 Tool 次数、单 Tool 次数、上下文/Token 和总耗时;
- SemanticGuard 单次超时、总耗时和输入大小;
- RAG 最大证据数、单条摘录和总字符数;
- 日志最大 Pattern/Event 数、消息长度、lookback 和总字符数;
- MySQL 最大行数、单元格长度、总字节数和查询超时;
- Redis Tool 调用记录的 TTL、单记录/Agent 投影/单 Run 大小上限。
首版使用静态应用配置和可调默认值,不建设配置中心、热更新、按租户覆盖或按模型路由配置。Harness 必须校验配置合法性、记录实际消耗和触发的预算原因;后续根据 Trace、Token、延迟和截断数据校准默认值。已经确认的“最多重试一次”属于失败语义,不允许通过配置放大为隐式重试循环。
重试策略装配
重试策略从业务流程中提取为小型类型化配置,由 Harness 统一装配,不分散在 Agent、Tool、Controller 或 SDK 中:
RetryPolicy
-> max_attempts
-> retryable_failures
HarnessRetryPolicies
-> intent_router: max_attempts=2
-> diagnosis_agent: max_attempts=1
-> tool_call: max_attempts=1
-> semantic_guard: max_attempts=2
-> evidence_repair: max_attempts=1
HarnessRetryExecutor只承载“同一操作再次执行”的技术重试,首版用于 Intent Router 和 SemanticGuard;- Intent Router 只在超时、传输失败或非法枚举输出时重试一次;第二次失败返回安全入口错误,不默认路由到 Diagnosis;
- Diagnosis Agent 整体和其单次模型调用不自动重试,ReAct 的正常模型/Tool 轮次不算重试;
- Tool 调用不自动重试;Tool
ERROR作为 Observation 返回,Agent 再次调用属于新的 Tool Action,必须由框架生成新的tool_call_id并计入预算; - EvidenceGuard 的一次无 Tool 结构修复不是同操作重试,由 Harness 按显式流程执行;
- SemanticGuard 只在超时、传输失败、解析失败或 Schema 无效时使用同一输入重试一次;
UNSUPPORTED是有效业务结果,不重试; NO_EVIDENCE、取消、预算耗尽和业务拒绝均不可重试;- 关闭或限制 SDK、HTTP Client 和数据库驱动的隐藏重试,所有实际 attempt 必须由 Harness 记录;
- 首版不引入 Retry DSL、插件注册中心、动态策略脚本或多级退避状态机。
Harness 不应包含:
- StateGraph;
- Planner/Executor/Composer 节点;
- Agent 间消息路由;
- 通用工作流 DSL;
- HarnessPlugin 注册中心;
- 多级业务重试状态机;
- 自行实现的 ReAct While 循环。
4.3 EvidenceGuard
原 Gatekeeper 不再作为独立编排组件存在。其能力进入 Diagnosis ReAct Agent 的 Harness:
Post-Tool
-> 按 tool_call_id 保存完整调用记录和 agent_result
Pre-Release
-> 校验 Draft Schema
-> 校验 analysis_id 唯一
-> 校验每条 Analysis 至少引用一个 tool_call_id
-> 校验 Conclusion/Action Plan/Recommendation 引用的 analysis_id 均存在
-> 校验 tool_call_id 是否存在且属于当前 Run
-> 校验 Tool 调用返回可引用证据状态
-> 校验 agent_result 存在
-> 生成带真实性保证的 verified evidence snapshot
EvidenceGuard 使用确定性代码,不调用 LLM,不决定语义推导是否成立。它是原 Gatekeeper 的 Harness 内能力,而不是独立 Agent 或编排节点。具体规则包括:
analysis_id在当前 Draft 内唯一;- 每条 Analysis 的
tool_call_ids非空,且每个 ID 对应当前 Run 中status=READY、存在agent_result、evidence_status可引用的 Tool Call; kind=NORMAL的 Analysis 只能引用EVIDENCE_FOUND;kind=NEGATIVE_OBSERVATION可以引用NO_EVIDENCE,其agent_result必须保留原始查询条件、时间/数据范围和零匹配信息;NO_EVIDENCE只证明当前查询范围内无匹配结果,不能被 EvidenceGuard 当作问题不存在、根因排除或系统健康的正向证据;evidence_status=ERROR一律不可引用;- 非空 Conclusion 的
based_on_analysis_ids非空且全部指向已有 Analysis; - 每条 Action Plan 和 Recommendation 的
based_on_analysis_ids非空且全部指向已有 Analysis; conclusion=null时允许保留有真实证据支撑、但尚不能形成结论的 Analysis;不要求每条 Analysis 都必须被 Conclusion 引用;- 最终引用清单只能从 Draft 实际引用且通过上述校验的 Tool Calls 确定性展开。
通过后,所引用 Tool 调用的 Run 归属、执行状态和 agent_result 被视为可信;ToolResultProjector 的投影正确性由代码和单元测试保证。EvidenceGuard 不判断 Analysis 是否正确解释证据,也不判断 Analysis 是否足以推出 Conclusion,这些属于 SemanticGuard。SemanticGuard 不再重复访问 Redis 或重新验证真实性。
4.4 SemanticGuard Agent
SemanticGuard 是独立审查 Agent,不加入主 ReAct Agent 上下文。
SemanticGuard 复用系统当前唯一配置的 ChatModel,不引入模型路由。它由 Harness 使用受限执行参数启动,Harness 负责隔离上下文、输入预算、调用超时、取消、最多一次重试、结构化输出校验、审计和最终释放策略。
必须满足:
- 使用全新的隔离上下文;
- 没有工具;
- 没有隐式记忆;
- 没有 ReAct 循环;
- 不读取主 Agent Thought、完整历史或 System Prompt;
- 不读取未通过 EvidenceGuard 的数据;
- 不访问 Redis,不重新校验 tool_call_id 或 agent_result 真实性;
- 不重新规划、查询、总结、润色、改写或补充用户答案;
- 不回调主 Agent 形成循环;
- 每次尝试只执行单轮语义和时间线审查,不允许 Agent 自身循环;仅 Harness 可在超时、调用失败或结构化输出无效时重试一次。
输入只包含:
- 原始 Query;
- 主 Agent Draft 的完整用户可见语义内容:Conclusion、Analysis、Action Plan、Recommendations 和 Limitations,但不包含内部
tool_call_id; - 经过 EvidenceGuard 验真的 Evidence snapshot,包括精确 excerpt/value 和必要的可读来源信息:RAG 的 document_id/title/breadcrumb,或日志/MySQL 的 source、timestamp、scope。
tool_call_id 只在主 Agent 与 Harness、EvidenceGuard 之间使用,不注入 SemanticGuard 或其他 Agent。Harness 在组装 snapshot 时取出所引用调用的完整有界 agent_result,不读取或注入 raw_response,并将其中的来源信息、精确摘录和结构化值按 analysis_id 分组。该转换使用确定性代码,不调用 LLM 重新摘要;Harness 在模型调用之外保留 Tool 调用映射。
{
"analysis_id": "a1",
"analysis_text": "连接池活跃连接达到 50/50",
"verified_evidence": [
{
"source_type": "LOG",
"source": "APPLICATION",
"scope": "order-service,最近 30 分钟",
"timestamp": "2026-07-21T10:30:00+08:00",
"excerpt": "HikariPool active=50, max=50, pending=23"
}
]
}
RAG 使用稳定 document_id/title/breadcrumb/excerpt,日志使用 source/scope/timestamp/pattern/event,MySQL 使用逻辑数据源、查询范围、列名和有界行/值。SemanticGuard 依靠 analysis_id -> verified_evidence 关系校验每条 Analysis,不需要理解 Tool ID,也不能访问完整工具正文。
不向 SemanticGuard 传递 Diagnosis Plan、Tool 调用过程或其他审计数据。SemanticGuard 对最终将展示给用户的完整 Draft 做报告级校验:
- Evidence 是否支持 Analysis;
- Analysis 是否能够推出 Conclusion;
- Action Plan 是否与已支持的分析/结论相关,危险行动是否标记人工确认;
- Recommendations 是否与诊断结果及 RAG/分析依据一致;
- Limitations 是否准确覆盖实际查询范围和缺失信息;
- 所有内容是否超出证据的服务、时间和数据范围。
SemanticGuard 只是审稿人,不是报告作者:它不能输出 corrected report、new conclusion、new action、new recommendation 或 user answer。真实性并不自动代表相关性、充分性或推导成立。
输出使用报告级二元状态,不使用未校准数字作为在线释放门禁,也不返回逐条 Analysis 状态:
{
"verdict": "SUPPORTED",
"reason": "已验证证据能够支持诊断草稿中的分析并推出结论"
}
verdict 只允许 SUPPORTED 或 UNSUPPORTED。Diagnosis Agent 是唯一报告作者;SemanticGuard 不生成、改写或裁剪用户报告;Harness 不总结报告,只发布原 Draft 或固定 Fallback。reason 只进入审计,不直接作为用户答案。
第一次超时、调用失败、解析失败或 Schema 校验失败时,Harness 使用相同的已验真证据快照重试一次,不重新运行主 Agent 或 Tool。第二次仍失败则按未通过处理:不释放未验证草稿,只返回安全降级报告并正常结束 SSE。单次与总超时由 Harness 配置提供,初期不冻结具体数值。
4.5 意图识别
使用一次轻量 Chat 对当前 Query 做三分类:
SYSTEM_CHAT
KNOWLEDGE_QUERY
DIAGNOSIS
约束:
- 输入只包含当前原始
query、可选last_intent和可选last_user_query; - 当前 Query 明确表达新主题时以当前 Query 为准;只有“继续”“那另一个服务呢”等追问才参考前一意图和问题;
- 不改写 Query;
- 不传完整历史、来源文档、evidence refs、Tool 结果或已发布答案;
- 不配置 Tool、Skill、记忆和 ReAct 循环;
- 输出只允许三个固定路由值;
- 原关键词
QuestionComplexity不再作为主路由。
{
"query": "那退款服务呢?",
"last_intent": "DIAGNOSIS",
"last_user_query": "订单 order-123 支付超时,帮我排查"
}
三个意图使用以下固定执行器:
SYSTEM_CHAT
- 用于系统定位、能力说明、使用方式、简单问候和非业务闲聊;
- 使用无 Tool 的普通 ChatModel;
- 系统能力来自简短、受控的能力说明,不依赖模型隐式知识;
- 不进入 ReAct、EvidenceGuard 或 SemanticGuard;
- 输出普通用户答案。
KNOWLEDGE_QUERY
- 用于内部文档、流程、API、错误码、配置规范和排障手册;
- 原始 Query 只执行一次有界
lookup_knowledge; - 使用普通 Chat 基于 RAG Evidence 生成答案;
- Harness 校验证据引用;
- 不开放 query_logs、query_metrics 或 query_mysql;
- 不进入完整 Diagnosis ReAct 循环;
- 输出
answer + references + limitations。
DIAGNOSIS
- 用于故障排查、根因定位、实时状态分析和多证据组合;
- 进入完整 Diagnosis ReAct Agent;
- 可使用 RAG、日志和 MySQL 等受控证据 Tool;本 Issue 不提供
query_metrics; - 经过 Harness EvidenceGuard 和独立 SemanticGuard;
- 输出完整诊断报告。
三个执行路径都接收同一个原始 Query,不做 Query 改写,不相互调用。原 DATABASE_QUERY 路由删除;MySQL Tool 只作为 DIAGNOSIS 的证据工具,不对应独立意图。
4.6 Diagnosis Agent 极简上下文
Diagnosis Agent 不接收完整 Session 历史,只接收当前 Query 和可选的上一个已完成回合:
{
"query": "那退款服务呢?",
"previous_turn": {
"user_query": "订单 order-123 支付超时,帮我排查",
"published_conclusion": "支付超时与连接池耗尽有关",
"scope": "order-service,最近 30 分钟",
"limitations": ["尚未取得长事务数据"],
"source_documents": [
{
"document_id": "payment-timeout-guide",
"title": "支付超时排查手册"
}
]
}
}
previous_turn只取同一 Session 上一个已完成回合;无上一回合或上一回合未安全发布时为null;- 只读取已安全发布的结构化 Conclusion、Scope、Limitations 和 RAG 来源文档元数据;
source_documents只包含稳定 document ID 和 title,不包含正文、Tool Call ID、Tool 结果或内部引用;- 不复用历史日志或 MySQL 数据,需要当前运行事实时重新调用受控 Tool;
- Harness 从上一回合结构化最终结果确定性组装,不调用 LLM 生成摘要;
- 字段长度和来源文档数量使用集中配置,超限时按字段边界截断或删除末尾来源;
- 当前 Query 始终优先,
previous_turn只辅助理解追问,不作为当前事实证据。
previous_turn 的持久化真理源固定为 diagnosis_run。Run 记录保存 intent、release_outcome 和 published_result;published_result 只包含 user_query/published_conclusion/scope/limitations/source_documents,不包含 Tool Call ID、原始证据、完整 Draft 或 SemanticGuard 审计原因。只允许读取同一 Session 最近一个 intent=DIAGNOSIS、release_outcome=SUCCESS 且 published_result 非空的 Run;FALLBACK/FAILED/CANCELLED 只用于审计,不进入下一轮上下文。Redis SessionContext 只作为短期会话缓存,不是该契约的真理源。
4.7 SSE 用户交互
SSE 采用“过程事件实时发送、最终报告安全释放”的口径:
- SSE 连接在整个诊断期间保持打开;
- 计划、Tool 进度、清洗、EvidenceGuard 和 SemanticGuard 状态实时发送;
- SemanticGuard 完成前不发送最终结论;
- 校验通过后以一个
content事件释放结构化最终报告; - 不将已生成的完整答案切片伪装成 Token 流;
- SemanticGuard 超时/失败时发送安全降级内容并结束;
- 不引入 WebSocket、轮询或验证后的第二个 Composer Agent。
首版只定义五种业务事件,固定顺序为:
metadata -> status* -> content|failure -> done
metadata:固定第一个且只发送一次,包含session_id和run_id;status:可发送零到多次,只包含固定阶段码和安全的用户可见说明;content:最多一次,承载正常答案或固定安全 Fallback,不做 Token 切片;failure:最多一次,只用于 Router/Harness 等无法生成任何安全响应的技术失败,不能与content同时发送;done:固定最后一个且只发送一次,outcome只允许SUCCESS/FALLBACK/FAILED。
最小事件结构:
event: metadata
data: {"session_id":"...","run_id":"..."}
event: status
data: {"code":"SEMANTIC_VALIDATING","message":"正在进行安全校验"}
event: content
data: {"content_type":"DIAGNOSIS_REPORT","payload":{...}}
event: failure
data: {"code":"ROUTING_UNAVAILABLE","message":"当前暂时无法处理该请求,请稍后重试"}
event: done
data: {"outcome":"SUCCESS"}
EvidenceGuard 或 SemanticGuard 降级使用 content + done(FALLBACK);Router 重试后仍失败等无法形成安全内容的技术错误使用 failure + done(FAILED)。客户端主动断开后不再尝试发送终态事件,Harness 必须取消当前模型和 Tool 调用,并只在内部将 Run 记录为 CANCELLED。首版不实现断线续传、事件重放、事件序号恢复、轮询或 WebSocket。
禁止输出:
- Chain of Thought 和内部规划;
- System Prompt、模型 Prompt 和完整上下文;
- 未脱敏 Tool 参数;
- 原始日志、完整数据库行和完整检索 Trace;
- 内部异常、供应商错误和 Java 堆栈;
- 尚未通过释放门禁的强结论;
- Planner plan、Verifier verdict、Graph retry 等内部实现状态。
只保留一个 POST /api/chat SSE 接口,不保留同步 Chat 或 /api/chat_stream 兼容路径。
5. ACI Tool Contract
5.1 原则
Agent-facing Tool 必须满足:
- 名称说明 Agent 能做什么,不暴露内部实现;
- 描述说明什么时候调用、输入什么、不能用于什么;
- 参数数量最小、含义明确、具备 Schema;
- Agent 不控制连接、凭据、region、topK、limit 等基础设施参数,除非业务确有必要;
- 输出稳定、结构化、有界;
- 明确区分成功有证据、成功无证据和工具失败;
- 输出包含可继续使用的稳定 Tool Call Reference;
- 错误信息安全、可恢复,不泄露内部细节;
- 相同输入不因隐藏 Session 记忆产生不可解释的语义变化;
- Agent 只看到执行结果,不看到审计/调试实现。
5.2 通用状态语义
Agent-facing evidence Tool 统一使用 evidence_status:
evidence_status = EVIDENCE_FOUND 查询成功且返回可使用证据
evidence_status = NO_EVIDENCE 查询成功但范围内没有证据
evidence_status = ERROR Tool 执行、参数、权限或清洗失败
EVIDENCE_FOUND 只表达 Tool 找到了证据,不声明证据支持任何分析或结论。NO_EVIDENCE 不是异常,只能支持明确标记为负向观察的 Analysis,不能推出问题不存在、问题已排除或系统健康。ERROR 不能伪装成无结果。SUPPORTED/UNSUPPORTED 只保留给 SemanticGuard。
5.3 通用 Tool Call Reference
每次可引用 Tool 调用只需要框架提供的一个稳定 ID:
tool_call_id
tool_call_id 由框架随 Tool Call 请求提供,Harness 不替换或重新生成该 ID,只负责在 Tool 执行前校验并贯穿 Agent response、Redis 调用记录、EvidenceGuard 和最终 Trace。该 ID 必须非空、满足长度/字符集约束,并在同一 Run 内唯一;重复、非法或缺失 ID 不得覆盖既有记录,直接进入 Tool ERROR。Redis Key 使用 runId + toolCallId 隔离不同 Run。SemanticGuard 不接收该 ID,只接收 Harness 从对应调用的 agent_result 组装的 verified evidence snapshot。
6. 工具结果清洗与投影层
本 Issue 使用中文“工具结果清洗与投影层”作为架构名称,不再使用 PTK/RTK 缩写。代码统一使用 ToolResultProjector,首版只有三个 Tool-specific 实现:RagResultProjector、QueryLogsResultProjector 和 MysqlResultProjector。不再拆分 Cleaner、Sanitizer、ContextBuilder 或通用转换 Pipeline。
统一处理顺序:
Agent Tool Request
-> Pre-Tool Guard
-> Mock Tool / MCP / DataSource
-> Raw Invocation Persistence
-> Tool-specific ToolResultProjector
-> Agent Result Persistence
-> Bounded Agent-facing Result
原则:
- 完整结果只在可信边界内保存;
- 当前版本不在 canonical evidence 持久化阶段做脱敏;Agent 仍只能接收有界的 Tool-specific 投影,原始 Redis 结果只能由 Harness 读取;
- Agent 只接收有界投影;
- Agent 看到的证据必须可回到 canonical result;
ToolResultProjector使用确定性代码,不调用 LLM 做二次摘要;- 不建立万能清洗算法;统一 Envelope,下沉 Tool-specific projection;
- 不把完整结果写入进程本地
refs/*.md作为主存储; - 清洗失败不能绕过 Harness。
7. RAG Tool 设计
7.1 ACI 描述
Tool 只说明:
查询内部知识库中的文档、接口说明、错误码和排障手册。
适用于稳定背景知识,不用于查询实时日志、指标或数据库状态。
输入 query:需要查询的问题或关键词。
不向 Agent 解释 L0/L1、category filter、fallback attempt、rerank 和 VectorStore/Milvus 实现。
7.2 Agent-facing 输出
{
"evidence_status": "EVIDENCE_FOUND",
"tool_call_id": "tool-123",
"query": "支付超时处理方式",
"evidence": [
{
"document_id": "payment-timeout-guide",
"source": "payment-timeout.md",
"title": "支付超时排查",
"breadcrumb": "支付系统 > 故障排查",
"excerpt": "当支付请求出现 ERR_TIMEOUT 时,应先检查……"
}
],
"returned_count": 1,
"truncated": false
}
7.3 Agent 不可见内容
- ContextPack;
- RetrievalTrace;
- RerankTrace;
- L0 hints;
- 原始候选列表;
- 向量原始分数;
- hit reasons;
- fallback attempts;
- Session 级 retrieved domains;
- 完整 Metadata;
- 完整文档正文。
这些内容按审计需要保存或删除,但不进入 Agent 上下文。
7.4 RAG 结果投影
- 删除 EvidenceBlock 与 ContextPack 的重复正文;
- 返回精确文档摘录,不使用 LLM 摘要;
- 相同文档/段落去重;
- 限制 evidence 数量、单条 excerpt 和整体字符数;
- 文档内容作为不可信证据数据,不执行其中的指令;
- 明确
NO_EVIDENCE和ERROR; - 删除 Session 隐式去重或改为显式 Run 内重复查询控制。
8. query_logs Tool 设计
8.1 ACI 输入
删除 Agent 可见的 getAvailableLogTopics。固定/动态 Topic 目录由 Harness 或 Tool Adapter 管理。
当前环境没有真实日志数据,本 Issue 首版继续使用现有 Mock 日志实现;不新增 MCP 或 Java CLS 适配器。Mock 只作为数据源实现,仍必须经过相同的 ACI、Harness、canonical evidence 和 Agent projection 链路。后续真实适配器若接入,必须复用本节 Contract,不在本 Issue 提前设计适配平台。
{
"topic": "APPLICATION",
"query": "order-service HikariCP connection timeout",
"lookback_minutes": 30
}
约束:
- topic 使用稳定逻辑枚举;
- query 表达日志检索目标;
- lookback 有默认值和最大值;
- region、TopicId、limit 由系统控制;
- Agent 不需要先调用发现工具;
- 当前仅验证本地 Mock 返回 Agent-facing Contract;真实 CLS/MCP 的兼容性留待后续接入时验证。
8.2 Agent-facing 输出
{
"evidence_status": "EVIDENCE_FOUND",
"tool_call_id": "tool-456",
"source_kind": "MOCK",
"scope": {
"topic": "APPLICATION",
"query": "order-service HikariCP connection timeout",
"start_time": "...",
"end_time": "..."
},
"match_count": 126,
"returned_count": 1,
"patterns": [
{
"count": 84,
"first_seen": "...",
"last_seen": "...",
"level": "ERROR",
"service": "order-service",
"example": "HikariPool connection is not available"
}
],
"events": [
{
"timestamp": "...",
"level": "ERROR",
"service": "order-service",
"message": "HikariPool connection is not available"
}
],
"truncated": true
}
8.3 日志结果投影
- 按稳定日志模板聚合重复事件;
- Pattern 保存 count、first_seen、last_seen 和代表样本;
- 额外保留少量按时间排序的事件,防止丢失时间线;
- 不固定删除 INFO,只根据 Query 和有界投影选择证据;
- 脱敏 Token、密码、Authorization Header 等敏感数据;
- 限制单条消息、Pattern 数、Event 数和整体字符数;
- 返回完整查询范围,防止将局部结果扩大为全局结论;
match_count表示查询范围内的原始匹配事件总数,returned_count表示events中实际返回的有界时间线事件数量;Pattern 数量直接由patterns数组长度表示;- Redis
raw_response不进入 Agent 上下文;持久化审计只保存脱敏参数、聚合元数据和有界结果摘要。 source_kind=MOCK必须保留在agent_result和诊断范围中;Harness 不得把 Mock 数据表述为生产实时事实。
9. 新增 MySQL Tool 设计
9.1 定位
新增一个只读、受限、可审计的外部 MySQL 查询 Tool。该 Tool 查询独立配置的业务/诊断数据源,不连接 Agent 自身的持久化数据库。
连接和安全策略由后续新增的独立配置文件提供。配置只暴露逻辑数据源 ID,不把真实连接信息交给 Agent:
- 数据源 URL、账号和密码不写入 Prompt、Skill 或 RAG 文档;
- 密码等敏感值使用环境变量或受控 Secret 注入,不在配置文件中硬编码;
- 配置定义允许的 schema/table/column、超时、最大行数和最大结果大小;
- Tool 通过逻辑数据源 ID 选择连接,不接受 Agent 传入 URL、账号、密码或任意 JDBC 参数;
- 不复用现有 Python 查询脚本的连接和执行方式;现有脚本中的硬编码凭据和可写执行分支必须清理。
表名、字段名、关联关系和业务语义后续从 Skill 或 RAG 召回的数据字典/查询知识中获得,MySQL Tool 本身不负责发现表结构。Skill 或 RAG 文档可以帮助 Agent 生成 SQL,但它们只是候选查询知识,不是授权来源。Harness 必须将 SQL AST 中实际使用的 schema/table/column 与独立配置的 allowlist 做最终校验;文档中出现但配置未授权的对象一律拒绝。
首版只提供静态“库、表、字段”三层白名单,不在本 Issue 设计租户或行级权限:
harness:
mysql-tools:
data-sources:
order_readonly:
jdbc-url: ${ORDER_MYSQL_JDBC_URL}
username: ${ORDER_MYSQL_USERNAME}
password: ${ORDER_MYSQL_PASSWORD}
default-schema: order_db
allowed-schemas:
order_db:
tables:
biz_order:
columns:
- order_id
- user_id
- payment_status
- created_at
- updated_at
payment_record:
columns:
- payment_id
- order_id
- channel
- status
- created_at
- updated_at
配置语义:
data-sourcesKey 是 Agent 可传入的逻辑数据源 ID,不是真实连接名;allowed-schemas下每个 Key 是允许访问的 MySQL 库/schema;未配置的库一律拒绝;- 每个库只允许
tables中显式列出的表;每张表只允许columns中显式列出的字段; - 首版不支持
*、正则、前缀匹配、继承或动态白名单;库、表、字段均为精确匹配; - SQL 未显式写库名时只能使用该数据源的
default-schema,且 default schema 必须同时存在于allowed-schemas; - JOIN 中每个表及每个投影、过滤、关联、分组、排序字段都必须通过对应库/表/字段校验;
- 未限定字段通过 JSqlParser 的表别名和作用域解析到唯一表;无法唯一解析时拒绝;
- 函数白名单、超时、行数和字节预算继续使用 Harness 的独立配置,不混入库表字段结构;
- 凭据只引用环境变量/Secret,不允许在配置文件中写明文。
固定调用链:
Skill/RAG 召回候选数据字典
-> Agent 使用明确表名和列名生成参数化 SQL
-> Harness 解析 SQL AST 并用独立配置做授权裁决
-> MySQL Tool 使用配置映射的只读数据源执行
-> Harness 清洗、持久化 canonical evidence 并投影给 Agent
MySQL Tool 不向 Agent 开放 SHOW TABLES、SHOW COLUMNS、DESCRIBE 或直接查询 information_schema 的元数据发现能力,避免绕过 Skill/RAG 知识边界和配置授权。
9.2 ACI 输入
初步推荐采用参数绑定的受限 SQL:
{
"data_source": "order_readonly",
"sql": "SELECT order_id, payment_status, updated_at FROM biz_order WHERE order_id = ?",
"params": ["order-123"]
}
只允许:
- 单条
SELECT; - 显式列名;
INNER JOIN和LEFT JOIN;WHERE、GROUP BY、HAVING、ORDER BY和受控LIMIT;- allowlist 中的聚合/标量函数,首版至少覆盖
COUNT、SUM、AVG、MIN和MAX; - 参数绑定;
- 独立配置中存在的逻辑数据源 ID;
- 上述静态 allowlist 内的 schema/table/column 精确匹配;
- Harness 注入或限制的行数、超时和字节预算。
必须阻止:
- INSERT/UPDATE/DELETE/REPLACE;
- CREATE/ALTER/DROP/TRUNCATE;
- 投影通配符
SELECT *和table.*,包括子查询中的通配符; WITH、子查询、UNION和窗口函数;CROSS JOIN、未在 allowlist 中的函数和无法识别的 AST 节点;SHOW TABLES、SHOW COLUMNS、DESCRIBE和information_schema元数据查询;- CALL;
- 多语句;
- SELECT INTO OUTFILE/DUMPFILE;
- FOR UPDATE;
- SLEEP/BENCHMARK/LOAD_FILE 等危险函数;
- 任意事务控制和写入路径。
COUNT(*) 作为聚合函数例外允许,因为它不返回全部字段;禁止的是字段投影通配符。若后续需要完全禁止星号,可将查询知识统一改为 COUNT(1),不影响整体校验结构。
9.3 JSqlParser 校验
首版引入 JSqlParser 将 Agent 生成的 SQL 解析为 AST。JSqlParser 只负责理解 SQL 结构,不连接或执行数据库;Harness 使用 AST visitor 执行 fail-closed 白名单校验:
解析且确认只有一个 Statement
-> 确认根节点是允许的 SELECT
-> 遍历所有 table/column/join/function/子表达式
-> 校验逻辑数据源和 schema/table/column/function allowlist
-> 校验占位符数量与 params 一致
-> 注入或压低 LIMIT
-> 通过 PreparedStatement 执行
解析失败、遍历到未知节点或无法证明符合允许子集时直接拒绝,不回退到正则、startsWith 或容错执行。具体依赖版本在实施阶段选择兼容 Java 17 的稳定版本,并通过固定安全用例验证后锁定。
安全边界必须同时包含:
- MySQL 专用只读账号;
- JSqlParser AST 校验,而不是 startsWith/正则;
- 独立配置提供的 schema/table/column allowlist;
- JDBC/数据库侧 read-only;
- JDBC
PreparedStatement和setMaxRows; - 查询超时;
- 最大行数、单元格长度和总字节数;
- 客户端断开时取消查询;
- 独立连接池或明确资源隔离。
9.4 Agent-facing 输出
{
"evidence_status": "EVIDENCE_FOUND",
"tool_call_id": "tool-789",
"columns": ["order_id", "payment_status", "updated_at"],
"rows": [
{
"order_id": "order-123",
"payment_status": "FAILED",
"updated_at": "2026-07-20 10:30:00"
}
],
"returned_count": 1,
"truncated": false
}
9.5 MySQL 结果投影
- 限制返回行数;
- 限制单元格、整行和整体结果大小;
- Blob、长文本和长 JSON 截断并标记;
- 按字段策略脱敏密码、Token、个人敏感信息;
- 每行保留结构化字段,整次调用通过
tool_call_id引用; - SQL 模板、脱敏参数、耗时、状态和有界结果摘要进入持久化审计;完整
raw_response只按 Redis canonical invocation 契约短期保存; - 连接字符串、账号、密码和内部堆栈不得进入 Agent 或审计明文。
10. 主 Agent 输出契约与低置信度处理
10.1 两类 Plan 必须分离
- Diagnosis Plan:主 Agent 开始排查时产生的 3–5 个高层步骤,通过 SSE status 提前输出;只描述准备查什么,不输出 Thought、完整 SQL、敏感参数或内部推理。
- Action Plan:诊断完成后给用户的下一步操作;与 Diagnosis Plan 不是同一个字段。
Diagnosis Plan 不参与 EvidenceGuard 或 SemanticGuard,因为它不是事实结论。执行中需要补充查询时只发送脱敏状态事件,不建立复杂 Plan 版本状态机。
10.2 已确认的 Agent Draft Schema
Diagnosis Agent 是诊断报告唯一的语义作者,负责生成结论、分析支撑、行动计划、长期建议、诊断范围与限制,并通过 Tool Call ID 选择各项分析所引用的证据。Harness 不生成、补充、删改或覆盖这些语义字段。
主 Agent 不输出不可安全裁剪的自由文本 answer,而是输出以下结构:
{
"conclusion": {
"text": "支付超时的主要原因是数据库连接池耗尽",
"based_on_analysis_ids": ["a1"]
},
"analysis": [
{
"analysis_id": "a1",
"kind": "NORMAL",
"text": "连接池活跃连接达到 50/50,并存在等待线程",
"tool_call_ids": ["tool-456"]
}
],
"action_plan": [
{
"action": "查询连接泄漏和长事务",
"based_on_analysis_ids": ["a1"],
"requires_human_confirmation": false
},
{
"action": "临时扩大连接池容量",
"based_on_analysis_ids": ["a1"],
"requires_human_confirmation": true
}
],
"recommendations": [
{
"text": "增加连接池等待线程和获取耗时告警",
"based_on_analysis_ids": ["a1"]
}
],
"limitations": {
"scope": "order-service,最近 30 分钟",
"missing_info": ["尚未取得慢 SQL 和长事务数据"]
}
}
字段语义:
conclusion:结论先行;引用支撑它的 analysis IDs,不重复绑定 Evidence;证据不足时允许为null。analysis:可独立验证的分析支撑。每一项必须有唯一 analysis ID、固定kind=NORMAL|NEGATIVE_OBSERVATION和至少一个tool_call_id,拒绝无来源分析。负向观察只能绑定NO_EVIDENCE,并严格限定为对应 Tool 的实际查询范围。action_plan:立即执行的下一步,必须通过based_on_analysis_ids绑定分析。危险或有副作用的操作必须标记requires_human_confirmation=true,Harness/HITL 策略拥有最终决定权。recommendations:长期治理建议,必须通过based_on_analysis_ids绑定分析;来自知识库或外部规范时,支撑该分析的 Tool Call 应来自 RAG。limitations.scope:由 Diagnosis Agent 根据本次实际查询范围生成;Harness 不改写或覆盖,SemanticGuard 负责校验它是否超出 verified evidence 中的真实 Tool scope。limitations.missing_info:尚未取得、导致结论受限的关键材料。
主 Agent 只向 Harness 输出 Tool Call IDs,不输出可自行编造的完整 Reference 内容。Harness 在调用 SemanticGuard 前使用这些 IDs 读取对应的 agent_result,仅传递已解析的来源文档/来源工具和证据内容。Harness 只把 Agent 已选择的 Tool Call ID 确定性展开为最终引用清单,展示来源、scope/timestamp 和精确 evidence excerpt/value;它不能选择额外来源、生成引用摘要或改变报告语义。
最终用户报告顺序固定为:
1. 结论
2. 分析支撑
3. 行动计划
4. 长期建议
5. 诊断范围与限制
6. 引用
10.3 释放语义
不使用主 Agent 自报的 0–1 数值置信度作为释放依据。
EvidenceGuard 失败
-> 不进入 SemanticGuard
-> 首次失败执行一次无 Tool 的结构化输出修复
-> 修复后重新执行 EvidenceGuard
-> 第二次仍失败则安全 Fallback
SemanticGuard SUPPORTED
-> Harness 原样释放主 Agent 的语义报告,并附加由其 Tool Call IDs 确定性展开的引用清单
SemanticGuard UNSUPPORTED/TIMEOUT/ERROR
-> 不释放主 Agent 草稿、分析、结论、行动或建议
-> Harness 返回固定安全 Fallback,只包含已验证来源、证据不足说明和诊断限制
固定安全 Fallback 由 Harness 通过确定性模板生成,不调用 LLM 或 Composer:
{
"type": "SEMANTIC_UNSUPPORTED",
"conclusion": null,
"message": "当前证据不足,无法确认根因",
"verified_sources": [
{
"source_type": "LOG",
"source": "APPLICATION (MOCK)",
"scope": "order-service,最近 30 分钟"
}
],
"limitations": ["语义校验未通过或暂不可用"],
"next_steps": ["补充当前缺失的数据后重新发起诊断"]
}
type是稳定的用户可见降级分类,只允许EVIDENCE_VALIDATION_FAILED、SEMANTIC_UNSUPPORTED和SEMANTIC_UNAVAILABLE;EVIDENCE_VALIDATION_FAILED表示 EvidenceGuard 修复后仍未通过,此时verified_sources必须为空;SEMANTIC_UNSUPPORTED表示证据已通过 EvidenceGuard,但不支持 Agent 报告;SEMANTIC_UNAVAILABLE表示 SemanticGuard 第二次超时、调用失败或输出无效;这两类可以返回本次已通过 EvidenceGuard 的来源;verified_sources只列出 EvidenceGuard 已通过的来源名称和查询范围,不包含主 Agent 的 Analysis、Conclusion 或推导文本;limitations使用稳定的用户可见原因分类,不直接展示 SemanticGuardreason、供应商错误或内部异常;next_steps只列出补充数据、缩小范围或稍后重试等安全动作,不包含主 Agent 未校验的 Action Plan/Recommendations;- 三种降级使用同一 Fallback Schema;内部可使用比
type更细的fallback_reason审计码,但不得将供应商错误或异常细节暴露给用户; - Fallback 通过最终
content事件发送并正常done,不作为 HTTP/SSE 系统错误。
11. 行为与协议变化
这是有意的不兼容重构:
- 删除同步
/api/chat响应模式; - 删除
/api/chat_stream; POST /api/chat改为唯一 SSE Chat 协议;- 删除
QuestionComplexity主路由; - Planner/Executor/Verifier/Composer 不再作为主链路多 Agent 角色;
- 不引入业务 StateGraph;
- Tool 参数和返回协议发生不兼容变化;
- RAG Session 隐式检索记忆删除或改为显式 Run 控制;
getAvailableLogTopics不再对 Agent 暴露;- 新增只读 MySQL Tool;
- 原 Gatekeeper 编排身份消失,能力进入 Harness EvidenceGuard;
- SemanticGuard 使用隔离上下文且不能形成 Agent 回路。
受影响的消费者包括前端 Chat、Controller tests、ChatService/Agent tests、Prompt contracts、Tool contract tests、Trace/Eval fixtures 和任何直接调用旧 Tool 方法的代码。
12. 分阶段实施计划
ISS-014 是总设计 Issue,不创建跨阶段共享的 OpenSpec change。以下 11 个实施切片各自执行一个完整、串行的 sm-flow:
| 阶段 | OpenSpec change | 状态 |
|---|---|---|
| 0 | single-react-design-freeze |
Completed;已归档,Git commit 见阶段历史 |
| 1 | single-react-aci-tool-contracts |
Completed;已归档 |
| 2 | single-react-harness-run-context |
Completed;已归档 |
| 3A | single-react-tool-invocation-store |
Completed;已归档 |
| 3B | single-react-rag-log-projections |
Completed;已归档 |
| 3C | single-react-mysql-readonly-tool |
Completed;已归档 |
| 4 | single-react-diagnosis-agent |
Completed;已归档 |
| 5 | single-react-evidence-semantic-guards |
Completed;已归档 |
| 6A | single-react-chat-application-usecase |
Completed;已归档 |
| 6B | single-react-chat-sse-cutover |
Completed;已归档 |
| 7 | single-react-cleanup-e2e |
Completed;已归档 |
每个 change 独立完成 Discover、Commit、Apply、阶段验收、Archive 和 Git commit;前一阶段 Archive 且提交后才允许启动下一阶段,不并行实施相邻阶段。
阶段 0:设计冻结与安全前置
目标:冻结本 Issue 的未决契约,不修改业务执行链。
任务:
- 将已确认的主 Agent Draft/Analysis/Conclusion Schema 固化为契约测试;
- 将 EvidenceGuard 一次无 Tool 输出修复和二次失败 Fallback 固化为契约测试;
- 冻结 SemanticGuard 报告级
SUPPORTED/UNSUPPORTED二元契约和固定安全 Fallback; - 将过程事件实时 SSE、最终报告门禁后释放的口径固化为协议;
- 将已确认的 SYSTEM_CHAT/KNOWLEDGE_QUERY/DIAGNOSIS 执行器映射固化为契约测试;
- 冻结 Harness 预算配置项、合法性校验和预算耗尽语义,不要求在本阶段冻结准确默认数值;
- 冻结
RetryPolicy/HarnessRetryPolicies/HarnessRetryExecutor、固定重试矩阵和隐藏重试禁用规则; - 冻结工具结果清洗与投影层及
ToolResultProjector代码命名; - 确认 Redis 调用记录保存
request + raw_response + agent_result,冻结单 Key、状态、默认 TTL/容量、非续期和 Harness-only ACL;当前版本明确暂不做持久化脱敏,并记录为后续安全增强项; - 确认 JSqlParser、首版 SQL 允许子集、MySQL allowlist 和只读账号方案;
- 移除并轮换现有脚本中的硬编码数据库凭据;
- 建立改造前 focused baseline。
验收:
- 所有阻塞性问题有明确决策;
- 无业务 Java 行为变化时可跳过单元测试,并记录理由;
- Security prerequisite 完成后才能进入 MySQL Tool 实现。
阶段 1:ACI Tool Contract 冻结
目标:先定义 Agent 能看到的工具界面,不实现 Agent 架构切换。
任务:
- 冻结 RAG、query_logs、query_mysql Request/Response Schema;
- 冻结调用生命周期
status=PROJECTING/READY/ERROR与结果语义evidence_status=EVIDENCE_FOUND/NO_EVIDENCE/ERROR,禁止混用; - 冻结
tool_call_id调用引用契约; - 缩短 Tool descriptions,移除内部实现泄漏;
- 为三类 Tool 添加独立契约测试;
- 确认现有 Mock Tool 的 Agent-facing Contract 和后续适配边界;本阶段不实现真实 MCP/CLS 适配器。
验收:
- Schema 和错误语义契约测试通过;
- Agent-facing 与 audit-facing 字段清单无交叉歧义;
- 不新增通用插件框架或工作流 DSL。
阶段 2:Harness Core 与 RunContext
目标:先建立后续 Tool、Agent、Guard 和入口共同依赖的显式运行上下文,不提前切换 HTTP/SSE 协议。
任务:
- 定义不可变
RunContext,显式携带sessionId/runId/deadline/cancellation/budget/retryPolicies; - 定义 Run 创建、预算计数、取消传播和终态管理的 Harness Core;
- 装配
HarnessRetryPolicies/HarnessRetryExecutor,关闭或纳管底层隐藏重试; - 定义 Redis Tool Call Key Factory 和单 Run 容量计数器,但不实现具体 Tool 投影;
- 所有后续边界通过参数或受控上下文对象显式接收
RunContext,禁止 ThreadLocal; - 使用 Fake Model/Tool 验证 deadline、取消、预算、重试记录和 Run 终态;
- 不修改 Controller 协议,不接入旧 ChatService 的临时适配层。
验收:
- RunContext 无隐式线程状态,可在同步/异步边界显式传播;
- 预算耗尽、取消和异常均产生唯一 Run 终态;
- 阶段 3A 可直接依赖 Harness Core,不需要临时耦合旧 ChatService;
- focused Harness Core tests 通过。
阶段 3A:Harness Tool Boundary 与 Canonical Invocation Store
目标:实现所有 Tool 共用的 Pre/Post 边界、canonical invocation store 和统一状态机,不在本阶段实现 Tool-specific 投影。
任务:
- 接收、校验并传播框架提供的
tool_call_id,覆盖缺失、非法、重复和跨 Run 引用测试; - 实现 Pre-Tool Schema/权限/只读/预算校验;
- 将
request + raw_response + agent_result写入同一 Redis Tool 调用记录;禁止仅依赖截断 preview 做验真; - 实现
PROJECTING/READY/ERROR生命周期和独立evidence_status,以及 TTL 不续期、单记录/单 Run 容量门禁和RESULT_TOO_LARGE; - 清洗失败显式 ERROR;
- 使用 Fake Tool/Projector 增加 invocation store、状态、TTL、容量、no-evidence 和 error tests。
验收:
- 未清洗原始结果不会进入 Agent;
- 每条返回证据可以回到当前 Run 的 canonical result;
- 只有当前 Run 的
READY记录可进入引用校验;EVIDENCE_FOUND、NO_EVIDENCE和ERROR的引用边界有契约测试,读取不会刷新 TTL,超限原始响应不会被静默截断; - 阶段 3B/3C 可复用同一 Tool boundary 和 invocation store,不复制状态机或 Redis 访问逻辑。
阶段 3B:RAG 与 query_logs 结果投影
目标:在阶段 3A 的统一边界上迁移两个现有证据 Tool,并完成 Agent-facing 有界投影。
任务:
- RAG:审计分离、去重复正文、精确摘录和上下文预算;
- query_logs:在现有 Mock 上实现统一 Contract、模板聚合、时间线抽样和脱敏;不实现真实 MCP/CLS 适配器;
- 两个 Tool 统一接入阶段 3A 的 canonical invocation store、
evidence_status和 Tool Call ID 契约; - 增加 Tool contract、truncation、no-evidence、负向观察和 error tests。
验收:
- RAG/日志原始结果不会未经投影进入 Agent;
- RAG/日志的长度、数量、查询范围和脱敏预算有 focused tests;
NO_EVIDENCE保留查询范围并且不会被表述为问题不存在;- 不实现真实 MCP/CLS 适配器。
阶段 3C:只读 MySQL Tool
目标:独立实现安全敏感的只读 MySQL Tool,避免与现有 Tool 迁移共享归档边界。
任务:
- 引入 JSqlParser,完成保守 SELECT 子集的 fail-closed AST 校验;
- 实现逻辑数据源、schema/table/column allowlist、参数绑定、只读执行、超时、取消和结果投影;
- 接入阶段 3A 的 canonical invocation store、预算、
evidence_status和 Tool Call ID 契约; - 使用隔离测试数据源完成 security、truncation、no-evidence 和 error tests。
验收:
- MySQL 的行数、单元格、总字节和查询超时预算有测试;
- MySQL write、WITH、子查询、UNION、投影通配符、未知 AST、allowlist bypass、timeout 和其他 security cases 被拒绝;合法显式列 SELECT 和
COUNT(*)通过。
阶段 4:单体 Diagnosis ReAct Agent
目标:将 Planner、Executor、内部 Verifier、自身 Composer 职责合并为一个完整 ReAct Agent;本阶段只通过内部应用用例和测试入口验证,不接管公开 Chat 入口。
任务:
- 创建单一 Diagnosis Agent Prompt;
- Agent 接收当前原始 Query 和可选的固定 Schema
previous_turn; - 使用框架已有 ReAct/tool loop,不手写 While;
- 接入 Harness 包装后的 Tool;
- 输出结构化 Draft/Analysis/Conclusion/Tool Call IDs;
- 证据不足时明确停止;
- 新 Agent 通过独立内部应用用例接入 Harness,公开
/api/chat暂不切换; - 旧多 Agent 主链路在阶段 5/6A/6B 完成释放门禁和切换前继续保留,禁止阶段 4 直接删除导致公开入口失效;
- 保留 Run/AgentStep/ToolInvocation 基础审计。
验收:
- 新诊断链路只有一个拥有工具循环的 Agent,且只在内部测试入口运行;
- Diagnosis Agent 整体、单次模型调用和 Tool 调用均无 Harness 自动重试;
- 无 Planner/Executor/Composer Agent 间 JSON 搬运;
- 无外层 Graph;
- focused Agent/tool-loop/output tests 通过;
- 工具、模型和上下文预算均有可配置且被 Harness 强制执行的确定性上限。
阶段 5:EvidenceGuard、SemanticGuard 与释放策略
目标:在不恢复 Graph 的前提下完成物理验真和独立语义审查。
任务:
- EvidenceGuard 校验 Draft Schema、Analysis ID 唯一性、Analysis
kind、报告内部引用完整性,以及当前 Run、Tool Call ID、调用生命周期、evidence_status和agent_result; - 格式/物理验真首次失败只执行一次无 Tool 输出修复,禁止重新运行诊断或 Tool 循环;
- 修复后再次执行 EvidenceGuard,仍失败则直接安全 Fallback;
- 构造 SemanticGuard 最小隔离输入;
- 复用系统当前唯一 ChatModel,实现由同一 Harness 受限控制的无工具、无记忆、单轮 SemanticGuard Agent;
- Harness 控制 SemanticGuard 输入预算、超时、取消、一次重试、结构校验、审计和降级释放,禁止 Agent 自身重试或形成回路;
- 实现 SUPPORTED/UNSUPPORTED/TIMEOUT/ERROR release policy;
- 确保 SemanticGuard 不回调主 Agent;
- 增加 duplicate/missing Analysis ID、empty evidence reference、fabricated Tool ID、cross-run、failed call、missing agent_result、
NO_EVIDENCE负向观察、无证据过度推断、unsupported report、timeout、Fallback type 和verified_sources规则测试。
验收:
- 重复/缺失 Analysis ID、空引用、伪造/跨 Run/失败调用或缺失
agent_result不会进入 SemanticGuard; - SemanticGuard 只看到 verified evidence;
- SemanticGuard 与主 Agent 复用同一模型配置,但不共享上下文、工具或记忆;
- EvidenceGuard 修复最多一次,且修复调用没有 Tool;
- 第二次物理验真失败直接 Fallback;
- 超时和失败不释放草稿;
- UNSUPPORTED 不会释放或局部裁剪主 Agent 草稿;
- Fallback 不包含主 Agent 草稿或 SemanticGuard 内部 reason,并以
content + done正常结束; - 无隐式 retry loop。
阶段 6A:Chat Application Use Case、意图路由与 previous turn
目标:在不切换公开协议的前提下完成 Chat 应用用例、三类意图路由和上一安全回合组装。
任务:
- 会话、历史和执行生命周期下沉应用用例,由应用用例创建并传播阶段 2 定义的
RunContext; - 引入三分类轻量 Intent Router;
- Intent Router 技术失败或非法枚举只由 Harness 重试一次,第二次失败不默认进入 Diagnosis;
- 由应用用例确定性组装 Diagnosis Agent 的上一个已安全发布回合,不调用上下文摘要模型;
- 原始 Query 原样进入选定执行路径;
- 新应用用例只通过内部测试入口验证,公开
/api/chat和/api/chat_stream暂不切换;
验收:
- SYSTEM_CHAT、KNOWLEDGE_QUERY 和 DIAGNOSIS 路由映射及失败语义通过 focused tests;
- previous turn 只来自同一 Session 上一个符合安全发布条件的结构化结果;
- 应用用例显式传播同一个 sessionId/runId,不依赖 Controller 提供模型和工具;
- 阶段 6B 可直接完成协议切换,不需要重写应用用例。
阶段 6B:唯一 /api/chat SSE 切换
目标:将公开 Chat 原子切换到阶段 6A 的应用用例,并完成破坏性 SSE 协议迁移。
任务:
- Controller 只负责请求校验、HTTP/SSE 协议和连接生命周期;
- 仅在阶段 5 的 EvidenceGuard、SemanticGuard 和 Release Policy 全部通过后,原子切换
/api/chat到新链路; - 切换前后均不得出现未经过释放门禁的公开 Draft;
- 只保留
POST /api/chat; - 删除
/api/chat_stream和同步兼容路径; - 同步迁移前端 Chat 消费者到固定 SSE 事件契约;
- 按固定顺序输出
metadata -> status* -> content|failure -> done,终态只允许SUCCESS/FALLBACK/FAILED; - 处理断开、取消、超时和 Run 终态;
- 不创建 Controller 自有无界线程池。
验收:
- Controller 不依赖 ChatModel/ToolCallbackProvider;
- 同一请求 sessionId/runId 在 SSE、日志和数据库一致;
- 禁止输出 Thought、Prompt、raw Tool data 和内部错误;
- SSE 事件 Schema、顺序、互斥、唯一终态、客户端取消和错误单元/集成测试通过。
阶段 7:物理清理、文档与最终验收
目标:删除旧架构和完成一次最终端到端验收。
任务:
- 物理删除被替代的旧 Agent、Hook、ThreadLocal、路由和死代码;
- 删除旧 Tool compatibility contract 和过时测试;
- 不保留注释旧代码或双轨开关;
- 更新 MVP 架构、接口、Tool、Trace 和安全文档;
- 审查 ISS-012/ISS-013 是否可归档或被本 Issue 吸收;
- 运行最终 Maven 启动 E2E;
- 核对
logs/; - 使用安全的数据库查询方式核对 exact sessionId/runId;
- 验证 Token、工具次数、SSE、EvidenceGuard、SemanticGuard 和 Fallback。
- query_logs 和外部 MySQL Tool 使用现有 Mock/测试数据完成契约、安全与编排验收;本阶段不声称完成真实 CLS 或生产业务 MySQL 数据源的 live E2E。
验收:
- 旧多 Agent/Graph/伪流式入口不存在;
- 全部 focused tests 通过;
- 最终 E2E、日志和数据库证据归档;
- 无硬编码凭据、临时 refs 文件、未解释兼容层和死代码;
- 工作区改动范围与本 Issue 一致。
13. 阶段门禁
实施时每阶段必须满足:
- 当前阶段范围和行为变化已确认;
- 需要的单元/契约测试已补充并通过;无必要时记录跳过理由;
- 当前独立 OpenSpec change 已完成 Apply 和阶段验收,证据已归档;
- 当前 change 已 Archive,OpenSpec 索引/规格同步完成;
git diff精确审查完成,当前阶段已独立 Git commit;- 前一阶段 Archive 和 Git commit 完成后才进入下一阶段,不并行实施相邻阶段;
- 阶段 0–6B 不运行完整 live E2E;阶段 7 全部完成后统一执行 Maven E2E、日志和数据库核验。
- 阶段 4 和 6A 不得接管公开入口;公开入口切换只能发生在阶段 6B,且必须依赖阶段 5 的释放门禁。
14. 总体验收标准
- 复杂诊断只存在一个拥有工具循环的 Diagnosis ReAct Agent。
- 不存在业务 StateGraph 或 Planner/Executor/Composer 多 Agent 主链路。
- Harness 不承担业务推理,不演变为工作流引擎。
- EvidenceGuard 是 Harness 内的确定性能力,不是独立编排节点。
- SemanticGuard 使用完全隔离上下文,无工具、无记忆、无回调循环。
- SemanticGuard 不使用未校准数值置信度控制在线释放。
- 所有 Agent-facing evidence Tool 符合 ACI 状态和 Tool Call ID 契约。
- RAG 不再向 Agent 返回 ContextPack/Trace/Rerank 等审计数据。
- query_logs 不要求 Agent 先调用 Topic discovery,且返回聚合、抽样、脱敏结果。
- MySQL Tool 只读、安全解析、参数绑定、allowlist、超时和结果上限全部生效。
- Tool 原始结果不会未经有界投影进入 Agent 上下文;Redis canonical evidence 当前可暂不脱敏,但不得被 Agent 直接读取。
- Redis 每次 Tool Call 单 Key 保存,状态、TTL、容量、ACL 和日志禁泄漏规则均有测试。
- 每个 Tool Call ID 可以按 exact runId 和调用记录状态验真,并取得对应
agent_result。 - 只保留一个
/api/chatSSE 接口。 - SSE 不输出 Thought、Prompt、原始 Tool 载荷和未验证结论。
- 客户端断开、模型/Tool/SemanticGuard 超时均有明确取消和 Run 终态。
- 所有重试由 Harness 按类型化策略装配和记录,无 SDK/HTTP/数据库隐藏重试或整个 Diagnosis Agent 重跑。
- 最终 E2E 能按 sessionId/runId 对齐 SSE、日志、AgentStep、ToolInvocation 和最终答案。
- Token、工具调用、Tool 投影、Redis TTL 和总延迟预算均来自集中配置,并有可验证的强制上限和耗尽原因。
- 11 个 OpenSpec changes 均已独立 Archive,并分别对应一个范围清晰的 Git commit。
- 不保留旧兼容分支、注释代码、本地 refs 卸载和硬编码凭据。
15. 非目标
- 不构建多 Agent 协作平台;
- 不引入业务 StateGraph;
- 不实现 Harness 插件市场、通用 Pipeline DSL 或策略语言;
- 不保留旧同步 Chat、
/chat_stream或旧 Tool Contract; - 不输出或持久化 Chain of Thought;
- 不使用本地
refs/*.md作为 Tool 原始结果主存储; - 不向 Agent 暴露 Redis Client、Redis Tool、key、连接信息或完整调用记录;
- 不提供历史来源召回 Tool、通用 Memory Tool 或完整上下文恢复能力;
- 不让
ToolResultProjector调用 LLM 生成证据摘要; - 不允许 MySQL 写操作;
- 不允许 MySQL Tool 查询 Agent 自身的持久化数据库;
- 不允许 Skill/RAG 文档绕过独立配置授予数据库访问权限;
- 本 Issue 不实现 MySQL 租户、行级权限或动态数据授权;首版只落实库/表/字段白名单;
- 不提供
query_metricsTool;指标查询和其 ACI/结果投影设计另立后续 Issue; - 不用调低模型 maxTokens 代替上下文治理;
- 不因 SemanticGuard 超时而释放未验证草稿;
- 不以追加“人工复核”警告替代删除不受支持的强结论。
16. 设计 Review:已确认决策
以下决策已经完成讨论,作为阶段 0 契约冻结的输入:
已确认项
-
Canonical Evidence 存储:已确认 Redis 每个 Tool Call 使用单 Key
superbiz:harness:tool-call:{runId}:{toolCallId},保存request/raw_response/agent_result/status/evidence_status/timestamps。status=PROJECTING/READY/ERROR表示调用生命周期,evidence_status=EVIDENCE_FOUND/NO_EVIDENCE/ERROR表示结果语义;NO_EVIDENCE只能支持限定查询范围的负向观察。默认 TTL 2 小时且不续期,默认单记录 1MB、Agent Result 64KB、单 Run 5MB;Harness-only ACL,原始超限进入RESULT_TOO_LARGE,不静默截断。当前版本暂不做持久化脱敏,默认值后续按运行数据校准。 -
SemanticGuard 模型与超时:已确认复用当前唯一 ChatModel,由同一 Harness 控制;首次超时/失败/结构无效时使用相同证据快照重试一次,仍失败则安全降级。单次和总超时提取为集中配置,初期不冻结具体数值。
-
SemanticGuard 完整报告审查:已确认接收原始 Query、Diagnosis Agent 完整 Draft 和 EvidenceGuard 生成的 verified evidence snapshot;校验结论、分析、行动计划、建议、限制和范围是否语义成立。它不访问 Redis、不重复验真、不接收 Diagnosis Plan/Tool 过程,也不生成、总结、改写或补充用户报告。Diagnosis Agent 是唯一报告作者,Harness 只发布原 Draft 或固定 Fallback。
-
预算数值:已确认主 Agent、Tool、SemanticGuard 和 Redis 的预算全部集中配置,首版使用可调默认值,不在设计阶段冻结未经数据验证的数值;Harness 负责强制执行和记录耗尽原因。
-
RAG 投影预算:最大 Evidence 数、单条 excerpt 和总字符预算提取为同一配置;默认值根据后续 Trace 和截断数据校准。
-
日志投影预算:最大 Pattern/Event 数、消息长度、lookback 和总字符预算提取为同一配置;聚合指纹规则仍属于 Tool 行为契约,不作为数值配置问题处理。
-
真实日志适配:已确认本 Issue 首版只沿用现有 Mock;真实 MCP/Java CLS 适配器和生产接入另立后续 Issue,当前只保留可复用的 Agent-facing Contract。
-
MySQL SQL Parser:已确认使用 JSqlParser。首版只允许显式列的单条 SELECT、INNER/LEFT JOIN、常用筛选/分组/排序和函数 allowlist;允许
COUNT(*),禁止 WITH、子查询、UNION、窗口函数、投影通配符和未知 AST;Harness 注入或压低 LIMIT,并以 PreparedStatement、只读账号、超时和setMaxRows兜底。 -
MySQL allowlist 配置:已冻结为逻辑数据源下的静态
allowed-schemas -> tables -> columns三层精确白名单,并配置default-schema。不支持通配符、正则或动态授权;凭据通过环境变量/Secret 注入。本 Issue 暂不展开租户和行级权限。 -
对话历史:已确认只使用显式极简上下文,不提供完整历史或 Redis 历史召回。Intent Router 接收当前
query + last_intent + last_user_query。diagnosis_run持久化intent/release_outcome/published_result,只有同 Session 最近一个DIAGNOSIS + SUCCESS的安全结构化结果可进入previous_turn;Fallback、失败和取消均排除。Diagnosis Agent 接收当前 Query 和固定字段user_query/published_conclusion/scope/limitations/source_documents,由 Harness 确定性组装并按配置截断,不调用 LLM 摘要,不复用旧日志/MySQL 结果。SemanticGuard 只接收当前诊断的 Query、完整 Draft 和 verified evidence snapshot。 -
重试边界:已确认由 Harness 装配类型化重试策略。Router 与 SemanticGuard 的技术失败最多重试一次;Diagnosis Agent、主模型调用和 Tool 调用不自动重试;EvidenceGuard 只执行一次显式无 Tool 结构修复;禁止隐藏和嵌套重试。
-
阶段依赖顺序:已调整为阶段 0 设计冻结、1 ACI Contract、2 Harness Core/RunContext、3A invocation store、3B RAG/日志投影、3C MySQL Tool、4 Diagnosis Agent、5 Guards、6A Chat 应用用例、6B SSE 切换、7 清理/E2E。阶段 3A 起只依赖显式 RunContext,不临时耦合旧 ChatService。
-
OpenSpec/sm-flow 映射:已确认 ISS-014 作为总 Issue,11 个实施切片各自使用独立 OpenSpec change 和完整串行 sm-flow;每阶段 Archive 并 Git commit 后才能进入下一阶段,最终 E2E 只在阶段 7 执行。
-
EvidenceGuard 与报告作者边界:Diagnosis Agent 是唯一语义作者;EvidenceGuard 作为 Harness 内的确定性能力校验 Draft Schema、Analysis 引用完整性和当前 Run Tool Call 真实性,不判断语义推导。Harness 只确定性展开 Agent 已选择的引用,不改写报告。
-
SemanticGuard evidence snapshot:Harness 将已验证 Tool Calls 的有界
agent_result按analysis_id分组,不注入tool_call_id、Redis 或raw_response,SemanticGuard 据此校验 Analysis、Conclusion、Action Plan、Recommendations、Scope 和 Limitations。 -
Fallback 与 SSE:Fallback 使用统一 Schema 和三种稳定
type;EvidenceGuard 失败时verified_sources=[]。SSE 首版固定为metadata -> status* -> content|failure -> done,不实现断线续传、事件重放、轮询或 WebSocket。
Review 结论
当前架构和关键契约已经足够进入阶段 0,不再继续增加设计组件。阶段 0 负责把上述决策固化为 OpenSpec、Schema、契约测试和安全基线;完成 Archive 与 Git commit 前不得进入阶段 1。
剩余风险属于实施期验证项,而不是继续扩展架构的理由:
- SemanticGuard 是否能稳定执行报告级二元语义校验;
- canonical evidence 是否按 Redis TTL 和访问边界承载完整调用结果;
- Harness 是否能严格执行集中预算,并为耗尽和截断提供可观察原因;
- MySQL Tool 的 AST 白名单、只读、超时和容量边界能否通过 Mock/隔离测试数据源的安全用例;真实生产数据源接入另立后续 Issue 验证。
实现中不得为这些风险创建 HarnessPlugin、Graph、通用 Tool DSL 或大量预留接口;按各阶段最小范围实现并通过门禁验证。
17. 相关文件
mvp/issues/active/ISS-012-executor-token-budget-and-context-growth.mdmvp/issues/active/ISS-013-chat-entry-decoupling-and-sse.mdmvp/disscus/plan.mdsrc/main/java/com/superbiz/agent/controller/ChatController.javasrc/main/java/com/superbiz/agent/service/ChatService.javasrc/main/java/com/superbiz/agent/hook/AgentLoggingHook.javasrc/main/java/com/superbiz/agent/hook/TokenTrackingChatModel.javasrc/main/java/com/superbiz/agent/hook/VerifierInputHook.javasrc/main/java/com/superbiz/agent/service/ExecutorGatekeeperService.javasrc/main/java/com/superbiz/agent/service/ToolInvocationRecorder.javasrc/main/java/com/superbiz/agent/tool/LookupKnowledgeTool.javasrc/main/java/com/superbiz/agent/agent/tool/QueryLogsTools.javasrc/main/resources/prompts/chat-planner-prompt.mdsrc/main/resources/prompts/chat-executor-prompt.mdsrc/main/resources/prompts/chat-verifier-prompt.mdsrc/main/resources/prompts/chat-composer-prompt.mdscripts/query_mysql.pymvp/architecture/current-mvp-architecture.md