面向 Java 工程师的 Agent Harness 学习指南
读者定位
本文面向有以下背景的工程师:
- 熟悉 Spring Boot
- 做过微服务、MVC、AOP、权限、审计、网关、插件化
- 理解常见设计模式
- 还没有系统做过 Agent Runtime
本文不是"Claude Code 源码导游",而是借助 Claude Code / Open-ClaudeCode 这类项目,抽取其中最重要的工程思想,帮助你建立 Agent Harness 的架构认知。
1. 什么是 Harness
1.1 不要把 Agent 理解成"一个 Prompt"
很多人第一次看 Agent,会把它理解成:
- 一个大 Prompt
- 加几个 Tool
- 再接一个聊天界面
这不够准确。
真正的 Agent 系统,尤其是 Claude Code 这类 coding agent,核心不是 prompt,而是 Harness。
1.2 Harness 的定义
Harness 可以理解为:
围绕 LLM 的执行外壳,用来把"文本生成"升级成"可控任务执行"。
它通常负责:
- 接收用户输入
- 组装 Prompt / Context
- 发起模型调用
- 解析模型返回的
text / tool_use - 执行工具
- 把
tool_result再写回上下文 - 控制多轮循环继续或终止
- 保存状态、记忆、会话
- 做权限、安全、审计、确认
- 提供插件、子 Agent、外部工具接入能力
1.3 Java 类比
如果用 Java 的眼光看,Harness 很像下面这些组件的组合体:
WorkflowEngineCommandBusPolicyEngineSessionStateStorePluginContainerIntegrationGateway
如果 Spring MVC 是"请求驱动的控制流",那么 Harness 更像"状态驱动的推理执行引擎"。
2. Claude Code 这类系统的整体架构
从工程上看,这类系统大致可以拆成 6 层。
2.1 交互入口层
负责接用户输入,可能是:
- REPL
- CLI
- IDE 集成
- SDK
- 远程桥接
这一层不重要,重要的是所有输入最终都会落到同一个 Harness Loop。
Java 类比:
- Controller
- CLI Adapter
- SDK Facade
2.2 Harness Kernel
这是系统内核,负责:
- 接收当前会话状态
- 组装 Prompt / Context
- 调用模型
- 解析 Tool Call
- 执行 Tool
- 更新上下文
- 控制循环
Java 类比:
HarnessLoopEngineTurnStateMachine
2.3 Tool Runtime
这是 Agent 的"命令执行层",负责:
- Tool 的注册与发现
- 输入 Schema
- 校验
- 权限
- 执行上下文
- 结构化返回结果
Java 类比:
- Command Pattern
- Registry Pattern
- Chain of Responsibility
2.4 State / Memory / Context
这是 Agent 不会"每轮失忆"的关键层,负责:
- 会话消息保存
- transcript 持久化
- 项目规则注入
- 工作记忆
- 长上下文裁剪和压缩
Java 类比:
- Session Aggregate
- Event Store
- Snapshot / Compaction Service
2.5 Governance Layer
负责把系统从 demo 提升到工程系统:
- Hook
- 权限控制
- 高风险确认
- 安全策略
- 审计能力
Java 类比:
- Filter
- Interceptor
- AOP
- Approval Workflow
2.6 Extension Layer
包括:
- Subagent
- MCP
- Plugin
- Remote Bridge
这层不是内核本身,但对平台化很重要。
3. 主执行循环(Execution Loop)
3.1 核心思想
Agent 不是"一次模型调用",而是一个循环:
- 拿到当前消息和上下文
- 组装 Prompt
- 调用模型
- 解析返回内容
- 如果有 Tool Call,就执行 Tool
- 把 Tool 结果写回消息
- 再次调用模型
- 直到完成或中断
3.2 执行链路
text
用户输入
-> 输入处理
-> Prompt / Context 组装
-> LLM Gateway
-> Assistant content blocks
-> 解析 tool_use
-> Tool Runtime 执行
-> 生成 tool_result
-> 追加回 messages
-> 循环继续
-> 结束 / 中断 / 达到上限
3.3 这部分在 Open-ClaudeCode 中的典型锚点
- 主循环:query.ts
- 会话入口:QueryEngine.ts
- Prompt 组装:queryContext.ts
- 模型调用:claude.ts
3.4 Java 伪代码
java
public class HarnessLoopEngine {
public TurnResult runTurn(SessionState session, UserInput input) {
ProcessedInput processed = inputProcessor.process(input, session);
PromptEnvelope prompt = promptAssembler.build(session, processed);
TurnState state = TurnState.start(session.getMessages(), prompt);
while (true) {
LlmResponse response = llmGateway.generate(state);
AssistantMessage assistant = response.toAssistantMessage();
state.appendAssistant(assistant);
List<ToolCall> toolCalls = assistant.extractToolCalls();
if (toolCalls.isEmpty()) {
session.replaceMessages(state.getMessages());
return TurnResult.completed(assistant);
}
List<ToolResultMessage> results =
toolRuntime.execute(toolCalls, state.getToolContext());
state.appendToolResults(results);
if (state.exceedsMaxTurns()) {
return TurnResult.maxTurns();
}
if (state.isCancelled()) {
return TurnResult.cancelled();
}
}
}
}
3.5 Java 工程师要学到什么
- 这不是 Controller,而是状态机
- Tool 调用不是附属逻辑,而是循环的一部分
- Loop 的终止、中断、重试、压缩都必须由 Harness 管
4. Tool Runtime
4.1 Tool 不是普通函数
在成熟 Agent 里,Tool 不应该只是一个 execute() 方法。
Tool 至少要有:
- 元数据
- 输入 Schema
- 校验逻辑
- 权限要求
- 执行上下文
- 返回结果映射
4.2 Tool Runtime 的典型组成
ToolDefinitionToolRegistryToolExecutorToolPermissionServiceToolInterceptorChainToolResultMapper
4.3 Open-ClaudeCode 的锚点
- Tool 抽象:Tool.ts
- Tool 注册:tools.ts
- Tool 执行:toolExecution.ts
- Tool 调度:toolOrchestration.ts
4.4 Java 类比
这部分最像:
- Command Pattern
- Strategy + Registry
- Policy Guard
- Adapter
4.5 最小抽象示例
java
public interface ToolDefinition<I, O> {
String name();
JsonSchema inputSchema();
boolean isReadOnly(I input);
}
public interface ToolHandler<I, O> {
ToolExecutionResult<O> execute(I input, ToolContext context);
}
4.6 为什么这层很关键
没有 Tool Runtime,Tool 很快会退化成:
- 到处散落的函数
- 没有统一权限
- 没有统一审计
- 没法稳定接入模型
有了 Tool Runtime,模型只是在"发出请求",真正掌权的是 Harness。
5. State / Memory / Context
5.1 为什么 Agent 不会每轮失忆
不是因为它有某个神秘 Memory SDK。
而是因为系统做了三件事:
- 保留会话消息
- 每轮重新注入稳定上下文
- 对长历史做压缩而不是简单丢弃
5.2 会话记忆
最基础的 memory 就是当前会话的 messages。
下一轮请求继续带上:
- 之前的 user message
- 之前的 assistant message
- 之前的 tool_result
那 Agent 就有了短期记忆。
Java 类比:
SessionState.messages
5.3 Transcript 持久化
成熟 Agent 会把消息落盘或入库,这样进程重启还能恢复。
Open-ClaudeCode 的锚点:
- transcript 存储:sessionStorage.ts
Java 类比:
- append-only event log
conversation_events表
5.4 工作记忆
工作记忆不是自然语言摘要,而是运行时状态,例如:
- 已读过哪些文件
- 当前 turn 数
- 已调用哪些 skill
- 当前任务或 todo
- 某些中间缓存
Open-ClaudeCode 的锚点:
- 会话状态:QueryEngine.ts
- AppState:AppStateStore.ts
5.5 项目级规则和稳定上下文
Claude Code 这类系统不会只依赖聊天历史。
它每轮还会重新注入:
- system prompt
- 项目规则
CLAUDE.md- skills
- git 状态
- 当前日期
Open-ClaudeCode 的锚点:
- 上下文:context.ts
- Prompt 组装:systemPrompt.ts
- 项目规则:claudemd.ts
5.6 长上下文裁剪 / 压缩 / 摘要
如果任务足够长,上下文一定会爆。
所以成熟 Harness 不会简单做"截断",而是做:
- tool result 预算控制
- snip / trim
- microcompact
- autocompact
- collapse
- summary 后再补回关键附件
Open-ClaudeCode 的锚点:
- 主循环中的 compact 逻辑:query.ts
- compact 服务:compact.ts
5.7 Java 工程师要抓住的抽象
memory 不等于向量库。
更准确的理解是:
- 会话记忆 = messages + transcript
- 工作记忆 = runtime state + cache
- 长期记忆 = 规则文件 + memory 文件
- 压缩记忆 = summary + compact boundary
6. Hook / Permission / Safety
6.1 Hook / 事件拦截机制
Hook 解决的问题是:
不要把日志、审计、策略、额外上下文注入、外部联动都写死在主循环里。
它本质上是 Harness 的拦截器总线。
Open-ClaudeCode 的锚点:
- Hook 引擎:hooks.ts
- Tool Hook 封装:toolHooks.ts
典型事件包括:
- UserPromptSubmit
- PreToolUse
- PostToolUse
- PermissionDenied
- Stop
- SubagentStart
Java 类比:
- Servlet Filter
- HandlerInterceptor
- AOP Around Advice
- Domain Event
6.2 Tool 前后的权限控制
在真正的 Agent 里,Tool 不是"模型要调用就调用"。
典型流程是:
text
解析输入
-> Schema 校验
-> 业务校验
-> PreTool Hook
-> 权限规则判定
-> 如有必要要求确认
-> 执行 Tool
-> PostTool Hook
Open-ClaudeCode 的锚点:
- 权限引擎:permissions.ts
- 执行与权限结合:toolExecution.ts
6.3 危险命令 / 高风险操作确认
只要 Tool 具备下面任意一种能力,就需要确认机制:
- 写文件
- 执行命令
- 访问网络
- 调起子 Agent
成熟系统通常支持:
ALLOWDENYASK
这本质上是 step-up authorization。
Open-ClaudeCode 的锚点:
- 危险权限识别:permissionSetup.ts
6.4 这一层为什么是核心
很多人把安全机制当"产品增强项",这是错误的。
对 coding agent 来说:
- Tool Runtime
- Permission
- Dangerous Action Confirmation
这三者和主循环一样,都是 Harness 内核的一部分。
7. Verification:测试 / 修复 / 总结
7.1 为什么 Verification 是 Agent 架构的一部分
一个真正有工程价值的 coding agent,不应该停在"我改完了"。
更合理的闭环是:
- 读取和理解
- 修改
- 生成 diff
- 运行验证
- 失败则修复
- 最后总结结果
这不是"体验优化",而是工程闭环。
7.2 Open-ClaudeCode 中可以看到的信号
- 编辑类 Tool:FileEditTool.ts
- diff 能力:gitDiff.ts
- verify 技能:verify.ts
7.3 Java 类比
你可以把 Verification 理解成:
- 一个轻量 CI Loop
- Patch + Diff + Validate 的执行链
7.4 关键学习点
对 Java 工程师来说,这一节最重要的思想是:
Agent 不只负责"生成方案",还应该负责"验证结果"。
8. Subagent / MCP / Plugin 的定位
8.1 Subagent
Subagent 的作用是:
- 任务分解
- 角色化协作
- 上下文隔离
- 权限隔离
Open-ClaudeCode 的锚点:
- 子 Agent Tool:AgentTool.tsx
- 子 Agent 运行:runAgent.ts
- fork 机制:forkedAgent.ts
Java 类比:
- Child Workflow
- Composite Command
- Fork/Join
定位:
- 很有价值
- 但不是 MVP 必需项
8.2 MCP
MCP 的作用是统一接外部能力,包括:
- 外部 Tool
- 外部 Resource
- 外部 Prompt / Command
Open-ClaudeCode 的锚点:
- MCP 客户端:client.ts
- MCP Tool 封装:MCPTool.ts
Java 类比:
- Connector SDK
- Integration Gateway
- Adapter Bus
定位:
- 高价值扩展项
- 不是 Harness Kernel 必需项
8.3 Plugin
Plugin 的作用是:
- 包装扩展能力
- 支持安装、启停、加载
- 不改主仓库也能增量扩展
Open-ClaudeCode 的锚点:
- 插件加载器:pluginLoader.ts
- 插件 Agent 加载:loadPluginAgents.ts
Java 类比:
- Spring Boot Auto Configuration
- SPI / ServiceLoader
- Plugin Container
定位:
- 平台化增强项
- Demo 完全可以先不做
8.4 一句话归类
text
Kernel:
Execution Loop / Tool Runtime / Memory / Permission / Safety
Advanced:
Verification / Subagent / MCP
Platform:
Plugin / Remote Bridge / Marketplace Style Extension
9. Java 工程师应该如何迁移思维
9.1 从"请求响应"迁移到"状态循环"
传统 Spring Boot 思维:
- 一次请求
- 一次服务调用链
- 一次响应
Agent 思维:
- 一次用户意图
- 多轮推理和动作
- 模型输出驱动下一步
- 状态跨轮保留
9.2 从"方法调用"迁移到"受控命令执行"
不要把 Tool 看成工具类方法。
更准确的理解是:
- Tool 是命令
- Tool Runtime 是命令执行总线
- Permission 是命令闸门
- Hook 是命令拦截器
9.3 从"模板 Prompt"迁移到"动态上下文组装"
不要把 Prompt 看成一个静态大字符串。
真正工程化的 Prompt 由多部分组成:
- system prompt
- 项目规则
- 当前任务信息
- 历史消息
- tool result
- memory / summary / attachments
9.4 从"日志"迁移到"可追踪的推理执行"
一个成熟 Agent 不能只记接口日志。
它至少要能追踪:
- 当前 turn
- 当前消息集
- Tool 调用
- 权限决策
- verify 结果
- summary / compact 行为
9.5 从"业务服务"迁移到"Runtime 内核"
不要先想:
AgentService.answer()
要先想:
HarnessLoopEngineToolRuntimeSessionStatePolicyEngine
10. 一条循序渐进的学习路线
阶段一:先吃透内核
先学这些:
- 什么是 Harness
- Execution Loop
- Prompt / Context Assembly
- Tool Runtime
- Session State
目标:
你能完整解释"一条用户输入如何经过多轮 model-tool-model 执行直到结束"。
阶段二:再学治理能力
继续学:
- Hook
- Permission
- Dangerous Action Confirmation
- Audit
目标:
你能解释"为什么 Agent 不是 LLM + Tools 的简单拼接"。
阶段三:再学记忆和长任务稳定性
继续学:
- transcript
- compact
- summary
- 工作记忆
- 项目规则注入
目标:
你能解释"为什么 Agent 不会每轮失忆,也不会长上下文崩掉"。
阶段四:最后学扩展能力
继续学:
- Verification 闭环
- Subagent
- MCP
- Plugin
- Remote Bridge
目标:
你能清楚区分:
- 什么是 Harness 必需项
- 什么是高级增强项
- 什么是平台产品能力
11. 如何基于 LangChain4j 自己实现一个最小 Harness
11.1 总原则
可以用 LangChain4j 做模型集成,但不要把 LangChain4j 当成完整架构。
特别不要让 @Tool 注解方法直接成为你的全部 Tool Runtime。
更稳妥的做法是:
- LangChain4j 只负责模型调用
- Harness 自己实现 Loop、Tool Runtime、State、Permission、Memory
11.2 建议的最小类设计
java
public class SessionState {
String sessionId;
List<Message> messages;
int turnCount;
WorkingMemory workingMemory;
}
public class WorkingMemory {
Set<String> readFiles;
Set<String> invokedSkills;
Map<String, Object> attributes;
}
public interface PromptAssembler {
PromptEnvelope build(SessionState session, UserInput input);
PromptEnvelope rebuildFromSession(SessionState session);
}
public interface LlmGateway {
LlmResponse generate(PromptEnvelope prompt, List<ToolSpec> visibleTools);
}
public interface ToolRuntime {
List<ToolResultMessage> execute(List<ToolCall> calls, ToolContext context);
ToolExecutionResult executeOne(ToolCall call, ToolContext context);
}
public interface PermissionService {
PermissionDecision decide(ToolCall call, ToolContext context);
}
public interface HookChain {
void beforeTool(ToolCall call, ToolContext context);
void afterTool(ToolCall call, ToolExecutionResult result, ToolContext context);
}
public interface TranscriptStore {
void append(String sessionId, Message message);
List<Message> load(String sessionId);
}
11.3 最小主循环伪代码
java
public class MinimalHarness {
public AssistantMessage run(SessionState session, UserInput input) {
PromptEnvelope prompt = promptAssembler.build(session, input);
while (true) {
LlmResponse response =
llmGateway.generate(prompt, toolRegistry.visibleTools());
AssistantMessage assistant = response.toAssistantMessage();
session.messages.add(assistant);
transcriptStore.append(session.sessionId, assistant);
List<ToolCall> calls = assistant.extractToolCalls();
if (calls.isEmpty()) {
return assistant;
}
List<ToolResultMessage> toolResults = new ArrayList<>();
for (ToolCall call : calls) {
hookChain.beforeTool(call, toolContext);
PermissionDecision decision =
permissionService.decide(call, toolContext);
if (!decision.isAllowed()) {
toolResults.add(ToolResultMessage.denied(call));
continue;
}
ToolExecutionResult result =
toolRuntime.executeOne(call, toolContext);
hookChain.afterTool(call, result, toolContext);
toolResults.add(result.toToolResultMessage(call));
}
session.messages.addAll(toolResults);
for (ToolResultMessage msg : toolResults) {
transcriptStore.append(session.sessionId, msg);
}
prompt = promptAssembler.rebuildFromSession(session);
}
}
}
11.4 MVP 必须有的东西
- 持久化
messages - 一个最小 TranscriptStore
- 统一 Tool 抽象
ALLOW / ASK / DENY三态权限- 至少一个
beforeTool / afterTool拦截点 - 基本 timeout / interrupt
11.5 MVP 可以先不做的东西
- Plugin
- MCP
- Subagent
- 远程桥接
- 复杂 summary / compact
- 很细的 Hook 生命周期
11.6 推荐最小范围
第一版只做这些就够了:
- 一个 chat 接口
- 一个 HarnessLoopEngine
- 一个 SessionStateStore
- 四个 Tool:
Read / Grep / Edit / Bash - 一个 PermissionService
- 一个 TranscriptStore
- 一个简单的 summary 或 compact 策略
这已经足够让你真正掌握 Harness,而不是只停留在"会调模型"。
12. 最后总结
通过 Claude Code 这类项目,Java 工程师最应该学到的,不是某个 Prompt 写法,而是下面这些工程抽象:
- Harness 是 Runtime 内核,不是 UI 功能
- Execution Loop 是状态机,不是一次 RPC
- Tool Runtime 是受控命令执行框架,不是工具方法集合
- Memory 是分层状态保存协议,不等于向量库
- Hook / Permission / Safety 是内核必需项,不是后补的装饰
- Verification 让 Agent 从"会说"变成"会做并会验证"
- Subagent / MCP / Plugin 是高级扩展,不是第一天就必须做
如果你把这些抽象吃透,再去看具体模型、具体框架、具体协议,理解成本会低很多。
13. 建议配合阅读的源码锚点
如果你想把这份学习文档和 ClaudeCode 的源码对应起来,可以按下面这些锚点看:
- Harness 主循环:query.ts, QueryEngine.ts
- Prompt / Context:queryContext.ts, context.ts, systemPrompt.ts
- Tool Runtime:Tool.ts, tools.ts, toolExecution.ts, toolOrchestration.ts
- State / Memory:AppStateStore.ts, sessionStorage.ts, memdir.ts, compact.ts
- Hook / Permission / Safety:hooks.ts, permissions.ts, permissionSetup.ts
- Verification:FileEditTool.ts, gitDiff.ts, verify.ts
- 高级扩展:AgentTool.tsx, runAgent.ts, client.ts, pluginLoader.ts, replBridge.ts