English | 中文
An embeddable agent runtime (an agent harness) for Android and the JVM. It runs a full agent loop inside your app's process: a sandboxed workspace the agent reads and writes, tools from your code and from MCP servers, skills and subagents, hooks and policy around every tool call, and durable sessions. Bring any OpenAI- or Anthropic-compatible model.
- The agent loop, on the device. Native tool calling with streaming, thinking, prompt caching and server-side web search where the provider has them; retries, budgets and conversation compaction that summarizes old turns.
- A sandboxed workspace. A virtual file system the agent works in; mount app directories, assets, in-memory sources or a folder the user picks (SAF). Nothing outside what you mount is reachable.
- Tools. Plain Kotlin functions (
functionTools), anyToolProvider, and remote MCP servers over Streamable HTTP (McpHttpClient, built in). With many tools the agent searches for the right ones instead of seeing them all. - Skills, AGENTS.md and subagents. Skills load on demand; the agent can delegate to child agents and wait for them.
- Control. Hooks before and after tool calls, a policy engine, confirmation for network and side-effecting actions, and a per-provider concurrency limit.
- Durable sessions. An append-only event log per session, resumable runs, SQLite session storage on Android.
- Optional modules. Operate other apps' screens through accessibility (Android Use), sandboxed JavaScript that calls tools, calendar, notification and media tools, and A2A client and server.
dependencies {
implementation(platform("cc.noharry:agent-bom:0.5.0"))
implementation("cc.noharry:agent-runtime-android") // an Android app
// implementation("cc.noharry:agent-runtime") // the JVM: a server, a desktop tool, tests
}Kotlin 2.0 or later (the artifacts are built at language level 2.1), JDK 17, Android minSdk 23. The workspace and
the event log use java.nio.file, which Android has from API 26: below that, enable core library desugaring with
com.android.tools:desugar_jdk_libs_nio 2.1.5 or later (as samples/quickstart-android does).
val tools = functionTools("device") {
tool("device_info", "The phone or tablet this app runs on.", readOnly = true) {
"${Build.MANUFACTURER} ${Build.MODEL}, Android ${Build.VERSION.RELEASE}"
}
}
val runtime = AndroidAgentRuntime.builder(context) // workspace, SQLite sessions, event log, history
.model(OpenAiModelClient({ apiKey }, model = "gpt-4o-mini", stream = true))
.tools(tools)
.build()
scope.launch {
runtime.startThread().runStreamed("What device am I on?")
.collect { event -> if (event is ThreadEvent.ItemDelta) answer.append(event.text) }
}Use thread.run(text) for a complete TurnResult, or runStreamed for eight event types. Each item has a stable
id, and ItemCompleted includes its full content. An Approval item waits in the same stream until approve() or
deny(); ask finishes with WAITING_FOR_ANSWER, and the next turn supplies the answer. thread.steer(text) adds
input while a turn runs; thread.interrupt() cancels it. Configure .storage(directory) on the JVM to restore model
history and thread.items() after restarting; Android configures private storage automatically. Live tool payloads
are original; stored item snapshots use the configured trace redactor.
Keep API keys off the device in a real app: point baseUrl at your own backend.
val deepWiki = McpToolProvider(
McpHttpClient("https://mcp.deepwiki.com/mcp", headers = { emptyMap() }, http = OkHttpHttpTransport(),
clientName = "my-app", clientVersion = "1.0"),
serverId = "deepwiki",
)
val runtime = AgentRuntime.builder()
// Any OpenAI-compatible endpoint: pass its baseUrl. AnthropicModelClient speaks the Messages API.
.model(OpenAiModelClient({ System.getenv("API_KEY") }, model = "gpt-4o-mini", stream = true))
.tools(deepWiki)
.workspaceRoot(File("agent-workspace"))
.build()The samples run as they are: AGENT_API_KEY=… ./gradlew :samples:quickstart-jvm:run --args="…", and
samples/quickstart-android is a one-screen app. ./gradlew :samples:extensions:run needs no key or network: a model
of your own (a scripted one standing in for an on-device model), tools from your code, and a hook that refuses a call.
| Artifact | What it is |
|---|---|
agent-runtime |
The runtime and host facade (plain JVM): AgentRuntimeBuilder, OpenAI- and Anthropic-compatible model clients, MCP, functionTools, skills, subagents, sessions and memory, web search and fetch, hooks and policy, and the workspace's virtual file system |
agent-runtime-android |
The Android layer: AndroidAgentRuntime.builder(context), SQLite sessions, the app-private event log, and mounting a folder the user picks through the Storage Access Framework |
agent-api |
Android-neutral Thread / Turn / Item contracts and stable model, tool, hook and event-log extension interfaces |
agent-exec-js |
Optional: exec_js, sandboxed JavaScript (QuickJS through Zipline) that can call tools; the app server's scripts (plugin packages' tools written in JavaScript) need it too |
agent-device-capabilities |
Optional: calendar, notification and media tools; with the app server, also their plugin (device-tools), loaded by default |
agent-android-use |
Optional: observe and operate the screen through an accessibility service; with the app server, also its plugin (android-use), loaded by default |
agent-a2a |
Optional (plain JVM): delegate tasks to remote A2A agents, and expose the agent as a local A2A endpoint. Two clients: a direct JSON-RPC/SSE one speaking A2A 0.3, and one over the official A2A Java SDK speaking A2A 1.0, whose SDK the app adds (below) |
agent-acp |
Optional (plain JVM): serve the agent's threads over the Agent Client Protocol (ACP) to editors and other ACP clients, on stdio or any stream |
agent-bom |
One version for all of the above and the app server's three artifacts below |
An Android app depends on agent-runtime-android; a server, desktop tool or test uses agent-runtime. Optional
modules depend at most on agent-api. The loop implementation is internal to agent-runtime; samples/minimal
shows a host with only a model.
The app server (experimental) makes an Android app an agent: one runtime behind a JSON-RPC protocol of threads,
turns and items, which every front end reaches as a client, and a plugin kernel the rest comes through. Its guide for
hosts is agent-app-server/README.md; samples/agent-app-sample* are four apps on it.
| Artifact | What it is |
|---|---|
agent-app-protocol |
The protocol (plain JVM): requests, notifications and approvals over threads, turns and items, the state a front end renders, and its client (ServerClient) |
agent-app-server |
The server (Android): sessions, models, MCP, skills, subagents and usage behind the protocol, the plugin kernel (AppServerPlugin, PluginHost) and plugin packages a person installs; a host installs it with an AppServerConfig |
agent-app-models |
Optional (Android): the model services' plugin, loaded by default once on the classpath: DeepSeek (with its balance, search and relay check), OpenAI, Anthropic, Google Gemini, xAI, OpenRouter, Groq, Moonshot (Kimi), Zhipu (GLM), Alibaba Cloud Model Studio (Qwen), Volcengine Ark (Doubao), MiniMax and SiliconFlow: where each is reached, its models' facts and prices, how its failures read |
The core speaks the wire protocols (OpenAI Chat Completions and Responses, the Anthropic Messages API) and knows no
service: with it alone a profile reaches any compatible service, with conservative defaults. Three published modules
bring a plugin that the server loads once the app depends on them, unlisted: agent-app-models, and
agent-android-use and agent-device-capabilities for operating the screen and the device's tools (those two only
compile against the server, so an app with the runtime alone gets their tools without it). A host's own plugins are its
code (AppServerPlugin). The other first-party ones (cards, drawn pages, memory, pictures, plans, A2A) are source
modules of the Bothy app, built into it rather than published as Maven artifacts,
so they change without a release of this one; their source is published with Bothy's next release.
The core names no plugin's package and no model service, and carries none of the runtime modules only a plugin brings
(agent-a2a, agent-android-use, agent-device-capabilities, agent-app-models;
checkAppServerLayering, part of ciCheck).
agent-a2a is compiled against the official A2A Java SDK 1.1.0.Final without carrying it, so the direct client
(DirectA2aClientModule) needs nothing more. An app that uses OfficialA2aClientModule adds the SDK, without its
a2a-java-sdk-common (agent-a2a has its own copy, which Android accepts); on Android the SDK's jakarta jars carry the
same notice files, of which the app keeps one:
dependencies {
implementation("cc.noharry:agent-a2a")
implementation("org.a2aproject.sdk:a2a-java-sdk-client:1.1.0.Final") {
exclude(group = "org.a2aproject.sdk", module = "a2a-java-sdk-common")
}
implementation("org.a2aproject.sdk:a2a-java-sdk-http-client-android:1.1.0.Final") {
exclude(group = "org.a2aproject.sdk", module = "a2a-java-sdk-common")
}
}
android {
packaging { resources.pickFirsts += setOf("META-INF/LICENSE.md", "META-INF/NOTICE.md", "META-INF/beans.xml") }
}Without the SDK, OfficialA2aClientModule() throws an IllegalStateException naming these artifacts, and a release
shrunk by R8 that names it does not build. The SDK's R8 rules come in agent-a2a's jar. Below Android 7.0 the SDK
needs core library desugaring, as the runtime does below 8.0.
Starting with 0.5.0, the Thread / Turn / Item API and the main extension interfaces in cc.noharry.agent.spi are
stable. Advanced diagnostic and tuning APIs remain experimental. The changelog records migrations.
- Declarations marked
@ExperimentalAgentRuntimeApior@ExperimentalA2aClientApiare the likeliest to change, and ask for an opt-in (without one, the runtime's are a compile error and the A2A client's a warning). - The app server's artifacts (
agent-app-protocol,agent-app-server,agent-app-models) are experimental as a whole; their entry points (ServerClient,AppServer,AppServerConfig,AppServerPlugin) carry@ExperimentalAppServerApi, whose opt-in, without one, is a warning. - Declarations marked
@InternalAgentRuntimeApiare not API: they are there for the runtime's own modules and tests, and may change or go in any release. - The public API of every module is recorded in its
api/*.api, which CI compares with the code, so no change to it goes unnoticed.
Security reports: see SECURITY.md.
To try changes not yet released, clone this repository next to your project and let Gradle build it from source; the same coordinates resolve to it:
// settings.gradle.kts of your project
includeBuild("../android-agent-runtime")Bothy is a tablet-first demo built on this runtime: a floating voice assistant, public MCP servers, plans that watch a server's state, generative UI, and a simulated car head unit to drive.
JDK 17 and the Android SDK. ./gradlew ciCheck runs what CI runs: the source rules, the API compatibility check, the
Android API level checks and lint, every unit test and the sample builds. See CONTRIBUTING.md.
MIT. Third-party code and native libraries keep their own licenses; see THIRD_PARTY_NOTICES.md.