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.
0.5.0 is not published yet. This branch uses the new Thread / Turn / Item API. Build from source or run
./gradlew publishToMavenLocaland addmavenLocal()to your repositories. Maven Central still has 0.4.0; its API differs from these examples. See the migration guide.
- 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-SNAPSHOT"))
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 |
agent-device-capabilities |
Optional: calendar, notification and media tools |
agent-android-use |
Optional: observe and operate the screen through an accessibility service |
agent-a2a |
Optional (plain JVM): delegate tasks to remote A2A agents over a direct JSON-RPC/SSE transport, and expose the agent as a local A2A endpoint |
agent-a2a-client-official |
Optional: the same client over the official A2A Java SDK |
agent-bom |
One version for all of the above |
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.
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). - 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.