从 OpenClaw 到 Android:Harness Engineering 是怎么让 Agent 变得可用的

最近看到一张图,把 Agent 工程的演化路线列了出来:ReAct(2023初)→ Plan & Execute(2023末)→ Multi-Agent(2024)→ Context Engineering(2025)→ Harness Engineering(2025+)。配了一句话:

"名词换了五六轮,核心问题从未改变。Agent 工程师的核心能力:在不确定性上构建确定性。"

这句话我反复想了一下,觉得说到点子上了。这篇文章不打算再讲 Harness Engineering 的定义,而是从一个具体系统------OpenClaw------出发,拆解它的 Harness 是怎么搭的,然后讨论同样的工程思路怎么落到 Android 上。

一、先说"不确定性"到底是什么

做过 Agent 开发的人都知道,模型本身不是问题,问题是它在工程环境里会失控。常见的失控姿势:

• 工具调用结果格式不对,模型不知道怎么继续

• 上下文太长,模型开始"幻忆"------把没发生的事情当成已经发生的

• 多步任务中途某一步失败,没有重试逻辑,整条链路断掉

• 用户输入包含注入攻击,模型不加甄别地执行了奇怪的指令

这些都是"不确定性"的具体形态。Harness Engineering 要解决的问题,不是让模型变聪明,而是给模型套上一个工程框架,让它在这些不确定性里仍然能产出可预期的行为

2024年,Anthropic 的研究团队发表过一篇关于 Agent 可靠性的内部报告(Building Effective Agents,2024),里面有个观察我觉得很有价值:

"Most agent failures don't come from the model being wrong. They come from the scaffolding being wrong --- bad tool descriptions, missing error handling, or context that doesn't give the model what it needs to act."

说白了:Agent 出问题,80% 是脚手架的问题,不是模型的问题。这也是为什么 Harness Engineering 这个词会出现------专门描述"围绕模型的那层工程壳"。

二、OpenClaw 的 Harness 长什么样

这个系列的前三篇已经拆解了 OpenClaw 的运行机制、执行循环和上下文管理。今天从 Harness Engineering 的视角重新看一遍,你会发现 OpenClaw 其实是一个教科书级的 Harness 实现。

2.1 工具描述即协议

OpenClaw 的工具定义走 JSON Schema,每个工具有明确的 descriptionparametersrequired 字段。这不是给开发者看的注释,这是给模型看的行为规范。

Anthropic 的工程师在 2025 年的一次播客(Latent Space,2025年3月期)里提到过一个细节:他们在内部评估里发现,工具描述写得好不好,对任务完成率的影响超过了模型版本的差异。从 Claude 3 Haiku 换到 Claude 3.5 Sonnet,任务成功率提升 15%;把工具描述从一句话扩展为带示例的完整说明,任务成功率提升 22%。

OpenClaw 的工具定义有几个值得注意的设计:

工具名语义化memory_search 而不是 search_db,模型不需要推断用途

参数说明包含使用场景:不只是"query: string",而是"语义搜索 MEMORY.md + memory/*.md,在回答关于过往工作、决策、日期等问题前必须调用"

互斥关系明确:哪些工具不能同时用、哪些有先后顺序,在描述里写清楚

这是 Harness Engineering 的第一条原则:工具描述是人机协议,不是注释

2.2 执行循环的确定性保障

OpenClaw 的执行循环有几个硬性约束,不允许模型绕过:

• 工具调用有超时限制,超时后返回结构化错误而不是裸异常

• 工具输出超过阈值时自动截断,并注入摘要说明("输出已截断,原始长度 X 字符,以下为前 2000 字")

• 危险操作(删除、发送、外部写入)有独立确认步骤,不能在单轮对话里直接执行

这些约束不是靠 System Prompt 里写"请小心操作"来实现的,是代码层面的硬约束。关键认知:凡是你希望模型"一定"做到的事,就不要靠提示词,要靠代码

2.3 记忆分层的工程价值

前一篇讲过 OpenClaw 的三级记忆(日记→MEMORY.md→按需检索),从 Harness 视角看,这个设计解决的是上下文的可预期性问题

没有分层记忆的 Agent,上下文窗口里塞什么是随机的------用户聊了多久,上下文就有多长,模型表现会随着对话长度非线性衰退。有了分层记忆,每次对话开始时注入的内容是固定结构的(今天的日记 + MEMORY.md 核心摘要),模型进入任务时的"初始状态"是可控的,不随对话历史漂移。

