How agentlib runs an agent on a phone.
agentlib is pure Dart with native bridges — no Node subprocess, no
exec. Here's the full stack, from your Flutter widgets down
to on-device and cloud model providers.
┌─────────────────────────────────────────────┐ │ Your Flutter app (Dart UI, widgets) │ ├─────────────────────────────────────────────┤ │ AgentSpec → Runner (streaming event loop) │ │ events: TextDelta · ToolCalled · Finished │ ├─────────────────────────────────────────────┤ │ Hooks (21 interception points) │ │ PreToolUse · OnSuspend · OnLowBattery … │ ├──────────────────────┬──────────────────────┤ │ Vfs (/workspace, │ MCP (HTTP + WS) │ │ /bin, /memory …) │ → /bin/mcp.*.* │ │ Sh (pipes, no exec)│ │ ├──────────────────────┴──────────────────────┤ │ ModelRoute (capability-aware fallback) │ ├──────────────────────┬──────────────────────┤ │ On-device providers │ Cloud providers │ │ FoundationModels │ Anthropic │ │ Gemini Nano │ OpenAI │ │ llama.cpp (Fllama) │ Google │ │ MediaPipe │ │ └──────────────────────┴──────────────────────┘
The layers
Reading top to bottom: your Flutter app builds an
AgentSpec and hands it to a
Runner, which drives the loop and
streams events. Every tool call passes through the
hooks layer before it reaches the
Vfs /
Sh and
MCP tool surfaces. Underneath,
ModelRoute decides whether a
turn runs on-device or in the cloud — the same agent code either way.
How a tool call flows
Every tool call in agentlib passes through four stages:
- The model streams a turn. The Runner asks the active ModelProvider — on-device or cloud — for the next turn and streams TextDelta events to your UI as tokens arrive.
- A tool call is parsed. When the model emits an Sh command, agentlib parses the pipeline (grep | jq | xargs) into calls against CLIs registered under /bin in the Vfs. No real exec happens.
- Hooks intercept. PreToolUse hooks run first — surfacing a consent sheet, checking policy, or auto-snapshotting before any highImpact tool. The call proceeds only if the hooks allow it.
- The tool runs and results return. Each 'binary' is a Dart function; each pipe is an in-memory stream. PostToolUse hooks write audit lines, then the ToolResult flows back into the model's context for the next turn.
No subprocess, no exec
The Sh mini-shell parses real Unix
syntax — pipes, redirects, $VAR, $(cmd) — but never
spawns a process. Each command resolves to a registered Dart function under
/bin, and each pipe is an in-memory stream. There's no
fork(2), no exec(2), no &
backgrounding, no eval. Read the reasoning in
why mobile-native.
Lifecycle-aware by design
The Runner's state is a
serialisable RunState, and
lifecycle hooks (OnSuspend, OnResume,
OnLowBattery, OnNetworkChange) let a run survive
backgrounding and app kills — and even resume from a push notification. See
the long-running agents use case.
Next: the eight primitives in depth, the on-device provider matrix, or install in 60 seconds.