【免费下载链接】PalmClaw
[EMNLP 2026 Demo] "PalmClaw: A Native On-Device Agent Framework for Mobile Phones"
PalmClaw 是一个原生运行在 Android 手机上的开源 AI Agent 框架:会话、上下文管理、记忆、技能、工具与 Agent Loop 全部在设备端完成,模型推理则调用用户自行配置的 LLM API。本文将带你从源码视角看懂它最核心的部分——端侧 Agent Loop 的一整轮对话是如何一步步跑起来的,适合刚接触 Agent 框架、又想看真实 Kotlin 实现的新手阅读。
🧭 先建立全局视图:一个进程,一个运行时
PalmClaw 是单模块 Android 应用,技术栈是 Kotlin + Jetpack Compose + Room + Coroutines + OkHttp。官方架构文档在 docs/engineering/architecture.md,其中把整个系统划分为 8 个职责清晰的层次:
| 层次 | 职责 | 主要组件 |
|---|---|---|
| 组合根 | 构造进程级依赖 | PalmClawApplication、AppContainer |
| UI | 渲染状态、收集输入 | MainActivity、ChatScreen、ChatViewModel |
| 运行时 | 协调渠道、调度、会话与 Agent Turn | GatewayRuntime、GatewayRuntimeSupervisor |
| Agent | 构建上下文并驱动模型/工具循环 | ContextBuilder、AgentLoop |
| 模型 | 统一各家模型协议 | LlmProvider及各 Provider 适配器 |
| 工具 | 校验 Schema、执行能力、返回结构化结果 | ToolRegistry与各类内置工具 |
| 存储 | 会话、消息、记忆、工作区持久化 | Room 仓库与文件存储 |
| 渠道 | 接入 Telegram、飞书、企业微信等外部消息系统 | 各 ChannelAdapter |
下面这张官方架构图把关系讲得很直白:左侧的 Channels(本地聊天、Telegram、Discord、飞书、企业微信)把消息送进中间的Agent Loop,Loop 内部是 LLM → 权限检查 → 工具执行 → 回复输出的循环;右侧的 Context(Memory、Skills)随时为 LLM 和工具提供上下文,而这一切都发生在手机沙箱之内。
一个重要的运行时约束:整个进程只有一个GatewayRuntime。前台使用、Always-on 模式和定时自动化都通过GatewayRuntimeSupervisor获取同一个运行时,只有最后一次释放才会真正停掉它(见 app/src/main/java/com/palmclaw/runtime/GatewayRuntime.kt)。这保证了记忆、工具注册表和渠道状态全局一致,不会出现"两份运行时打架"。
🔄 Agent Turn 全生命周期:一条消息的 6 站之旅
Agent Loop 的入口是AgentLoop.run(),位于 app/src/main/java/com/palmclaw/agent/AgentLoop.kt。它做的事情可以概括为:持久化输入消息 → 选择技能 → 构建上下文 → 调用模型 → 解析并执行工具调用 → 循环直至完成。下面按执行顺序逐站拆解。
第 1 站:落库与"预热"上下文
用户消息不是直接丢给模型的,而是先通过MessageRepository写入 Room 数据库。紧接着,Loop 会取出最近 4 条用户消息拼成recentUserContext——它用于技能选择,让 Agent 结合"最近用户在聊什么"来决定本轮激活哪些技能。
第 2 站:按需激活 Skills
技能由 app/src/main/java/com/palmclaw/skills/SkillsLoader.kt 加载,来源有两类:内置技能(打包在 app/src/main/assets/skills/ 中,如summarize、memory、cron)和工作区中用户安装的技能。每个技能的核心是一个SKILL.md文件,例如 app/src/main/assets/skills/summarize/SKILL.md。
激活规则是"always 技能 + 按输入匹配到的技能"取并集。这样设计的妙处在于:上下文不是全量塞入,只有与当前任务相关的技能正文才会进入系统提示词,从而控制 token 消耗。
第 3 站:ContextBuilder 组装系统提示词
app/src/main/java/com/palmclaw/agent/ContextBuilder.kt 负责把数据库里的历史消息还原成模型能理解的ChatMessage列表。它处理的不是简单拼接,而是有状态机的细节:
- 工具链完整性:assistant 消息若携带
tool_calls,其对应的tool结果消息必须紧随其后成对出现,孤立的 tool 消息会被丢弃(orphan检查),保证发给模型的上下文符合 OpenAI 风格的 tool 协议; - 噪音过滤:以
[Error]、[Info]开头的 assistant 消息不会进入上下文; - 动态系统提示词:包含真实的当前时间与时区、会话 ID、当前会话工作区路径(
session://与shared://路径语义),以及长期记忆、激活技能、技能摘要等分节内容。
系统提示词的策略模板来自 app/src/main/assets/templates/ 下的AGENT.md、SOUL.md、USER.md、TOOLS.md,用户可以自定义——这相当于给 Agent 写"性格设定"。
第 4 站:调用 LLM 并解析工具调用
模型调用被收敛在一个极小的接口后面:LlmProvider.chat(messages, toolsSpec)(见 app/src/main/java/com/palmclaw/providers/LlmProvider.kt)。无论底层是 OpenAI 兼容协议还是 Anthropic 兼容协议,Agent Loop 只面对这一个签名,这就是"Provider 归一化"的价值。
响应回来后的解析在 app/src/main/java/com/palmclaw/agent/ToolCallParser.kt:优先读取结构化的toolCalls字段;如果模型把工具调用以 JSON 文本形式写在正文里(还带着 ```json 代码块围栏),它会清洗后兜底解析。这种双通道设计让框架对不同模型的"听话程度"更宽容。
第 5 站:ToolRegistry 校验与执行
工具调用不会裸奔。ToolRegistry(app/src/main/java/com/palmclaw/tools/ToolRegistry.kt)在每次执行前做 Schema 校验(由ToolArgumentsValidator完成),并施加统一的超时控制。被用户禁用的工具会直接返回"disabled in this execution context"错误,让模型自己感知并调整策略。
执行结果有两道"限流阀":
- 结果正文超过上限会被截断并追加
...[truncated]; - 若工具元数据中带有"终止 Agent Loop"标记(如消息类工具已成功投递),本轮工具执行完后 Loop 立即结束。
第 6 站:循环与边界条件
Loop 默认最多跑20 轮(可在 1–100 间调整),工具结果默认截断到5000 字符(可配 100–20000),这些安全边界统一集中在 app/src/main/java/com/palmclaw/config/AppRuntimeConfig.kt 的AppLimits对象里。
四种正常/异常出口:
- 模型返回纯文本、无工具调用 → 自然结束;
- 达到最大轮数 → 追加一条"Stopped after reaching max rounds"提示;
- 用户取消 →
CancellationException向上传播,会话可继续; - 任意异常 → 捕获后写入一条 Error 消息,而不是让进程崩溃。
⚖️ 并发模型:会话内串行,跨会话受限并行
一个容易被忽略的细节是 app/src/main/java/com/palmclaw/runtime/SessionTurnCoordinator.kt:
- 同一会话内的 Agent Turn 用
Mutex串行——避免两条消息并发读写同一会话历史造成上下文错乱; - 不同会话之间用
Semaphore限流,默认最多2 个Turn 并发——防止多个长任务同时跑满手机的网络与内存。
这正是"单运行时"架构下多会话体验流畅的关键:你在 Telegram 渠道跑的长任务,不会卡死本地聊天的回复。
🧠 后台记忆整合:对话结束后才发生的事
finally块里有一个优雅的收尾动作:scheduleBackgroundConsolidation(sessionId)。当某会话未整合的消息数超过记忆窗口(默认 50 条)时,app/src/main/java/com/palmclaw/agent/MemoryConsolidator.kt 会在后台协程里把"旧消息区间"交给模型提炼成长期记忆,并推进"最后整合下标"。
这样设计的好处:对话响应路径上零额外开销,而模型在下一轮组装上下文时,长期记忆已经作为系统提示词的一部分注入——Agent 越用越"懂你",但不会无限膨胀上下文。
📂 延伸阅读:模块路径速查
| 主题 | 路径 |
|---|---|
| 架构总览文档 | docs/engineering/architecture.md |
| Agent Loop 主循环 | app/src/main/java/com/palmclaw/agent/AgentLoop.kt |
| 上下文组装 | app/src/main/java/com/palmclaw/agent/ContextBuilder.kt |
| 工具调用解析 | app/src/main/java/com/palmclaw/agent/ToolCallParser.kt |
| 工具注册与校验 | app/src/main/java/com/palmclaw/tools/ToolRegistry.kt |
| 会话 Turn 并发控制 | app/src/main/java/com/palmclaw/runtime/SessionTurnCoordinator.kt |
| 运行时安全边界常量 | app/src/main/java/com/palmclaw/config/AppRuntimeConfig.kt |
| 内置技能示例 | app/src/main/assets/skills/ |
🎯 小结
PalmClaw 的端侧 Agent Loop 给新手示范了一套工程化的 Agent 设计范式:单一运行时保证全局一致,LlmProvider接口屏蔽模型差异,Skills 按需激活控制上下文体积,ToolRegistry 用 Schema + 超时 + 截断给工具执行加护栏,会话级 Mutex + 全局 Semaphore 解决并发,后台记忆整合让"长期记忆"不阻塞交互路径。如果你想在自己的项目里实现一个移动端 Agent,这套"小而全"的 Kotlin 实现几乎可以直接当参考蓝图。
【免费下载链接】PalmClaw
[EMNLP 2026 Demo] "PalmClaw: A Native On-Device Agent Framework for Mobile Phones"
相关推荐
android-wifi-pojie源码剖析:如何用Kotlin + Jetpack Compose构建WiFi密码破解工具
android wifi pojie源码剖析:如何用Kotlin + Jetpack Compose构建WiFi密码破解工具 本文带你 完整剖析 android
网络安全渗透测试Mural 双端架构深度解析:SwiftUI 与 Jetpack Compose 如何共用一套学习内核
Mural 双端架构深度解析:SwiftUI 与 Jetpack Compose 如何共用一套学习内核 Mural 是一款通过真实对话练习外语的原生语言学习应用
lenny-skills是如何工作的:深度剖析SKILL.md文件结构与guest-insights.md,看懂AI如何调用产品管理框架
lenny skills是如何工作的:深度剖析SKILL.md文件结构与guest insights.md,看懂AI如何调用产品管理框架 lenny skill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考