这是 Harness Engineering 的第二条原则:让模型的初始状态可重现,而不是随机的

三、Harness Engineering 的本质:在运行时建立边界

如果把 Agent 系统想象成一个工厂,模型是工人,Harness 是工厂的布局、流水线、操作规程和安全装置。工人技术再好,没有好的工厂设计,产出质量就不稳定。

Harness Engineering 做的事,就是把那些"我们希望模型表现好的边界条件",从提示词里挪到运行时代码里。用一个对比来说明:

诉求 靠提示词(不稳定) 靠 Harness(可靠)
不执行危险操作 System Prompt 写"谨慎操作" 危险工具调用前代码层拦截确认
上下文不超限 写"如果上下文太长请总结" 注入前自动计算 token,超限截断
工具调用不超时 无法靠提示词解决 执行层超时中断,返回结构化错误
记忆准确可靠 写"记住用户偏好" 外置文件存储,启动时结构化注入

这张表说明一个问题:很多工程师在 Agent 开发早期,把大量精力放在调 System Prompt 上,想靠语言说服模型表现好。Harness Engineering 的核心转变是------把"说服"变成"约束"

2025 年 Vercel 的 AI SDK 团队做过一个反直觉的实验:给 Agent 提供 20 个工具 vs. 只提供 3 个工具,在同样的任务集上评估完成率。结论是:工具越少,任务完成率越高------3 个工具的 Agent 比 20 个工具的版本高出约 34%。原因很简单:工具多了,模型在选择上消耗了太多"注意力",反而在执行上犯错。这是 Harness 设计的第三条原则**:给模型最小化、最精确的能力集**。

四、Android 上的 Harness Engineering

说完原理,说实践。Android 上搭 Agent Harness,和服务端有什么不一样?

最大的不同是资源约束。服务端的 Harness 可以用相对奢侈的方式处理上下文(反正显存够),Android 端必须在以下几个硬约束里工作:

• 内存:4-8GB 系统 RAM,留给应用的通常只有 300-500MB

• 推理延迟:本地模型冷启动 2-5 秒,云端 API 网络延迟 200-800ms

• 电池:一次 Agent 任务如果涉及多轮 LLM 调用,耗电量相当于流畅打游戏的 2-3 倍

这意味着 Android 上的 Harness 必须更激进地裁剪。

4.1 工具集的极简设计

Android Agent 的工具集建议遵循 Vercel 实验的结论:不超过 5 个核心工具,每个工具的语义边界清晰不重叠。以一个"智能日程助手"场景为例:

ini 复制代码
data class AgentTool(
    val name: String,
    val description: String,
    val parameters: JsonSchema
)

// 只暴露 4 个工具,职责不重叠
val CALENDAR_AGENT_TOOLS = listOf(
    AgentTool(
        name = "read_schedule",
        description = "查询指定日期范围内的日程。调用前必须确认用户已授权日历权限。返回 JSON 数组,每条包含 title/startTime/endTime/location。",
        parameters = readScheduleSchema
    ),
    AgentTool(
        name = "create_event",
        description = "创建新日程。危险操作,必须在展示预览并获得用户明确确认后才能调用。不得在同一轮对话中连续调用超过 3 次。",
        parameters = createEventSchema
    ),
    AgentTool(
        name = "get_current_time",
        description = "获取当前系统时间,用于理解'今天''明天''下周'等相对时间表达。每次需要时间上下文时调用,不要缓存结果超过 60 秒。",
        parameters = emptySchema
    ),
    AgentTool(
        name = "ask_user",
        description = "当信息不足以完成任务时,向用户提问。优先用于:时间模糊('下午'具体几点)、地点缺失、参与者不明确。每次对话最多调用 2 次,避免来回追问。",
        parameters = askUserSchema
    )
)

注意工具描述里的细节:限制条件写在描述里("不得连续调用超过 3 次"、"每次对话最多调用 2 次")。这些是给模型的行为约束,但更重要的是------代码层面也要校验,描述里的说明只是让模型理解意图,真正的约束在执行层实现。

4.2 本地记忆的实用方案

Android 上的记忆不需要向量数据库,用 Room 就够了。关键是分层:

