diff --git a/mvp/engineering/harness/Harness Tool 调用链-一次工具调用的完整旅程.md b/mvp/engineering/harness/Harness Tool 调用链-一次工具调用的完整旅程.md
new file mode 100644
index 0000000..57c25fe
--- /dev/null
+++ b/mvp/engineering/harness/Harness Tool 调用链-一次工具调用的完整旅程.md
@@ -0,0 +1,203 @@
+# Harness Tool 调用链:一次工具调用的完整旅程
+
+**更新日期**:2026-08-03
+**主题**:从「模型决定调用工具」到「模型收到观察」的运行时完整链路——拦截器 → invoke → Adapter → ToolBoundary → 返回 → 二次加工 → ToolCallResponse
+**结构篇**:[Harness tool 域代码学习笔记-工具的注册调用与执行链路](Harness%20tool%20域代码学习笔记-工具的注册调用与执行链路.md)(讲装配/注册/静态结构)
+**本文**:动态时序(一次调用怎么跑完)
+
+## 1. 旅程全景(一张图)
+
+```mermaid
+sequenceDiagram
+ participant M as 模型
+ participant F as 框架 ReactAgent
+ participant I as HarnessToolInterceptor(per-Run)
+ participant ET as HarnessEvidenceTools(单例)
+ participant AD as RagToolAdapter(单例)
+ participant TB as ToolBoundary(单例)
+ participant P as DiagnosisProgressTracker
+
+ rect rgb(240, 248, 255)
+ Note over M,I: 阶段 A:模型决定 → 拦截器(执行前)
+ M->>F: 输出 tool_call(工具名 + 参数 JSON)
+ F->>I: 回调 interceptToolCall(request, handler)
+ I->>I: ① supports 注册检查
+ I->>ET: ② parse(typed 严格契约)
+ ET-->>I: ParsedAgentToolCall(previous_observation + input)
+ I->>P: ③ 协议校验(pending 评价)+ 判重
+ end
+
+ rect rgb(255, 250, 240)
+ Note over I,TB: 阶段 B:invoke → 执行(backend + 投影)
+ I->>ET: ④ invoke(context, toolName, toolCallId, args)
+ ET->>AD: bridge 闭包 → adapter.execute(context, envelope)
+ AD->>TB: boundary.execute(context, envelope, executor, projector)
+ TB->>TB: ⑤ 五阶段:preflight/预算/begin → executor 跑 backend → 校验 → projector 投影 → markReady
+ TB-->>I: ToolBoundaryResult(READY/ERROR)
+ end
+
+ rect rgb(245, 255, 245)
+ Note over I,M: 阶段 C:返回 → 模型(执行后)
+ I->>I: ⑥ 双源校验(controlView 重读 evidence_status)
+ I->>P: ⑦ recordCompleted(NO_EVIDENCE 立即 NO_GAIN / FOUND 挂 pending)
+ I->>I: ⑧ modelObservation 加工(有界观察 + stop_required/reason)
+ I-->>F: ToolCallResponse.of(toolCallId, toolName, observation)
+ F-->>M: observation 作为本轮 tool 结果
+ end
+```
+
+**三个阶段**:A 执行前(模型决定→门禁)→ B 执行中(invoke→backend→投影)→ C 执行后(校验→记账→成型)。
+
+---
+
+## 2. 阶段 A:模型决定 → 拦截器(执行前)
+
+### 2.1 模型怎么知道有这个工具
+
+```
+模型 → callbacks 里看到工具(名字+描述+Schema)→ 决定调用 lookup_knowledge
+ → 输出 tool_call JSON(工具名 + 参数)
+```
+
+工具名是**模型决定的**——框架把模型输出包成 `ToolCallRequest`(含 toolName + arguments),回调拦截器。
+
+### 2.2 拦截器的三道执行前门
+
+```mermaid
+flowchart LR
+ A["① supports(toolName)?"] -->|"否(非证据工具)"| X["handler.call 透传"]
+ A -->|"是"| B["② parse:typed 严格契约
FAIL_ON_UNKNOWN_PROPERTIES + FAIL_ON_TRAILING_TOKENS"]
+ B -->|"违规"| Y["协议处理(不执行)"]
+ B --> C["③ 协议校验(pending 评价)+ 判重"]
+ C -->|"重复"| Z["recordDuplicateScope(不执行)"]
+ C -->|"通过"| D["进入阶段 B:invoke"]
+```
+
+关键:**不是「拿到名字就执行」**——parse(模型输出必须精确匹配 `RagToolCall{previous_observation, input}`,多一个字段都炸 INVALID_ENVELOPE)、协议校验、判重,三道门不通过都不执行 backend。
+
+---
+
+## 3. 阶段 B:invoke → 执行(backend + 投影)
+
+### 3.1 invoke 的委托链
+
+```
+I.invoke(context, "lookup_knowledge", "call-1", args)
+ → ET.invokers.get("lookup_knowledge") ← 注册表取 bridge 闭包
+ → bridge lambda:adapter.execute(context,
+ new ToolCallRequestEnvelope(runId, "call-1", "lookup_knowledge", args, true, true))
+ → ragAdapter.execute(context, envelope)
+ → boundary.execute(context, envelope, executor, projector)
+```
+
+**envelope 是 bridge 里现造的**:`authorized=true, readOnly=true` 写死——每个进 ToolBoundary 的信封都声明「已授权 + 只读」。
+
+### 3.2 Adapter 组装两个函数(接线员)
+
+```java
+return boundary.execute(context, envelope,
+ // executor:跑 backend 拿 raw(LookupResult 序列化成 JSON 文本)
+ ignored -> objectMapper.writeValueAsString(legacyExecutor.execute(request.query())),
+ // projector:raw → 有界契约 + evidenceStatus
+ raw -> projector.project(request, envelope.toolCallId(), raw));
+```
+
+| 端口 | 干什么 | 产物 |
+|---|---|---|
+| `executor` | 调具体后端 | rawResponse(JSON 文本,执行链「货币」) |
+| `projector` | 净化定型 | ProjectedToolResult(agentResult, evidenceStatus) |
+
+**模型永远看不到 raw**——raw 只用于校验、落 canonical、投影。
+
+### 3.3 ToolBoundary 五阶段
+
+```mermaid
+flowchart TD
+ A["① preflight + Tool 预算 + request bytes → begin(PROJECTING)"]
+ B["② executor.execute(requestJson) → backend raw"]
+ C["③ raw 大小校验 + Run bytes 预留"]
+ D["④ projector.project(raw) → 有界 agent_result + evidenceStatus"]
+ E["⑤ agent_result 校验 + bytes → markReady(READY) 或 markError(ERROR)"]
+ A --> B --> C --> D --> E
+```
+
+返回 `ToolBoundaryResult(READY/ERROR)`——PROJECTING 永不外泄。
+
+---
+
+## 4. 阶段 C:返回 → 模型(执行后)
+
+### 4.1 拦截器的二次加工(不是直接返回)
+
+```mermaid
+flowchart LR
+ A["ToolBoundaryResult"] --> B{"status == READY?"}
+ B -->|"否"| E1["error observation
BUDGET_EXHAUSTED 额外 markBudgetLimitReached"]
+ B -->|"是"| C["⑥ 双源校验:controlView 重读 evidence_status"]
+ C -->|"不一致"| E2["OBSERVATION_CONTRACT_MISMATCH 拒绝"]
+ C -->|"一致"| D["⑦ recordCompleted
NO_EVIDENCE → 立即 NO_GAIN
FOUND → 挂 pending"]
+ D --> F["⑧ modelObservation 加工
(有界观察 + stop_required/reason)"]
+ F --> G["ToolCallResponse.of(...) → 框架 → 模型"]
+```
+
+### 4.2 双源校验(自洽性防线)
+
+```text
+源1:result.evidenceStatus() ← Projector 投影时计算的声明值
+源2:controlView(agentResult).evidenceStatus() ← 从 agent_result 内容重读
+一致 ? 通过 : OBSERVATION_CONTRACT_MISMATCH 拒绝
+```
+
+防止「声明有证据但内容空 / 声明无证据但内容有」的不一致状态进入 progress 记账。
+
+### 4.3 给模型的对象形态
+
+```
+ToolCallResponse.of(toolCallId, toolName, observation)
+ observation = 有界观察:
+ 正常结果:脱敏后的契约内容(可能裁剪)
+ 饱和时: 附加 stop_required:true + reason
+ 协议错误:repair_required:true + violation_type/期望ID/指令
+```
+
+框架把 observation 作为本轮 tool 结果给模型——**模型下一轮读取它,决定继续调用(带评价)还是输出 Draft 收尾**。
+
+---
+
+## 5. 旅程的衔接点(模型视角的闭环)
+
+```mermaid
+flowchart LR
+ A["模型调工具"] --> B["观察(有界契约)"]
+ B --> C{"模型决定"}
+ C -->|"继续"| D["下次 Tool Call + previous_observation 评价"]
+ C -->|"收尾"| E["输出 Draft → Release 发布"]
+ D --> B
+```
+
+**progress 协议的闭环**:模型每次继续调用,都要在 Envelope 里回带对上一轮的 GAINED/NO_GAIN 评价——这就是拦截器 ③ 校验的 pending 逻辑(可回看 progress 笔记)。
+
+---
+
+## 6. 关键点总结
+
+| 阶段 | 关键认知 |
+|---|---|
+| A 执行前 | 工具名是模型决定的;parse 是 typed 严格契约(输出必须匹配 Schema);三道门不通过不执行 |
+| B 执行中 | executor/projector 是 Adapter 组装进 boundary 的**参数**;执行链货币是 JSON 文本;模型永远看不到 raw |
+| C 执行后 | 拦截器不直接返回——双源校验 + progress 记账 + modelObservation 成型;ToolCallResponse 才是模型拿到的对象 |
+
+## 7. 面试 30 秒说法
+
+> "一次工具调用的完整旅程分三段:执行前,模型从 callbacks 看到工具并决定调用,拦截器做 supports 分流、typed 严格 parse、协议校验和判重——三道门不通过都不执行 backend;执行中,invoke 经 bridge 到 Adapter,Adapter 把 executor(跑 backend 拿 raw)和 projector(raw 投影成有界脱敏契约)组装进 ToolBoundary 的五阶段门禁,返回 ToolBoundaryResult;执行后,拦截器不直接返回——先双源校验 evidence_status,再 recordCompleted 记进度,再 modelObservation 加工成有界观察,最后包装成 ToolCallResponse 给模型。模型看到的永远是脱敏后有界的观察,raw 只进 canonical 供审计验真。"
+
+## 8. 代码位置索引
+
+| 环节 | 文件 |
+|---|---|
+| 拦截器(A/C 阶段) | `src/main/java/com/superbiz/agent/harness/agent/HarnessToolInterceptor.java` |
+| 注册表 + parse + invoke | `src/main/java/com/superbiz/agent/harness/agent/HarnessEvidenceTools.java` |
+| Adapter 组装(B 阶段) | `src/main/java/com/superbiz/agent/harness/tool/adapter/RagToolAdapter.java` |
+| 五阶段门禁 | `src/main/java/com/superbiz/agent/harness/tool/boundary/ToolBoundary.java` |
+| 双源校验 + 观察成型 | `src/main/java/com/superbiz/agent/harness/agent/ToolResultViewProjector.java` |
+| 模型观察形态 | `src/main/java/com/superbiz/agent/harness/agent/ToolControlView.java` |
diff --git a/mvp/engineering/harness/Harness tool 域代码学习笔记-工具的注册调用与执行链路.md b/mvp/engineering/harness/Harness tool 域代码学习笔记-工具的注册调用与执行链路.md
new file mode 100644
index 0000000..bcd22b7
--- /dev/null
+++ b/mvp/engineering/harness/Harness tool 域代码学习笔记-工具的注册调用与执行链路.md
@@ -0,0 +1,382 @@
+# Harness tool 域代码学习笔记:工具的注册、调用与执行链路
+
+**更新日期**:2026-08-03
+**主题**:tool 域 49 个文件的完整链路——装配 → 注册 → 调用 → 执行 → 返回,拆分阶段讲,最后合并
+**设计视角**:[Harness 组件全景-职责-设计原因与边界](Harness组件全景-职责-设计原因与边界.md) §7 Tool
+**代码视角**:[Harness progress 代码学习笔记](Harness%20progress%20代码学习笔记-从拦截器五道门到唯一发布点.md)(progress 域衔接,本笔记是 tool 域)
+
+## 1. 定位:tool 域管什么
+
+**职责**:工具如何安全执行、保存真相并只暴露必要内容。
+
+| 问题 | 不解决会怎样 | 催生的层 |
+|---|---|---|
+| 每个 Adapter 自己写授权/预算/审计 → 漂移 | 三个工具三种行为 | **Boundary**(统一门禁) |
+| raw 结果直接给模型 | 敏感数据泄露、超大响应、无结构 | **Projector**(有界投影) |
+| 模型可能编造证据 | 结论无法验真、审计黑洞 | **Store**(canonical 真相) |
+| MySQL 查询不可控 | 写库、删库、危险 SQL | **MySQL 沙箱**(只读红线) |
+
+**49 文件分 6 组**:
+
+| 组 | 数量 | 角色 |
+|---|---|---|
+| Contract | 18 | 跨层类型化语言(Call/Request/Result) |
+| Boundary | 7 | 统一门禁(ToolBoundary + 配套) |
+| Projection | 3 | raw → 有界 agent 契约 |
+| Store | 9 | canonical 真相持久化 |
+| Adapter | 3 | 接线(boundary + 后端 + 投影器) |
+| MySQL 沙箱 | 9 | 只读执行 + fail-closed 校验 |
+
+**核心设计**:几乎不用继承——用「接口 + 组合 + 函数式接口」三件套解耦。
+
+---
+
+## 2. 阶段一:装配(config → bean 注入链)
+
+### 2.1 注入链
+
+```mermaid
+flowchart TD
+ subgraph 底层
+ R["RedisCanonicalInvocationStore"]
+ B["ToolBoundary"]
+ RP["RagResultProjector"]
+ LP["QueryLogsResultProjector"]
+ end
+ subgraph 中层
+ RA["RagToolAdapter"]
+ QA["QueryLogsToolAdapter"]
+ MA["MysqlToolAdapter"]
+ end
+ subgraph 顶层
+ ET["HarnessEvidenceTools"]
+ end
+ R --> B
+ B --> RA
+ B --> QA
+ B --> MA
+ RP --> RA
+ LP --> QA
+ ET --> RA
+ ET --> QA
+ ET --> MA
+```
+
+注入规律:所有 `@Bean` 方法参数 = 依赖注入点;**没有任何类 extends 别人**。
+
+### 2.2 为什么不用继承
+
+```
+❌ 继承方案(没采用):
+ abstract class BaseToolAdapter { ... }
+ RagToolAdapter extends BaseToolAdapter { ... }
+ → 加一个工具就得改基类,横切逻辑散落
+
+✅ 组合方案(实际):
+ Adapter = ToolBoundary(门禁) + 后端(执行) + Projector(投影)
+ ↑ 构造注入持有引用,不是继承
+ → 每个 Adapter 独立组装,改一个不影响其他
+```
+
+组合的优势:
+1. **ToolBoundary 对三种工具完全无感知**——只认 `ToolExecutor` / `ToolResultProjector` 两个端口,三个工具共用同一个实例;
+2. **后端各不相同**(LookupKnowledgeTool / QueryLogsTools / JDBC),无法抽象成共同基类,用函数式接口适配;
+3. **开闭原则**:加新工具 = 新写 Adapter + Projector + config 注册,**不动已有类**(门禁/真相/进度自动继承)。
+
+---
+
+## 3. 阶段二:注册(HarnessEvidenceTools 门面)
+
+### 3.1 两个平行的注册表
+
+```mermaid
+flowchart LR
+ subgraph fromAdapters
+ RAG["ragAdapter::execute"]
+ LOGS["logsAdapter::execute"]
+ MYSQL["mysqlAdapter::execute"]
+ end
+ subgraph HarnessEvidenceTools
+ direction TB
+ CALL["callbacks(List)
模型可见 Schema + 必炸"]
+ INV["invokers(Map)
toolName → bridge 闭包"]
+ end
+ RAG -->|bridge| INV
+ LOGS -->|bridge| INV
+ MYSQL -->|bridge| INV
+ INV -.同一个工具名串起.-> CALL
+ CALL --> MODEL["模型(可见工具目录)"]
+ INV --> INTERCEPTOR["拦截器(执行入口)"]
+```
+
+**同一工具名字符串串起两个表**:模型从 callbacks 决定调 `lookup_knowledge` → 拦截器用同一个名字去 invokers 取执行器。
+
+### 3.2 bridge:方法引用绑定实际调用者
+
+```java
+private static EvidenceToolInvoker bridge(String toolName, AdapterCall adapter) {
+ // lambda 闭包捕获 adapter 实例 + 固定的 toolName
+ return (context, toolCallId, arguments) -> adapter.execute(
+ context,
+ new ToolCallRequestEnvelope(
+ context.runId(), toolCallId, toolName, arguments, true, true));
+}
+```
+
+关联链(三层绑定):
+
+```
+① config:new RagToolAdapter(boundary, mapper, projector, backend)
+ → adapter 实例已组合好 boundary + projector + backend
+② fromAdapters:ragAdapter::execute 是「绑定实例的方法引用」
+ → bridge lambda 捕获它 —— invoker 与 Adapter 的关联在此固化
+③ 构造方法:按工具名常量 put 进 invokers —— "lookup_knowledge" → 捕获了 ragAdapter 的 lambda
+```
+
+**invoker 与调用者的关联 = 方法引用绑定**:取出来直接 `adapter.execute(...)`,不需要再查表找调用者。
+
+### 3.3 三个关键设计点
+
+**① callbacks 的必炸保护**:
+
+```java
+FunctionToolCallback.builder(name, ignored -> {
+ throw new IllegalStateException(
+ "Harness evidence Tools require the framework Tool interceptor");
+})
+```
+
+| 场景 | callback 行为 |
+|---|---|
+| 正常(拦截器接管) | 不执行(拦截器直接 evidenceTools.invoke) |
+| 异常(某处 handler.call / 直接调) | **抛异常** → 暴露「绕过门禁」的 bug |
+
+结构性保证:唯一能执行证据工具的路径 = 拦截器接管 → 门禁永远在线;绕过不可能静默成功(fail-fast)。
+
+**② mysql 条件注册**:
+
+```java
+// config
+boolean mysqlEnabled = 数据源配置了 jdbcUrl ? true : false;
+return fromAdapters(rag, logs, mysqlEnabled ? mysql : null);
+
+// fromAdapters
+EvidenceToolInvoker mysql = mysqlAdapter == null ? null : bridge(QUERY_MYSQL, mysqlAdapter::execute);
+// 构造方法里 null 也不注册 invokers / callbacks
+```
+
+没配数据源 → query_mysql 从模型视野和执行注册表**都消失**(不暴露「必死工具」)。RAG/日志后端内置,无条件注册。
+
+**③ definition 的 typed 输入类**:
+
+```java
+definition(AgentToolContracts.LOOKUP_KNOWLEDGE, ..., RagToolCall.class)
+```
+
+输入类型 = 模型必须匹配的 Schema——`RagToolCall{previous_observation, input}`。parse 时用 `FAIL_ON_UNKNOWN_PROPERTIES + FAIL_ON_TRAILING_TOKENS` 强制匹配,**模型输出多一个字段都炸**(INVALID_ENVELOPE)。
+
+---
+
+## 4. 阶段三:调用(拦截器 → 注册表)
+
+```mermaid
+sequenceDiagram
+ participant M as 模型
+ participant F as 框架 ReactAgent
+ participant I as HarnessToolInterceptor
+ participant ET as HarnessEvidenceTools
+
+ M->>F: 决定调用 lookup_knowledge(输出 tool_call JSON)
+ F->>I: 回调 interceptToolCall(request, handler)
+ I->>I: supports(toolName) ? 注册检查
+ I->>ET: parse(toolName, arguments, mapper) → typed Envelope
+ ET-->>I: ParsedAgentToolCall(previous_observation + input)
+ I->>I: 协议校验 / 判重(不通过不执行)
+ I->>ET: invoke(context, toolName, toolCallId, args)
+ ET->>I: bridge lambda → adapter.execute
+```
+
+调用链要点:
+
+| 点 | 说明 |
+|---|---|
+| **工具名是模型决定的** | `request.getToolName()` 来自模型输出,拦截器拿它查 invokers |
+| **invoke 前有三道门** | supports 分流 → parse 严格契约 → 协议/判重——判重不通过不执行 |
+| **envelope 现造** | bridge 里构造,`authorized=true, readOnly=true` 写死——每个进 ToolBoundary 的信封都声明「已授权 + 只读」 |
+| **工具名被闭包捕获** | 即使调用方传错名字,envelope 里也是正确的工具名(防混淆) |
+
+---
+
+## 5. 阶段四:执行(Adapter → ToolBoundary → 后端)
+
+### 5.1 Adapter = 接线员
+
+```mermaid
+flowchart LR
+ AD["Adapter.execute"] -->|"boundary.execute(context, envelope,"| TB["ToolBoundary"]
+ AD -->|"executor = ignored -> legacyExecutor.execute(query)"| TB
+ AD -->|"projector = raw -> projector.project(...)"| TB
+ TB -->|"executor 跑 backend"| BK["具体后端
LookupKnowledgeTool / QueryLogsTools / JDBC"]
+ TB -->|"projector 投影"| PR["RagResultProjector / QueryLogsResultProjector / MysqlResultProjector"]
+ TB -->|"markReady"| ST["CanonicalInvocationStore"]
+```
+
+**两个端口**:executor(跑 backend 拿 raw)+ projector(raw → 有界脱敏契约)。**模型永远看不到 raw**——这是执行链的核心目的。
+
+### 5.2 LegacyExecutor vs 专用 Executor
+
+| | RAG/日志 | MySQL |
+|---|---|---|
+| 后端来源 | 重构前旧类(LookupKnowledgeTool / QueryLogsTools) | 全新实现(JdbcMysqlReadOnlyExecutor) |
+| 端口位置 | Adapter **内部**定义 LegacyExecutor | mysql **包**里定义 MysqlReadOnlyExecutor |
+| 注入方式 | `backend::lookupKnowledge` 方法引用 / lambda | 直接注入专用实现 |
+| 为什么 | 复用成熟旧代码 | 新工具直接面向沙箱设计 |
+
+```
+RAG: Adapter → LegacyExecutor(Adapter内部) → LookupKnowledgeTool(旧后端)
+MySQL: Adapter → MysqlReadOnlyExecutor(mysql包) → JdbcMysqlReadOnlyExecutor(新实现)
+```
+
+### 5.3 ToolBoundary 五阶段(执行门禁)
+
+```mermaid
+flowchart TD
+ A["① preflight + Tool 预算 + request bytes → begin(PROJECTING)"]
+ B["② executor.execute(requestJson) → backend raw"]
+ C["③ raw 大小校验 + Run bytes 预留"]
+ D["④ projector.project(raw) → 有界 agent_result + evidenceStatus"]
+ E["⑤ agent_result 校验 + bytes → markReady(READY)"]
+ A --> B --> C --> D --> E
+```
+
+**三笔 bytes 预留**:request(①)/ raw(③)/ agent_result(⑤)。
+
+### 5.4 脱敏与有界(投影器)
+
+- **日志**:sanitize 抹掉密码/token/主机/Pod/IP/PID/SQL 字面量;均匀采样 + 模式聚合;
+- **MySQL**:敏感列(password/token/secret 等)单元格 → `[REDACTED]`;行数/字符/字节三重截断;
+- **RAG**:chunk 级去重 + 摘录截断 + fitBudget 总字节兜底。
+
+---
+
+## 6. 阶段五:返回(双源校验 → 记账 → 有界观察)
+
+```mermaid
+sequenceDiagram
+ participant TB as ToolBoundary
+ participant I as HarnessToolInterceptor
+ participant P as DiagnosisProgressTracker
+ participant M as 模型
+
+ TB-->>I: ToolBoundaryResult(READY/ERROR)
+ I->>I: 双源交叉验证(声明值 vs 内容重算 evidence_status)
+ alt 不一致
+ I->>M: OBSERVATION_CONTRACT_MISMATCH(拒绝)
+ else 一致
+ I->>P: recordCompleted(call, evidenceStatus)
+ P-->>I: 快照(NO_EVIDENCE 立即 NO_GAIN / FOUND 挂 pending)
+ I->>I: modelObservation 加工(含 stop_required / stopReason)
+ I-->>M: 有界 observation(模型永远看不到 raw)
+ end
+```
+
+**错误处理三种形态**:
+
+| 场景 | 处理 |
+|---|---|
+| Adapter 业务/参数异常 | catch → `INVALID_REQUEST`(不泄露内部细节) |
+| MySQL 安全异常 | `MysqlSecurityException` 单独 catch → `INVALID_REQUEST` |
+| 日志后端缺失 | `ObjectProvider.getIfAvailable()` → 返回空结果 JSON(不炸) |
+
+---
+
+## 7. 全链路合起来
+
+```mermaid
+sequenceDiagram
+ participant C as config(启动)
+ participant ET as HarnessEvidenceTools(单例)
+ participant I as 拦截器(per-Run)
+ participant AD as Adapter(单例)
+ participant TB as ToolBoundary(单例)
+ participant ST as CanonicalStore(单例)
+ participant M as 模型
+
+ rect rgb(240, 248, 255)
+ Note over C,ET: ① 装配(应用启动一次)
+ C->>C: 建 boundary / 3 个 Adapter(注入 boundary+后端+投影器)
+ C->>ET: fromAdapters(rag, logs, mysql?)
+ ET->>ET: bridge → invokers + definition → callbacks(平行,同名串起)
+ end
+
+ rect rgb(255, 250, 240)
+ Note over M,AD: ② 调用+执行(每次 Tool Call)
+ M->>I: 模型决定工具名 → 框架回调拦截器
+ I->>I: supports 分流 → parse(typed 严格契约)→ 协议/判重
+ I->>ET: invoke → invokers.get(名字) → bridge 闭包
+ ET->>AD: adapter.execute(context, envelope[现造,授权只读写死])
+ AD->>TB: boundary.execute(context, envelope, executor, projector)
+ TB->>ST: begin(PROJECTING) → executor 跑 raw → projector 投影 → markReady(READY)
+ TB-->>I: ToolBoundaryResult
+ I->>I: 双源校验 → recordCompleted → modelObservation
+ I-->>M: 有界 observation(无 raw)
+ end
+```
+
+**四阶段汇总**:
+
+| 阶段 | 做什么 | 关键类 |
+|---|---|---|
+| 装配 | Spring 组合依赖(无继承) | config / Adapter / ToolBoundary |
+| 注册 | bridge 成 invoker + definition 成 callback(平行同名串起) | HarnessEvidenceTools |
+| 调用 | supports → parse 严格契约 → 协议/判重 → invoke | Interceptor / HarnessEvidenceTools |
+| 执行+返回 | boundary 五阶段 → 投影脱敏 → canonical → 双源校验 → 记账 → 有界观察 | Adapter / ToolBoundary / Projector / Store |
+
+---
+
+## 8. 易错点
+
+| 易错 | 正确 |
+|---|---|
+| 必炸 = 死工具 | 必炸是**保护**:模型通过拦截器正常执行,只有绕过路径才炸 |
+| LegacyExecutor 是通用 executor | 它只服务于「复用旧后端」;新工具直接注入专用 Executor(如 MysqlReadOnlyExecutor) |
+| callbacks 是执行器 | 它是「模型可见目录」+ 必炸占位;真执行走 invokers |
+| 执行链只有 executor | 还有 **projector**(raw → 有界契约)——模型永远看不到 raw |
+| MySQL 没有 tool 类 | `JdbcMysqlReadOnlyExecutor` 就是它的后端执行类,只是不叫 Tool |
+| 工具注册是静态列表 | **配置驱动**:没配数据源 → query_mysql 从两表消失 |
+| 返回就是 ToolBoundaryResult | 返回后还有双源校验 → recordCompleted → modelObservation |
+
+## 9. 面试话术合集(30 秒)
+
+### 9.1 为什么不用继承
+
+> "tool 域刻意不用继承:ToolBoundary 通过 ToolExecutor/ToolResultProjector 两个函数式端口接收执行和投影逻辑,三个 Adapter 各自用构造注入组合 boundary + 后端 + projector,HarnessEvidenceTools 再用 bridge 把 Adapter 包成统一的 EvidenceToolInvoker 注册表。类图里没有 extends 箭头——全是 has-a(组合)和函数适配(函数式接口),扩展新工具不改任何已有类。"
+
+### 9.2 为什么必炸保护
+
+> "必炸保护是执行不可绕过的结构性保证:证据工具的 ToolCallback 被故意定义为直接抛异常,使 handler 路径成为死路。唯一能执行证据工具的路径就是拦截器接管——预算、canonical、进度协议、脱敏门禁永远在线;任何绕过尝试要么抛异常暴露 bug(fail-fast),要么根本不执行(fail-closed)。非证据工具不需要门禁,所以拦截器放行、callback 正常。"
+
+### 9.3 LegacyExecutor 是什么
+
+> "LegacyExecutor 是 Adapter 内部定义的旧后端端口:RAG 和日志是重构前就有的工具,后端实现(backend::lookupKnowledge)被方法引用注入复用,通过 Adapter 包进 Harness 门禁——『旧后端复用,新门禁外挂』。它和 ToolBoundary 的 ToolExecutor 是两层:LegacyExecutor 是具体后端怎么查,ToolExecutor 是边界统一端口,Adapter 把前者包成后者。MySQL 是全新工具,没有 legacy,直接用新写的 MysqlReadOnlyExecutor。"
+
+### 9.4 模型为什么看不到 raw
+
+> "执行链是两个端口:executor 跑 backend 拿 raw,projector 把 raw 投影成有界脱敏契约(截断 + 脱敏 + 冻结 Schema)。ToolBoundary 只让 READY/ERROR 离开,模型拿到的是 modelObservation 加工后的有界观察——raw 只进 canonical Store 供审计和验真,永远不进入模型上下文。"
+
+### 9.5 新工具怎么加(开闭原则)
+
+> "新工具按 MySQL 模板:写 Contract 三件套 + 专用执行器 + 投影器 + Adapter,config 注册。要改的只有 AgentToolContracts 常量、HarnessEvidenceTools 构造、config;不用改 ToolBoundary、canonical、拦截器——新工具自动获得预算门禁、真相记录、脱敏投影、进度收敛、证据验真全套管控。"
+
+## 10. 代码位置索引
+
+| 类 | 文件 |
+|---|---|
+| `HarnessEvidenceTools` | `src/main/java/com/superbiz/agent/harness/agent/HarnessEvidenceTools.java`(agent 包,tool 域门面) |
+| `RagToolAdapter` / `QueryLogsToolAdapter` / `MysqlToolAdapter` | `.../tool/adapter/` |
+| `ToolBoundary` / `ToolBoundaryResult` / `ToolCallRequestEnvelope` / `ToolExecutor` / `ToolResultProjector` / `ProjectedToolResult` | `.../tool/boundary/` |
+| `RagResultProjector` / `QueryLogsResultProjector` / `ToolProjectionLimits` | `.../tool/projection/` |
+| `CanonicalInvocationStore` / `RedisCanonicalInvocationStore` / `CanonicalToolInvocation` / `ToolCallKeyFactory` / `CanonicalInvocationLimits` | `.../tool/store/` |
+| Contract 18 个 | `.../tool/contract/` |
+| `MysqlSqlValidator` / `JdbcMysqlReadOnlyExecutor` / `MysqlResultProjector` / `MysqlDataSourceDefinition` 等 | `.../tool/mysql/` |
+| 装配 | `src/main/java/com/superbiz/agent/config/HarnessChatConfiguration.java` |
diff --git a/src/main/java/com/superbiz/agent/harness/tool/adapter/MysqlToolAdapter.java b/src/main/java/com/superbiz/agent/harness/tool/adapter/MysqlToolAdapter.java
index cc27c00..12d631c 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/adapter/MysqlToolAdapter.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/adapter/MysqlToolAdapter.java
@@ -15,13 +15,20 @@ import com.superbiz.agent.harness.tool.mysql.MysqlSqlValidator;
import java.util.Objects;
-/** Validates and runs the logical MySQL Tool through the canonical boundary. */
+/**
+ * MySQL 逻辑工具的接线员:反序列化请求 → SQL 沙箱校验(fail-closed)→
+ * 把只读执行器(executor)和投影器(projector)组装进 ToolBoundary 统一门禁。
+ * 安全/参数异常映射为 INVALID_REQUEST(不泄露内部细节)。
+ */
public final class MysqlToolAdapter {
private final ToolBoundary boundary;
private final ObjectMapper objectMapper;
+ /** SQL 沙箱:白名单表列 + fail-closed 策略。 */
private final MysqlSqlValidator validator;
+ /** 只读执行器(JDBC 只读连接 + 超时 + 行数 + 取消)。 */
private final MysqlReadOnlyExecutor executor;
+ /** 投影器:raw 行 → 有界脱敏契约。 */
private final MysqlResultProjector projector;
public MysqlToolAdapter(ToolBoundary boundary, ObjectMapper objectMapper,
@@ -34,12 +41,19 @@ public final class MysqlToolAdapter {
this.projector = Objects.requireNonNull(projector, "projector must not be null");
}
+ /**
+ * 执行入口:解析请求 → SQL 沙箱校验(生成执行计划)→ 组装 executor/projector
+ * 交给 ToolBoundary。任何安全/参数异常统一映射 INVALID_REQUEST。
+ */
public ToolBoundaryResult execute(RunContext context, ToolCallRequestEnvelope envelope) {
try {
MysqlToolRequest request = objectMapper.readValue(envelope.requestJson(), MysqlToolRequest.class);
+ // 沙箱校验:表列白名单 + fail-closed 策略 → 规范化执行计划
MysqlQueryPlan plan = validator.validate(request);
return boundary.execute(context, envelope,
+ // executor:只读执行器返回 raw 行 JSON
ignored -> objectMapper.writeValueAsString(executor.execute(plan, context)),
+ // projector:有界脱敏投影(用该数据源的限制)
raw -> projector.project(request, envelope.toolCallId(), raw, plan.dataSource().limits()));
} catch (MysqlSecurityException | IllegalArgumentException e) {
return ToolBoundaryResult.error(envelope == null ? null : envelope.toolCallId(),
diff --git a/src/main/java/com/superbiz/agent/harness/tool/adapter/QueryLogsToolAdapter.java b/src/main/java/com/superbiz/agent/harness/tool/adapter/QueryLogsToolAdapter.java
index c321899..29c759f 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/adapter/QueryLogsToolAdapter.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/adapter/QueryLogsToolAdapter.java
@@ -17,9 +17,14 @@ import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
-/** Bridges logical query-log requests and the existing Mock tool through ToolBoundary. */
+/**
+ * 逻辑日志请求与既有 Mock 工具的接线员:
+ * 反序列化请求 → 校验 topic/query/lookback → 构造查询范围 scope →
+ * 把 legacy executor 和投影器组装进 ToolBoundary 统一门禁执行。
+ */
public final class QueryLogsToolAdapter {
+ /** legacy backend 执行端口(region + 旧主题 + 关键词 + 条数)。 */
@FunctionalInterface
public interface LegacyExecutor {
String execute(String region, String legacyTopic, String query, Integer limit) throws Exception;
@@ -58,25 +63,33 @@ public final class QueryLogsToolAdapter {
this.legacyLimit = legacyLimit;
}
+ /**
+ * 执行入口:解析请求 → 校验 → 构造 scope → 组装 executor/projector 交给 ToolBoundary。
+ * 业务参数非法返回 INVALID_REQUEST(不抛异常打断 ReAct)。
+ */
public ToolBoundaryResult execute(RunContext context, ToolCallRequestEnvelope envelope) {
try {
QueryLogsRequest request = objectMapper.readValue(envelope.requestJson(), QueryLogsRequest.class);
if (request.topic() == null || request.query() == null || request.query().isBlank()) {
return ToolBoundaryResult.error(envelope.toolCallId(), ToolBoundaryErrorCode.INVALID_REQUEST);
}
+ // 回看窗口:缺省 30 分钟,上限 24 小时
int lookback = request.lookbackMinutes() == null
? DEFAULT_LOOKBACK_MINUTES : request.lookbackMinutes();
if (lookback <= 0 || lookback > 24 * 60) {
return ToolBoundaryResult.error(envelope.toolCallId(), ToolBoundaryErrorCode.INVALID_REQUEST);
}
Instant end = clock.instant();
+ // 实际查询范围(审计/公开 scope 用)
LogQueryScope scope = new LogQueryScope(
request.topic(), request.query(),
DateTimeFormatter.ISO_INSTANT.format(end.minus(Duration.ofMinutes(lookback))),
DateTimeFormatter.ISO_INSTANT.format(end));
String legacyTopic = legacyTopic(request.topic());
return boundary.execute(context, envelope,
+ // executor:调用 legacy 日志 backend
ignored -> legacyExecutor.execute(region, legacyTopic, request.query(), legacyLimit),
+ // projector:投影成冻结契约(含 scope)
raw -> projector.project(request, envelope.toolCallId(), scope, raw));
} catch (Exception e) {
return ToolBoundaryResult.error(envelope == null ? null : envelope.toolCallId(),
@@ -84,6 +97,7 @@ public final class QueryLogsToolAdapter {
}
}
+ /** 逻辑主题 → legacy 日志主题名映射。 */
private static String legacyTopic(LogTopic topic) {
return switch (topic) {
case APPLICATION -> "application-logs";
diff --git a/src/main/java/com/superbiz/agent/harness/tool/boundary/ProjectedToolResult.java b/src/main/java/com/superbiz/agent/harness/tool/boundary/ProjectedToolResult.java
index 961822d..2fde81a 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/boundary/ProjectedToolResult.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/boundary/ProjectedToolResult.java
@@ -4,6 +4,10 @@ import com.superbiz.agent.harness.contract.EvidenceStatus;
import java.util.Objects;
+/**
+ * Projector 的产出:有界 agent_result 文本 + 客观 evidence status。
+ * ToolBoundary 只接受 FOUND/NO_EVIDENCE(ERROR 走错误路径,不产生投影结果)。
+ */
public record ProjectedToolResult(String agentResult, EvidenceStatus evidenceStatus) {
public ProjectedToolResult {
@@ -11,6 +15,7 @@ public record ProjectedToolResult(String agentResult, EvidenceStatus evidenceSta
throw new IllegalArgumentException("agentResult must not be blank");
}
Objects.requireNonNull(evidenceStatus, "evidenceStatus must not be null");
+ // 投影结果只能是合法证据语义(空不空),错误状态不从这里出
if (evidenceStatus != EvidenceStatus.EVIDENCE_FOUND
&& evidenceStatus != EvidenceStatus.NO_EVIDENCE) {
throw new IllegalArgumentException("projected result must be evidence or no-evidence");
diff --git a/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolCallRequestEnvelope.java b/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolCallRequestEnvelope.java
index b95e847..5c8887c 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolCallRequestEnvelope.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolCallRequestEnvelope.java
@@ -2,11 +2,22 @@ package com.superbiz.agent.harness.tool.boundary;
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * 一次 Tool 执行的内部信封:同时证明 Run、调用 ID、工具、参数、授权意图和只读意图。
+ * 由 Adapter 在调 ToolBoundary 前构造(HarnessEvidenceTools 的 bridge 固定 authorized/readOnly=true)。
+ * 与模型侧 progress Envelope(previous_observation + input)不同:这是边界内部信封。
+ */
public record ToolCallRequestEnvelope(
+ /** 所属 Run。 */
@JsonProperty("run_id") String runId,
+ /** 本次调用 ID(canonical key 的一部分)。 */
@JsonProperty("tool_call_id") String toolCallId,
+ /** 工具名。 */
@JsonProperty("tool_name") String toolName,
+ /** 业务请求 JSON(纯业务参数,无协议字段)。 */
@JsonProperty("request") String requestJson,
+ /** 是否授权(bridge 恒为 true;策略拒绝走 UNAUTHORIZED)。 */
@JsonProperty("authorized") boolean authorized,
+ /** 是否只读意图(诊断 Tool 必须只读,否则 NOT_READ_ONLY)。 */
@JsonProperty("read_only") boolean readOnly) {
}
diff --git a/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolExecutor.java b/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolExecutor.java
index 1e72822..6d5c0ec 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolExecutor.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolExecutor.java
@@ -1,6 +1,11 @@
package com.superbiz.agent.harness.tool.boundary;
+/**
+ * 具体 backend 的 raw 执行函数端口(函数式):输入业务请求 JSON,输出原始响应文本。
+ * ToolBoundary 不依赖具体 backend,只认这个端口——Adapter 把各自 backend 接进来。
+ */
@FunctionalInterface
public interface ToolExecutor {
+ /** 执行 backend,返回原始响应(非 null);失败抛异常由边界映射错误码。 */
String execute(String requestJson) throws Exception;
}
diff --git a/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolResultProjector.java b/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolResultProjector.java
index 9d7eb7e..0c6dfb3 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolResultProjector.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/boundary/ToolResultProjector.java
@@ -1,6 +1,12 @@
package com.superbiz.agent.harness.tool.boundary;
+/**
+ * raw → 有界 agent 契约的投影端口(函数式):输入原始响应,输出投影结果
+ * (有界 agent_result + 客观 evidence status)。每个 Tool 一个实现
+ * (Rag/QueryLogs/Mysql ResultProjector),ToolBoundary 通过它解耦投影逻辑。
+ */
@FunctionalInterface
public interface ToolResultProjector {
+ /** 投影 raw:必须返回非 null 的有界结果;失败抛异常由边界映射 PROJECTION_ERROR。 */
ProjectedToolResult project(String rawResponse) throws Exception;
}
diff --git a/src/main/java/com/superbiz/agent/harness/tool/contract/AgentToolContracts.java b/src/main/java/com/superbiz/agent/harness/tool/contract/AgentToolContracts.java
index b641ffa..130c2f9 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/contract/AgentToolContracts.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/contract/AgentToolContracts.java
@@ -1,21 +1,33 @@
package com.superbiz.agent.harness.tool.contract;
+/**
+ * 三个证据 Tool 的「名字 + 模型可见描述」的单一事实源(冻结契约)。
+ *
+ *
所有地方(拦截器判重、Normalizer 分派、Projector 分派、注册表)都引用这里的
+ * 常量而不是字符串字面量——避免工具名拼错导致跨层漂移。
+ */
public final class AgentToolContracts {
+ /** 知识库检索工具名。 */
public static final String LOOKUP_KNOWLEDGE = "lookup_knowledge";
+ /** 日志查询工具名。 */
public static final String QUERY_LOGS = "query_logs";
+ /** MySQL 只读查询工具名。 */
public static final String QUERY_MYSQL = "query_mysql";
+ /** 模型可见的 RAG 工具描述:稳定背景知识,不用于实时日志/指标。 */
public static final String LOOKUP_KNOWLEDGE_DESCRIPTION =
"查询内部知识库中的文档、接口说明、错误码和排障手册。"
+ "适用于稳定背景知识,不用于查询实时日志、指标或数据库状态。"
+ "输入 query:需要查询的问题或关键词。";
+ /** 模型可见的日志工具描述:应用错误/慢查询/系统事件,不用于指标或表。 */
public static final String QUERY_LOGS_DESCRIPTION =
"查询指定逻辑日志主题在时间窗口内与目标相关的日志证据。"
+ "适用于应用错误、慢查询和系统事件,不用于查询指标或数据库表。"
+ "输入 topic、query、lookback_minutes。";
+ /** 模型可见的 MySQL 工具描述:授权数据源的参数化只读 SELECT,禁止发现表结构/写操作。 */
public static final String QUERY_MYSQL_DESCRIPTION =
"在授权的逻辑数据源上执行参数化只读查询,获取业务数据库事实。"
+ "只用于已知库表字段的 SELECT,不用于发现表结构或执行写操作。"
diff --git a/src/main/java/com/superbiz/agent/harness/tool/contract/LogEvent.java b/src/main/java/com/superbiz/agent/harness/tool/contract/LogEvent.java
index 5e5f3a1..c466856 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/contract/LogEvent.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/contract/LogEvent.java
@@ -2,6 +2,9 @@ package com.superbiz.agent.harness.tool.contract;
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * 单条日志事件(冻结契约):时间/级别/服务/消息四元组。
+ */
public record LogEvent(
@JsonProperty("timestamp") String timestamp,
@JsonProperty("level") String level,
diff --git a/src/main/java/com/superbiz/agent/harness/tool/contract/LogPattern.java b/src/main/java/com/superbiz/agent/harness/tool/contract/LogPattern.java
index 53440d2..89c87f4 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/contract/LogPattern.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/contract/LogPattern.java
@@ -2,11 +2,17 @@ package com.superbiz.agent.harness.tool.contract;
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * 日志模式聚合(冻结契约):把相似事件压缩成一条「模式」,
+ * 供模型快速了解事件全貌而不用读每条原始事件。
+ */
public record LogPattern(
+ /** 该模式出现次数。 */
@JsonProperty("count") long count,
@JsonProperty("first_seen") String firstSeen,
@JsonProperty("last_seen") String lastSeen,
@JsonProperty("level") String level,
@JsonProperty("service") String service,
+ /** 示例事件(有界)。 */
@JsonProperty("example") String example) {
}
diff --git a/src/main/java/com/superbiz/agent/harness/tool/contract/LogQueryScope.java b/src/main/java/com/superbiz/agent/harness/tool/contract/LogQueryScope.java
index 5e73a05..9fa5f44 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/contract/LogQueryScope.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/contract/LogQueryScope.java
@@ -2,6 +2,10 @@ package com.superbiz.agent.harness.tool.contract;
import com.fasterxml.jackson.annotation.JsonProperty;
+/**
+ * 实际日志查询范围(冻结契约):反映「这次到底查了什么」,
+ * 供审计、ProgressProjector 的公开 scope、以及重复检测参考。
+ */
public record LogQueryScope(
@JsonProperty("topic") LogTopic topic,
@JsonProperty("query") String query,
diff --git a/src/main/java/com/superbiz/agent/harness/tool/contract/LogSourceKind.java b/src/main/java/com/superbiz/agent/harness/tool/contract/LogSourceKind.java
index bf304d2..933f9a8 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/contract/LogSourceKind.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/contract/LogSourceKind.java
@@ -1,5 +1,9 @@
package com.superbiz.agent.harness.tool.contract;
+/**
+ * 日志来源类型(冻结契约)。当前只有 MOCK(演示/评估环境),
+ * 后续可扩展 ES/ClickHouse 等真实来源。
+ */
public enum LogSourceKind {
MOCK
}
diff --git a/src/main/java/com/superbiz/agent/harness/tool/contract/LogTopic.java b/src/main/java/com/superbiz/agent/harness/tool/contract/LogTopic.java
index f828426..e848215 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/contract/LogTopic.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/contract/LogTopic.java
@@ -1,7 +1,13 @@
package com.superbiz.agent.harness.tool.contract;
+/**
+ * 逻辑日志主题(冻结契约):模型只能在这三个主题内查询,不能自由指定任意来源。
+ */
public enum LogTopic {
+ /** 应用错误/业务日志。 */
APPLICATION,
+ /** 数据库慢查询。 */
DATABASE_SLOW_QUERY,
+ /** 系统事件。 */
SYSTEM_EVENTS
}
diff --git a/src/main/java/com/superbiz/agent/harness/tool/contract/MysqlToolCall.java b/src/main/java/com/superbiz/agent/harness/tool/contract/MysqlToolCall.java
index c292679..449cdb4 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/contract/MysqlToolCall.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/contract/MysqlToolCall.java
@@ -3,7 +3,13 @@ package com.superbiz.agent.harness.tool.contract;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.superbiz.agent.harness.progress.PreviousObservation;
+/**
+ * 模型发起的 MySQL 只读查询 Tool 调用 Envelope(Agent-facing 冻结契约):
+ * 协议字段 previous_observation + 业务输入 input。
+ */
public record MysqlToolCall(
+ /** 对上一轮观察的评价(首次调用可为 null)。 */
@JsonProperty("previous_observation") PreviousObservation previousObservation,
+ /** 业务输入:data_source + sql + params。 */
@JsonProperty("input") MysqlToolRequest input) {
}
diff --git a/src/main/java/com/superbiz/agent/harness/tool/contract/MysqlToolRequest.java b/src/main/java/com/superbiz/agent/harness/tool/contract/MysqlToolRequest.java
index 7bae46a..5a27c3d 100644
--- a/src/main/java/com/superbiz/agent/harness/tool/contract/MysqlToolRequest.java
+++ b/src/main/java/com/superbiz/agent/harness/tool/contract/MysqlToolRequest.java
@@ -4,9 +4,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
+/**
+ * MySQL 只读查询业务输入(冻结契约):授权逻辑数据源 + 参数化 SQL + 绑定参数。
+ * 判重指纹 = {data_source, sql, params}。
+ */
public record MysqlToolRequest(
+ /** 授权数据源名(不是任意 JDBC URL)。 */
@JsonProperty("data_source") String dataSource,
+ /** 参数化 SQL(只允许 SELECT,沙箱校验)。 */
@JsonProperty("sql") String sql,
+ /** 绑定参数(防注入)。 */
@JsonProperty("params") List