for · mobile teams

Agents that survive the OS, the reviewer, and the user.

Suspend & resume, push-resume via APNs/FCM, consent-sheet hooks, audit-log hooks, on-device first for privacy. Built for the team that actually has to ship the app.

Survive backgrounding

Mobile apps get backgrounded all the time. The OS may kill the process. agentlib's RunState is serialisable: when OnSuspend fires, your app can persist the state to disk in a few milliseconds. On OnResume (or app cold start), call Runner.resume(state) and the agent picks up where it left off — same messages, same tools, same context window.

suspend_resume.dart
hooks.register(OnSuspend((event, ctx) async {
  final state = ctx.serializeState();
  await persistedStore.write('agent.state', state);
}));

// On cold start:
final state = await persistedStore.read('agent.state');
if (state != null) Runner.resume(state, config: ...);

Push-resume via APNs / FCM

Long-running work doesn't need to live in the app. Your backend can finish a job and ping the device to "pick this up". agentlib parses standard PushPayload and resumes from a checkpoint.

push_resume.dart
// Backend pushes:
//   { "agentlib.resume": "" }
//
// In the app's notification handler:
final payload = PushPayload.tryParse(notification.userInfo);
if (payload != null) {
  await Runner.resumeFromPush(payload, config: ...);
}

Privacy: on-device first

For privacy-sensitive workloads (health, finance, legal, kids), ModelRoute.preferOnDevice([...]) keeps data on the device by default. Cloud only as a fallback you control, and you can refuse to fall back if the data is classified.

privacy_routing.dart
final agent = AgentSpec(
  name: 'medical-helper',
  instructions: 'You are an on-device medical assistant. No PHI leaves the device.',
  model: ModelRoute.onDeviceOnly(
    [FoundationModelsProvider(), GeminiNanoProvider(), FllamaProvider(...)],
  ), // Throws if no on-device provider can serve the request — never falls back to cloud.
);

Consent sheets via PermissionRequestHook

Any tool with a capabilities declaration (microphone, camera, contacts, accessibility) triggers a PermissionRequestHook before the tool dispatches. Your app shows a native iOS/Android consent sheet; the agent waits for the result.

consent.dart
hooks.register(PermissionRequestHook((req, ctx) async {
  final granted = await showCupertinoModalPopup(
    context: ctx.uiCtx!,
    builder: (_) => ConsentSheet(capability: req.capability, reason: req.reason),
  );
  return granted == true ? HookOutcome.carryOn() : HookOutcome.deny('user denied');
}));

Audit logs for compliance

Every tool call goes through PreToolUse + PostToolUse. Log to your existing analytics pipeline; agentlib doesn't dictate where logs go.

audit.dart
hooks.register(PostToolUse((call, result, ctx) async {
  await audit.log(
    runId: ctx.threadId,
    tool: call.tool,
    args: call.args,
    ok: result is ToolResultOk,
    at: DateTime.now(),
  );
  return HookOutcome.carryOn();
}));

What this gives the team

  • iOS reviewers: no Node binary in your bundle, no exec, sandbox-safe by design.
  • Privacy: defaults to on-device, easy to enforce no-fallback for sensitive workloads.
  • Auditing: 21 hooks make every tool call observable without prompt hacking.
  • Reliability: serialisable state means a crash mid-agent isn't user-visible.
next

The dev path.

Wire suspend/resume, push-resume, and consent hooks in one afternoon. Production checklist on GitHub.