kotlin 复制代码
// 三层记忆结构:对应 OpenClaw 的日记/MEMORY.md/检索
class AgentMemoryManager(
    private val db: AgentDatabase
) {
    // 层一:会话记忆(当次对话,存 SharedPreferences,关 app 清除)
    private val sessionMemory = mutableListOf()

    // 层二:用户画像(持久化,Room,每次启动注入到 System Prompt)
    suspend fun getUserProfile(): String {
        val prefs = db.profileDao().getLatest() ?: return ""
        // 只返回最近 30 天内更新的 profile,避免过期信息污染上下文
        val cutoff = System.currentTimeMillis() - 30L * 24 * 3600 * 1000
        return if (prefs.updatedAt > cutoff) prefs.summary else ""
    }

    // 层三:历史事件检索(关键词匹配,不用向量,够用)
    suspend fun searchHistory(query: String, limit: Int = 5): List {
        return db.historyDao().search(query, limit)
    }

    // 构建注入上下文:固定结构,每次启动相同
    suspend fun buildContextBlock(): String {
        val profile = getUserProfile()
        return buildString {
            if (profile.isNotEmpty()) {
                appendLine("## 用户偏好")
                appendLine(profile)
                appendLine()
            }
            appendLine("## 当前时间")
            appendLine(SimpleDateFormat("yyyy-MM-dd HH:mm EEEE", Locale.CHINESE).format(Date()))
        }
    }
}

这个结构的关键是 buildContextBlock() 返回的内容是结构固定的------每次 Agent 启动,注入的上下文形状是一样的,模型知道从哪里找什么信息,不需要靠推断。

4.3 执行层的确定性保障

Android 端的 Agent 执行层,有几个必须处理的边界情况:

kotlin 复制代码
class AgentExecutor(
    private val llmClient: LLMClient,
    private val toolRegistry: ToolRegistry
) {
    private val MAX_TURNS = 8        // 硬限制轮数,防止无限循环
    private val TOOL_TIMEOUT_MS = 5_000L  // 工具调用超时

    suspend fun run(userInput: String, context: String): AgentResult {
        val messages = mutableListOf(
            Message("system", buildSystemPrompt(context)),
            Message("user", userInput)
        )

        repeat(MAX_TURNS) { turn ->
            val response = llmClient.complete(messages, tools = toolRegistry.getTools())

            // 没有工具调用,说明任务完成
            if (response.toolCalls.isEmpty()) {
                return AgentResult.Success(response.content, turns = turn + 1)
            }

            // 执行工具调用,有超时保护
            val toolResults = response.toolCalls.map { call ->
                try {
                    val result = withTimeout(TOOL_TIMEOUT_MS) {
                        toolRegistry.execute(call.name, call.arguments)
                    }
                    ToolResult(call.id, result, isError = false)
                } catch (e: TimeoutCancellationException) {
                    // 超时返回结构化错误,不是裸异常
                    ToolResult(call.id, "工具调用超时(${TOOL_TIMEOUT_MS}ms),请重试或换一种方式", isError = true)
                } catch (e: Exception) {
                    ToolResult(call.id, "工具执行失败:${e.message}", isError = true)
                }
            }

            messages += Message("assistant", toolCalls = response.toolCalls)
            messages += Message("tool", toolResults = toolResults)
        }

        // 达到最大轮数,保底返回
        return AgentResult.Interrupted("任务超出最大执行步数,已停止")
    }
}

这段代码里有几个关键的 Harness 细节:

MAX_TURNS = 8:硬限制轮数,永远不会无限循环,即使模型卡住了

• 工具超时返回结构化错误文本而非抛异常:模型能理解这个信息,知道要换策略

• 达到上限后保底返回而非崩溃:用户看到的是"任务超出步数已停止",不是 ANR

五、Android 上 Agent 的实际应用形态

讲完工程,讲场景。Android 上 Agent 目前最有落地价值的方向,我个人的判断是三类:

5.1 系统级 Accessibility Agent

用 AccessibilityService 让 Agent 能"看"屏幕、"点"界面。这是 Google 自己也在探索的方向(Android AI Core + AccessibilityService),本质上是给模型一个操控手机 UI 的工具集。

