Harness 组件全景:职责、设计原因与边界
更新日期:2026-07-29
适用代码:src/main/java/com/superbiz/agent/harness
术语与状态:CONTEXT.md · Harness生命周期与状态.md
配套主文:Harness设计-非确定性Agent的确定性控制边界.md
本文是完整组件参考手册,不建议第一次接触 Harness 时顺序阅读。入门请先读 Harness 阅读入口,需要逐组理解组件时使用组件渐进式导读。
1. 这份文档怎样定义“全部组件”
当前 harness 目录包含 10 个一级职责域、189 个 Java 源文件。它们并不都是独立运行的“服务”:
- 执行组件拥有行为,例如 Core、Interceptor、Boundary、Guard、Release、Executor;
- 端口与适配器隔离框架、Redis、JPA、JDBC 和业务 Tool;
- 状态与契约类型固定跨组件语言,防止字符串协议漂移;
- Limits、Prompt 和异常类型把边界配置与失败语义显式化。
因此本篇先解释 10 个职责域为什么存在,再列出每个生产类型。判断某个类应该放在哪里时,只问三个问题:它拥有什么状态、它能作出什么决定、它绝不能决定什么。
2. 组件地图
| 职责域 |
文件数 |
解决的问题 |
核心组件 |
application |
34 |
谁创建 Run、路由请求、持久化和映射公开结果 |
ChatApplicationUseCase、三个 Executor、ChatRunStore |
core |
14 |
deadline、取消、预算和唯一终态由谁拥有 |
DiagnosisHarnessCore、RunContext、RunBudget、RunLifecycle |
agent |
17 |
如何把框架 ReAct 接入 Harness,而不复制 ReAct |
DiagnosisAgentUseCase、Factory、Model/Tool Interceptor |
progress |
14 |
如何识别无增益、重复和协议空转 |
DiagnosisProgressTracker、Projector、scope normalizer |
tool |
49 |
Tool 如何安全执行、保存真相并只暴露必要内容 |
ToolBoundary、Adapters、Projectors、Canonical Store、MySQL sandbox |
guard |
15 |
如何分开验证引用真实性和结论支持度 |
EvidenceGuard、SemanticGuard、GuardModelCall |
release |
6 |
谁拥有最终 SUCCESS / FALLBACK 决策 |
DiagnosisReleaseUseCase、EvidenceRepair、SafeFallbackFactory |
retry |
8 |
哪些失败允许重试、attempt 如何可见 |
HarnessRetryExecutor、Policies、Failure taxonomy |
audit |
17 |
如何重放决策而不复制敏感正文 |
Trace、Tool audit、Model ledger、Agent hook |
contract |
15 |
跨层公开语言如何保持类型化 |
Draft、PublishedResult、Fallback、状态枚举 |
3. Application:Run 的应用所有者
为什么需要
Core 只知道一次 Run 是否活跃,并不知道 HTTP、SSE、意图路由、数据库持久化和上一轮上下文。若这些职责塞进 Core,Harness 会变成业务工作流引擎;若散落在 Controller,则每个入口都可能产生不同的终态和 Fallback。
核心组件
| 组件 |
为什么设计 |
作用 |
明确边界 |
ChatApplicationUseCase |
为一次请求建立唯一应用事务边界 |
解析 session、读取历史、创建 Run、路由、执行分支、落终态和安全输出 |
不做诊断推理,不自行构造诊断 Fallback |
IntentRouter |
路由也会消耗模型、超时并返回非法 JSON |
在有界输入、timeout 和显式 retry 下输出唯一 IntentType |
不执行业务 Tool,不生成最终回答 |
SystemChatExecutor |
系统问答不需要 ReAct,但仍必须受模型预算控制 |
单轮回答产品能力和闲聊 |
不声称查询了实时数据 |
KnowledgeQueryExecutor |
知识问答需要一次 RAG 和一次受控生成,但不需要完整诊断链 |
调用知识 Tool、校验 Tool result、生成带来源答案 |
首版不进入 SemanticGuard,不执行多 Tool 诊断 |
DiagnosisChatExecutor |
Agent 执行和安全发布需要一个明确接合点 |
执行 Agent、处理合法停止/非法 Draft、调用 Release、映射公开内容 |
不重复 Guard 或 Release 决策 |
ChatRunStore / JpaChatRunStore |
内存 Run 状态与长期数据库状态职责不同 |
保存 Run 开始、intent、结果、预算摘要;读取安全上一轮 |
不保存 canonical raw;只允许安全发布结果进入 PreviousTurn |
PublishedResultPolicy |
直接复用上一轮完整结果会让上下文无限增长并传播失败内容 |
生成可持久化 PublishedResult 和有界 PreviousTurn |
Fallback、失败、raw evidence 不进入下一轮 |
类型清单
| 类型 |
分类与功能 |
ChatApplicationRequest、ChatApplicationResult |
应用入口和出口 DTO;固定 session、run、intent、outcome 和内容类型 |
ChatApplicationContent、ChatContentType |
公开内容的 sealed/typed 边界,避免任意对象直接发给 SSE |
DiagnosisContent、KnowledgeContent、SystemChatContent、FallbackContent |
四种公开内容载体;分别包装安全诊断、知识答案、系统回答和 Fallback |
ChatApplicationStatus、ChatApplicationObserver |
向 SSE 报告有界阶段,不泄露模型内部步骤 |
ChatRunControl |
只向入口暴露 exact session/run 和客户端断开取消能力 |
ChatApplicationException、ChatFailureCode |
把内部异常映射为稳定、可公开的失败语义 |
IntentRouting、SystemChatOperation、KnowledgeQueryOperation、DiagnosisOperation |
四个应用端口;使主用例不依赖具体模型或执行器 |
DiagnosisExecutionResult |
Diagnosis 分支的内部返回,携带 outcome、content、published result 和预算已处理标记 |
IntentRouterInput、IntentRouterLimits、IntentRouterPrompt、IntentRoutingException |
路由输入、边界、Prompt 和失败类型 |
KnowledgeQueryLimits、SingleTurnExecutorLimits |
知识与单轮模型路径的输入、输出、timeout 上限 |
ChatRunStore、RoutingHistory |
持久化端口及最小路由历史 |
PreviousTurnLimits、PublishedResultPolicy |
安全历史的字段/大小限制与投影策略 |
4. Core:每个 Run 的执行不变量
为什么需要
模型、Tool、Guard 和 Application 都要检查取消、deadline 和预算。如果每层各自维护计数或终态,会出现多个真相源;如果依赖 ThreadLocal,则异步线程无法可靠继承。
| 组件 |
为什么设计 |
作用 |
明确边界 |
DiagnosisHarnessCore |
所有执行边界需要同一套 active / budget / terminal 规则 |
创建 RunContext,执行模型/Tool/Token/bytes 门禁,处理取消和终态 |
不持久化,不调用 Agent/Tool,不维护全局 Run Map |
RunContext |
Run 身份和状态句柄必须一起显式传播 |
固定 sessionId、runId、deadline 及 per-run handles |
record 结构不可变,不代表内部计数不变化 |
RunLifecycle |
成功、失败、取消可能竞态到达 |
原子 compareAndSet 实现 first-terminal-wins |
不映射公开 ReleaseOutcome |
RunCancellation |
取消原因和回调只能被第一个请求确定 |
first-reason-wins,并通知 lifecycle/资源回调 |
不承诺强杀同步 Provider 请求 |
RunBudget |
多维预算必须原子地先检查再计数 |
模型、Tool、单 Tool、输入/输出/总 Token 和 bytes 计量 |
不判断信息是否有价值 |
RunCapacityCounter |
bytes 可能由并发边界累计 |
CAS 方式维护 Run 总容量 |
不负责字段级截断策略 |
类型清单
| 类型 |
分类与功能 |
RunBudgetLimits、RunBudgetUsage |
预算配置和值快照;区分限制与已使用量 |
BudgetKind、BudgetExceededException |
明确指出耗尽的是模型、Tool、Token 还是 bytes |
RunState、RunTermination |
内存执行状态与不可变终止快照 |
RunCancellationReason |
客户端断开、用户请求、deadline、预算和内部失败的取消分类 |
RunAbortedException |
将已经确定的 RunTermination 穿过深层调用栈,不丢失终态 |
5. Agent:框架 ReAct 与 Harness 的接合层
为什么需要
业务需要框架原生 Tool Calling 和 ReAct loop,但框架默认并不知道项目的 RunContext、预算、审计、Tool 双视图和停止协议。接合层的目标是“拦截边界”,不是重新实现 Agent 循环。
| 组件 |
为什么设计 |
作用 |
明确边界 |
DiagnosisAgentFactory |
每个 Run 的 interceptor 和 metadata 不同 |
为当前 Run 创建 ReactAgent,注册 Tool callback、Prompt、Hook 和 interceptor |
不缓存跨 Run Agent 状态 |
DiagnosisAgentUseCase |
框架输入输出是字符串,业务要求严格 Draft 和 bytes 边界 |
序列化输入、显式注入 Run metadata、调用 Agent、严格解析 Draft、映射受控停止 |
不执行证据和语义校验 |
HarnessModelInterceptor |
每一轮 ReAct 模型调用都必须进入预算和 Token 账本 |
调用前 reserve,调用后记录 Provider usage 并再次检查 active |
不重试模型 |
HarnessToolInterceptor |
模型 Tool Call 中混有 Harness 进展协议和业务参数 |
校验 Envelope、上一轮增益、重复/饱和、调用 Tool、投影 observation、交付 STOP_REQUIRED |
不执行 backend,不复制 ToolBoundary 预算 |
HarnessEvidenceTools |
Tool schema 必须由服务端原生注册,且业务 Tool 可选启用 |
注册 RAG/log/MySQL callback,严格解析通用 Envelope,桥接 Adapter |
Prompt 不写死 Tool schema;未配置 MySQL 时不暴露死 Tool |
ToolResultViewProjector |
canonical agent_result 仍含 Harness 控制字段 |
生成 Control View 和白名单 Model Observation |
不读取 raw response,不判断根因 |
类型清单
| 类型 |
分类与功能 |
DiagnosisAgentInput、DiagnosisAgentExecution |
Agent 用例输入,以及 Draft/ProgressSnapshot/stop reason 的执行结果 |
DiagnosisAgentLimits |
query、previous turn、总输入和 Draft 的 UTF-8 bytes 上限 |
DiagnosisAgentPrompt、DiagnosisDraftOutputSchema |
最小职责 Prompt 与严格结构化输出 Schema |
EvidenceToolInvoker |
Agent 层到具体 Adapter 的函数端口 |
ParsedAgentToolCall |
解包后的 previous observation、typed business input 和 JSON 参数 |
ToolControlView |
Harness 消费的 evidence status、count、scope 等控制视图 |
DiagnosisAgentLimitException |
Agent 输入或输出越界 |
DiagnosisAgentOutputException |
空、非法 JSON、Schema 不合格 Draft,并可携带安全 ProgressSnapshot |
DiagnosisCollectionStoppedException |
STOP_REQUIRED 后仍请求 Tool 时,把受控停止穿出框架 loop |
6. Progress:从资源上限到正常收敛
为什么需要
预算只能阻止无限消耗,不能识别“连续查询没有推进诊断”。Progress 子系统只保存 Run 内最小控制状态,不复制完整证据。
| 组件 |
为什么设计 |
作用 |
明确边界 |
DiagnosisProgressTracker |
连续无增益、待评价调用和协议错误需要线程安全单一所有者 |
记录 completed scope、pending evaluation、NO_GAIN、协议错误、饱和和一次停止指令 |
不保存 raw/agent_result,不判断非空内容的业务价值 |
ToolScopeNormalizer |
字段顺序或无关格式不应绕过重复检测 |
将各 Tool typed input 规范化为稳定 scope |
首版不做自然语言语义去重 |
DiagnosisProgressProjector |
受控停止或非法 Draft 后仍需安全说明已检查内容 |
按 Tracker identity 回读 canonical READY 记录,生成有界事实、来源和限制 |
无法验真的记录直接排除,不输出 raw |
DiagnosisProgressProjection |
Agent 用例不应依赖具体 Redis projector |
定义 Run 到安全快照的端口,并提供 empty 实现 |
不决定 Fallback 类型 |
类型清单
| 类型 |
分类与功能 |
InformationGain |
仅 GAINED / NO_GAIN,避免引入含混质量等级 |
DiagnosisCollectionState |
COLLECTING / SATURATED,只描述信息收集状态 |
DiagnosisStopReason |
区分信息饱和、预算限制和进展协议错误 |
PreviousObservation |
模型在下一次 Tool Call 回传上一轮 tool_call_id + information_gain |
CompletedToolCall、ToolScopeIdentity |
保存已完成调用的 identity 和规范化 scope,不保存 payload |
DiagnosisProgressSnapshotState |
Tracker 的内部控制快照,含计数、pending ID 和停止指令状态 |
DiagnosisProgressSnapshot |
Release 可消费的安全快照,含 verified sources、observed facts 和 limitations |
ProgressProtocolViolationType、ProgressProtocolViolationException |
缺字段、乱序评价、意外评价和非法 Envelope 的稳定分类 |
7. Tool:执行、真相、投影与后端安全
Tool 是文件最多的职责域,但可以按四层理解。
7.1 Boundary:所有 Tool 共用的确定性入口
| 组件 |
为什么设计 |
作用 |
明确边界 |
ToolBoundary |
每个 Adapter 自己实现授权、预算、store 和 audit 会产生漂移 |
统一 preflight、active、read-only、Tool budget、bytes、canonical 状态迁移和 audit |
不理解 Tool 业务内容;不做信息增益判断 |
ToolCallRequestEnvelope |
调用必须同时证明 Run、ID、Tool、参数、授权和只读意图 |
Boundary 的内部调用信封 |
不等同于模型侧 progress Envelope |
ToolExecutor |
Boundary 不依赖具体 backend |
raw 执行函数端口 |
不投影结果 |
ToolResultProjector |
raw 到 canonical agent_result 的逻辑因 Tool 而异 |
标准化、限制、计算客观 evidence status |
不判断结论支持度 |
ToolBoundaryResult |
只允许 READY 或 ERROR 离开 Boundary |
向上返回 ID、状态、agent result、evidence status 或稳定错误码 |
PROJECTING 不对外暴露 |
ProjectedToolResult |
projector 同时返回有界 agent result 与客观状态 |
Boundary/store 的中间值 |
不是最终 Model Observation |
ToolBoundaryErrorCode |
不能把内部异常正文交给 Agent |
固定非法 ID、Run mismatch、未授权、非只读、超限、执行/投影/store 等错误 |
不包含敏感原因 |
7.2 Canonical Store:TTL 内的完整 Tool 真相
| 组件 |
为什么设计 |
作用 |
明确边界 |
CanonicalInvocationStore |
Guard 需要独立于 Agent 上下文读取原始调用真相 |
定义 begin/find/markReady/markError 状态端口 |
不负责长期审计 |
RedisCanonicalInvocationStore |
完整 request/raw 有敏感性和容量,适合短期 TTL 存储 |
原子创建,保持剩余 TTL 的状态更新,读取不续期 |
只有该 Adapter 访问 Redis |
CanonicalToolInvocation |
request、raw、agent_result 和两个状态必须形成合法组合 |
封装 PROJECTING -> READY/ERROR 转换及 Run 可引用判断 |
ERROR/PROJECTING 不可被 EvidenceGuard 引用 |
CanonicalInvocationLimits |
Redis record、raw 候选和 agent result 需要独立硬限制 |
统一 UTF-8 bytes 校验 |
不执行截断 |
ToolCallKeyFactory |
Key 要隔离 Run 且保留框架 ID |
校验安全 segment,生成 prefix/runId/toolCallId |
不生成或改写 tool_call_id |
DuplicateInvocationException、InvocationStateException、CanonicalStoreException、ResultTooLargeException |
Store 失败需要可分类而不是字符串猜测 |
区分重复、非法迁移、基础设施错误和容量错误 |
最终对 Agent 仍映射为安全错误码 |
7.3 Adapter 与 Projector:隔离业务后端
| 组件 |
为什么设计 |
作用 |
明确边界 |
RagToolAdapter |
检索实现会演进,但 Agent schema 和 Boundary 不应随之变化 |
解析 RAG request,经 Boundary 调 backend 和 RagResultProjector |
不把检索轨迹直接给模型 |
QueryLogsToolAdapter |
现有日志 Tool 的返回和时间语义需要规范化 |
校验范围、桥接 backend、投影日志事件 |
0 命中是 NO_EVIDENCE,不是技术失败 |
MysqlToolAdapter |
LLM 生成 SQL 必须经过授权和只读沙箱 |
解析 request、先校验 SQL,再经 Boundary 执行和投影 |
未配置数据源时 Tool 不注册 |
RagResultProjector |
上游 evidence、score、relevance 表达不稳定 |
输出有界证据,并保留粗粒度 relevance_level |
非空/REFERENCE 不等于支持根因 |
QueryLogsResultProjector |
raw 日志不可直接进入上下文 |
生成有界 events、pattern、scope 和截断标记 |
不泄露无限日志正文 |
MysqlResultProjector |
JDBC rows 和元数据需要稳定 Agent contract |
限制行列、单元格和 bytes,输出 rows/columns/scope |
不执行 SQL 安全判断 |
ToolProjectionLimits、MysqlToolLimits |
各投影边界必须集中、可测试 |
配置证据数、文本、行列和结果上限 |
不改变 Run 总 bytes 预算 |
7.4 MySQL 只读沙箱
| 组件 |
为什么设计 |
作用 |
明确边界 |
MysqlSqlValidator |
readOnly=true 声明不能证明 SQL 安全 |
解析并限制单条 SELECT、数据源、schema/table/column、limit 等 |
不执行查询 |
MysqlDataSourceDefinition |
连接存在不代表 Agent 可访问所有表列 |
保存逻辑数据源和 allowlist |
不携带密码 |
MysqlQueryPlan |
校验后的内容不应在 Executor 再解析原始请求 |
固定已批准数据源、SQL 和限制 |
只能由 Validator 产生 |
MysqlReadOnlyExecutor / JdbcMysqlReadOnlyExecutor |
JDBC 细节与 Harness Boundary 解耦 |
在只读连接、timeout 和 row limit 下执行 plan |
不接收未经验证的 request |
MysqlRawResult |
JDBC 原始但结构化的执行结果 |
携带 columns、rows、truncated、duration |
仍需 Projector 后才能进入 canonical agent_result |
MysqlSecurityException |
安全拒绝必须与基础设施错误区分 |
表达非法 SQL、未授权对象等 |
不向 Agent泄露详细策略 |
7.5 Tool contract 完整清单
| 类型 |
设计原因与功能 |
AgentToolContracts |
Tool 名、描述和服务端注册契约的唯一常量源,防止 Prompt/代码漂移 |
RagToolCall、QueryLogsToolCall、MysqlToolCall |
模型侧统一 Envelope:previous_observation + input |
RagToolRequest、QueryLogsRequest、MysqlToolRequest |
业务 Tool 的 typed input;Interceptor 解包后仍保持原业务协议 |
RagToolResult、QueryLogsToolResult、MysqlToolResult |
canonical agent-facing 标准结果,不等同于 raw backend response |
RagEvidence、SourceDocument |
有界知识证据及文档身份 |
RagRelevanceLevel |
PRECISE / HIGHLY_RELEVANT / REFERENCE 等客观检索相关度,不是诊断置信度 |
LogQueryScope、LogEvent、LogPattern |
日志查询实际范围、事件和聚合模式 |
LogSourceKind、LogTopic |
限制日志来源和主题的可选集合 |
ToolContractCollections |
对 contract 集合做 defensive copy 和空值规范化 |
8. Guard:把两个验证命题分开
8.1 Evidence Guard
| 组件 |
为什么设计 |
作用 |
明确边界 |
EvidenceGuard |
模型不能被信任去验证自己引用的 ID 和 Run 归属 |
严格验证 Draft、analysis ID、canonical READY、当前 Run 所有权、evidence status 和引用闭包 |
不调用模型,不判断结论语义是否成立 |
EvidenceGuardResult |
校验只能是 verified snapshot 或 violations |
阻止半有效结果继续发布 |
不包含原始 Draft 修复逻辑 |
VerifiedEvidenceSnapshot |
SemanticGuard 只能看到已验真的证据投影 |
汇总 verified analyses 和 sources |
不包含 raw Tool payload |
VerifiedAnalysisEvidence、VerifiedEvidence |
保持 analysis 到证据的归属关系 |
提供来源、scope、excerpt 等安全证据 |
不提升为业务结论 |
EvidenceViolation、EvidenceViolationCode |
Repair 和 Fallback 需要稳定失败原因 |
标识缺 analysis、未知调用、Run mismatch、非 READY、引用不闭合等 |
不保存内部异常栈 |
8.2 Semantic Guard 与模型调用边界
| 组件 |
为什么设计 |
作用 |
明确边界 |
SemanticGuard |
引用真实仍可能无法支持结论 |
用隔离单轮调用输出 SUPPORTED / UNSUPPORTED,严格解析并有限 retry |
无 Tool、无记忆、不访问 Redis、不改写 Draft |
GuardModelCall |
Router、单轮回答、Repair、Semantic 都需一致的模型预算、timeout 和 bytes 控制 |
在线程池中执行单轮 ChatModel,记录 usage,超时取消 future |
不决定业务 retry policy |
SemanticDraftView |
Guard/Repair 只应比较用户可见语义 |
从 Draft 提取稳定语义视图,并判断修复前后是否一致 |
不包含引用实现细节 |
SemanticGuardInput、SemanticGuardDecision |
固定 Guard 输入和带 verdict 的输出 |
只传 query、Draft view 和 verified snapshot |
不传 Prompt 历史或 raw Tool response |
SemanticGuardLimits、SemanticGuardPrompt |
输入、输出、单次/总 timeout 和判定职责可测试 |
限定一次语义审查的成本与 Prompt |
不暴露给 Diagnosis Agent |
GuardModelCallException |
单轮模型失败要按 timeout、transport、parse、schema 分类 |
为 Harness retry 提供类型化信号 |
不直接映射用户内容 |
9. Release:唯一公开决策点
为什么需要
如果 Agent、Guard、Application 都可以各自构造结果,同一个失败会出现不同用户语义,迟到内容也可能绕过安全校验。Release 必须集中回答一个问题:当前 Run 有哪些内容可以公开?
| 组件 |
为什么设计 |
作用 |
明确边界 |
DiagnosisReleaseUseCase |
Draft、停止、Guard 和 Repair 的组合分支必须只有一个所有者 |
处理有结论、无结论、受控停止、非法 Draft;协调 Guard/Repair/Semantic;返回成功或 Fallback |
不持久化、不发送 SSE、不写新结论 |
EvidenceRepair |
引用或结构小错不应总是丢掉语义正确的 Draft |
单轮修复引用,严格解析,并用 SemanticDraftView 保证用户语义不变 |
只 attempt 一次;不新增事实、不改结论 |
SafeFallbackFactory |
失败文案若交给模型生成会再次引入幻觉 |
确定性构造 evidence failed、semantic unsupported/unavailable、insufficient evidence、missing context |
只使用已验真事实和有界问题码 |
DiagnosisReleaseResult |
下游不能同时收到 Draft 和 Fallback |
类型化承载 outcome、Draft、verified evidence 或 SafeFallback |
不等同于 RunState |
EvidenceRepairLimits、EvidenceRepairPrompt |
Repair 的成本与职责必须比 Diagnosis Agent 更窄 |
限制输入/输出/timeout,固定只修引用的指令 |
不允许 Tool Calling |
10. Retry:显式、类型化、可审计的 attempt
为什么需要
重试会改变成本、延迟和副作用,必须是调用者的有意识决策。统一 Executor 负责循环,但具体组件持有自己的 Policy。
| 类型 |
设计原因与功能 |
HarnessRetryExecutor |
在每个 attempt 前检查 Run active,统一记录成功/失败,并绝不吞掉取消和预算耗尽 |
HarnessRetryPolicies |
集中定义 Router/SemanticGuard 最多两次,其余一次的严格策略 |
RetryPolicy |
maxAttempts + retryable failures 的不可变值,避免布尔 retry=true |
RetryFailure |
timeout、transport、parse、schema、invalid output、cancel、budget 等稳定分类 |
RetryAttempt |
记录 attempt 序号、是否成功和失败类型,供 Trace 使用 |
RetryOperation、RetryFailureClassifier |
将执行和异常分类作为端口注入,Executor 不依赖具体模型组件 |
RetryExecutionException |
重试终止时保留最终 attempt 和失败分类 |
11. Audit:记录控制事实,而不是复制业务正文
为什么需要
诊断系统需要回答“为什么停止、调用了什么、Token 是否对账、发布为何降级”,但普通观察面不应长期保存 Prompt、SQL、日志 query、raw response 或 reasoning。
| 组件 |
为什么设计 |
作用 |
明确边界 |
DiagnosisTraceRecorder / JpaDiagnosisTraceRecorder |
所有阶段需要同一 exact-run timeline |
按 Run 分配 sequence,追加安全事件;失败 best-effort |
不改变业务结果,不保存敏感正文 |
TraceAuditEvents |
各组件手写 details 容易字段漂移或泄露 |
集中构造 run/routing/model/tool/progress/guard/release 事件 |
只接受有界、安全字段 |
DiagnosisTraceAuditEvent |
Recorder 与业务组件解耦 |
统一事件 identity、phase、type、status、details |
不是领域事件总线 |
ToolInvocationAuditSink / JpaToolInvocationAuditSink |
canonical raw 不能长期保存,但调用元数据要留存 |
保存 exact Run、Tool ID、状态、耗时、bytes 和有界 enrichments |
不保存完整 request/raw/agent result |
RagLookupAuditEnricher |
RAG 需要有限的质量诊断字段 |
从受限输入提取 step/query/relevance 等安全摘要 |
不打印完整 rewritten/raw query |
HarnessAgentAuditHook |
框架每轮 Agent 模型步骤需关联数据库 step 和 Provider reasoning 可用性 |
写 AgentStep metadata,并把 reasoning/assistant text 放独立受限存储 |
reasoning 不进普通 Timeline、Guard 或下一轮上下文 |
AgentStepAuditTracker |
Tool audit 需要关联当前 Agent step,但不能使用 ThreadLocal |
按 runId 显式绑定/查询/清理 stepId |
不保存 Step 实体 |
ModelCallAuditor / ModelCallLedger |
所有模型入口都要按组件、轮次对账 Token |
begin call、记录 Provider usage、写 MODEL_TOKEN_USAGE、生成 Run reconciliation |
Usage 缺失时标 unavailable,不估算 |
RunConclusionExtractor |
Trace/DB 常需直接读取安全发布结论 |
从 public JSON 提取 conclusion |
不读取 Provider reasoning |
Audit 类型清单
| 类型 |
分类与功能 |
ModelCallComponent |
Router、System、Knowledge、Diagnosis、Repair、Semantic 的统一组件枚举,并映射 Trace phase |
ToolInvocationAuditEvent |
durable Tool metadata 事件 |
TracePhase、TraceEventType、TraceEventStatus |
Timeline 的阶段、事件和结果词汇表 |
12. Contract:跨组件唯一语言
为什么需要
Harness 横跨模型 JSON、Java 对象、Redis、JPA 和 SSE。若状态以自由字符串在各层重复定义,SUCCESS、READY、EVIDENCE_FOUND 很容易被混为一谈。Contract 包将不同维度保持正交。
| 类型 |
为什么存在与表达什么 |
DiagnosisDraft |
Diagnosis Agent 唯一结构化输出;analysis 绑定 Tool Call 引用,允许 conclusion=null |
KnowledgeAnswerDraft |
Knowledge Query 单轮模型输出,答案项绑定文档来源 |
PublishedResult |
只有成功、安全的诊断结果可持久化为下一轮候选 |
PreviousTurn |
PublishedResult 的有界历史投影,不是完整会话记录 |
SafeFallback |
确定性公开降级结构,包含 verified sources、observed facts、limitations、next steps 和 validation issues |
SourceDocument |
知识回答中的文档身份与来源 |
IntentType |
SYSTEM_CHAT / KNOWLEDGE_QUERY / DIAGNOSIS 路由结果 |
InvocationStatus |
Tool invocation 生命周期:PROJECTING / READY / ERROR |
EvidenceStatus |
Tool 客观结果:EVIDENCE_FOUND / NO_EVIDENCE / ERROR |
SemanticVerdict |
最终推论支持度:SUPPORTED / UNSUPPORTED |
ReleaseOutcome |
Harness 发布结果:SUCCESS / FALLBACK / FAILED / CANCELLED |
SseOutcome |
当前未被运行时消费的遗留枚举;公开 done 实际使用 ReleaseOutcome,不要把它作为 SSE 协议真理源 |
FallbackType |
evidence validation、semantic、insufficient evidence、missing context 等降级原因 |
AnalysisKind |
区分正向证据分析与限定范围的负向观察 |
ContractCollections |
对公共 contract 集合做 defensive copy、去空和不可变处理 |
13. 配置装配:组件如何真正连起来
HarnessChatConfiguration 不在 harness 包内,但它是运行时组件图的 Composition Root:
- 构造两个有界线程池:Chat worker 与 Harness model executor;
- 从
ChatHarnessProperties 创建 Run、预算、停止阈值和各模型调用 limits;
- 装配 Redis canonical store、ToolBoundary、三类 Adapter/Projector;
- 仅在存在有效逻辑数据源时注册
query_mysql;
- 为每个 Run 创建带 Model/Tool interceptor 和 Audit Hook 的 Agent;
- 装配 Guard、Repair、Release、Router、三个执行分支和顶层 Application UseCase。
它存在的原因是让所有限制和替换点在一个地方可见。组件内部不应自行读取 Spring 配置或寻找全局 Bean,否则 focused test 很难证明其边界。
14. 用调用链快速定位组件
| 想回答的问题 |
首先阅读 |
接着阅读 |
| 一次请求如何创建并结束 Run |
ChatApplicationUseCase |
DiagnosisHarnessCore、JpaChatRunStore |
| ReAct 每轮如何计预算 |
DiagnosisAgentFactory |
HarnessModelInterceptor、ModelCallAuditor |
| Tool 为什么被拒绝 |
HarnessToolInterceptor |
DiagnosisProgressTracker、ToolBoundary |
| Tool 结果为何没有原样给模型 |
ToolBoundary |
具体 ResultProjector、ToolResultViewProjector |
| 一条 evidence_ref 如何验真 |
EvidenceGuard |
CanonicalToolInvocation、具体 ToolResult contract |
| 为什么最终是 FALLBACK |
DiagnosisReleaseUseCase |
SafeFallbackFactory、Trace 的 RELEASE 事件 |
| 为什么诊断停止继续查 |
DiagnosisProgressTracker |
Tool progress / rejection / collection stop Trace |
| Token 为什么对不上 |
ModelCallAuditor |
ModelCallLedger、RUN_FINISHED reconciliation |
15. 组件边界自检
未来新增能力时,可以用下面的问题判断放置位置:
- 它是在判断业务根因吗?应留在 Diagnosis Agent,而不是 Core/Tool/Guard。
- 它能由代码机械证明吗?应放在 Boundary、Tracker 或 EvidenceGuard。
- 它需要完整 Tool 真相吗?读取 canonical store,不要从 Agent observation 反推。
- 它要决定公开内容吗?只能进入 Release,不要在 Application 或 Guard 私自构造。
- 它是短期验真数据还是长期运营元数据?前者 canonical,后者 metadata audit。
- 它会再次调用模型吗?必须进入 Core budget、ModelCallAuditor、timeout 和显式 retry policy。
- 它引入了新的状态吗?先确认是否只是 error code、stop reason 或 fallback reason,避免再造全局生命周期。