architecture · how it works

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 agentlib stack — top to bottom, UI to model providers.

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
ModelProvider stream tokens PreToolUse hook Tool dispatch VFS · Sh · MCP PostToolUse hook ToolResult → feed back Snapshot if highImpact
Agent loop. Hooks intercept every tool call. Snapshots before destructive ops.

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.

build it

Wire this into your app.

agentlib is open source, MIT, and on pub.dev. One pubspec line to get the whole stack.