这个方向的 Harness 设计难点在于:UI 树是动态的,工具描述写的是语义操作("点击确认按钮"),但执行时需要实时解析 AccessibilityNodeInfo 找到对应节点。Harness 层要做的就是把"语义指令 → 节点定位 → 执行 → 结果验证"这条链路封装好,错误时给模型可操作的反馈。

5.2 端侧对话增强(RAG-lite)

不做完整 Agent 循环,只做一件事:让本地 LLM(Gemma 2B / Phi-3 mini)在回答时能访问用户的本地数据(笔记、联系人、日历)。这是 Harness 最轻量的形态------单次检索注入,不需要多轮工具调用。

Google 的 MediaPipe LLM Inference API 在 2025 年底支持了 RAG 扩展接口,可以在推理时注入额外的文本片段,而不需要把所有内容都加载进上下文窗口。这是端侧 Agent 非常值得用的基础能力。

5.3 意图路由 Agent

这个我觉得是当前最容易落地的方向:用一个轻量分类器(或小模型)理解用户的自然语言输入,然后路由到现有的功能模块。不需要模型真的"做"什么,只需要它"判断"应该做什么。

Harness 在这里的价值是定义清晰的意图类型和路由规则,让分类器的输出是枚举值而不是自由文本,执行层完全确定。论文 *"Chain of Thought Prompting Elicits Reasoning in Large Language Models"(Wei et al., 2022)*里有一个被忽视的结论:对于分类任务,给模型提供有限的选项集比让它自由生成答案,准确率高 15-20%。这正是意图路由 Harness 的设计依据。

六、一个可以现在就开始的 checklist

Harness Engineering 听起来很重,但起步不需要重构一切。如果你现在手上有一个 Android + AI 的项目,可以用这个 checklist 检查当前状态:

📌 📋 Android Agent Harness 自检清单 ☐ 工具数量是否控制在 5 个以内? ☐ 每个工具的描述是否包含"何时调用"和"限制条件"? ☐ 执行层是否有最大轮数硬限制? ☐ 工具调用是否有超时处理,且返回结构化错误而非裸异常? ☐ 危险操作(写入、删除、发送)是否有代码层的二次确认? ☐ 注入到 System Prompt 的上下文是否结构固定、不随对话漂移? ☐ 是否有任务中断后的保底 fallback,避免 ANR 或白屏?

这七条,每条都不难实现,但缺了任何一条,Agent 在真实用户手里都可能以你想不到的方式崩掉。

最后说两句

我最近和几个做 Android + AI 的朋友聊,发现大家踩的坑惊人地相似:花了很多时间调 prompt,结果发现根本问题是工具设计太随意;或者模型调用链跑通了,但 production 环境里一旦网络抖动就 crash,因为没有超时和 fallback。

这些都是 Harness 的问题,不是模型的问题。OpenClaw 给了我一个很好的参照:一个真正可用的 AI 助手,不是因为模型多聪明,而是因为围绕它的工程壳足够扎实。

Android 上的 Agent 还在早期,但 Harness Engineering 的方法论已经成熟了。这轮技术演进,最先吃到红利的,应该是那些把工程基础打好的人。

相关推荐
hnlgzb4 小时前
常见的Android Jetpack库会有哪些?这些库中又有哪些常用类的?
android·android jetpack
钛态7 小时前
Flutter 三方库 http_mock_adapter — 赋能鸿蒙应用开发的高效率网络接口 Mock 与自动化测试注入引擎(适配鸿蒙 HarmonyOS Next ohos)
android·网络协议·flutter·http·华为·中间件·harmonyos
王码码20357 小时前
Flutter for OpenHarmony:Flutter 三方库 algoliasearch 毫秒级云端搜索体验(云原生搜索引擎)
android·前端·git·flutter·搜索引擎·云原生·harmonyos
左手厨刀右手茼蒿7 小时前
Flutter for OpenHarmony: Flutter 三方库 shamsi_date 助力鸿蒙应用精准适配波斯历法(中东出海必备)
android·flutter·ui·华为·自动化·harmonyos
代码飞天7 小时前
wireshark的高级使用
android·java·wireshark
2501_915918418 小时前
苹果App Store上架审核卡住原因分析与解决方案指南
android·ios·小程序·https·uni-app·iphone·webview
skiy8 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
小小小点9 小时前
Android四大常用布局详解与实战
android
MinQ10 小时前
binder和socket区别及原理
android