从 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,每个工具有明确的 description、parameters 和 required 字段。这不是给开发者看的注释,这是给模型看的行为规范。

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 的方法论已经成熟了。这轮技术演进,最先吃到红利的,应该是那些把工程基础打好的人。

相关推荐
三少爷的鞋7 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)11 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码13 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程18 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程18 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓18 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程18 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer