Developer Guide

Developing an Agent App

How to develop an Agent App — you can build anywhere, using any tech stack that outputs a web bundle.

Overview

You can develop an Agent App in any repository, on any machine, with any project structure. There is no requirement to work within the AssistMe monorepo. As long as your app produces a valid web bundle (a self-contained index.html) and you submit it through the publish API, it will run inside the host app.

How Dynamic Loading Works

The host app renders your Agent App in a WebView. The process is:

  1. Your app is built as a web bundle (a single index.html with inlined JS/CSS)
  2. You upload the bundle to any accessible URL (Supabase Storage, CDN, S3, etc.)
  3. You call the agent-app-publish API with the bundle URL and SHA256 hash
  4. When a user opens your app, the host downloads the bundle, verifies its integrity, and loads it in a WebView
  5. The host injects the user's auth session into localStorage before your JavaScript loads — so your Supabase client finds the user already authenticated

This means your app code requires zero changes to work inside the host. No special SDK import needed, no host-specific initialization.

Quick Start

The simplest way to build an Agent App is with Expo (React Native for Web):

# Create a new project anywhere
npx create-expo-app my-agent-app
cd my-agent-app

# Develop your app
npx expo start

# Build as web bundle
npx expo export --platform web

Then package the output into a single index.html, upload it, and call the publish API. That's it.

You're not limited to Expo — any framework that produces a web app will work (React, Vue, Svelte, plain HTML/JS, etc.). The only requirement is a self-contained index.html that can run in a WebView.

Host Context & Bridge API

When your app runs inside the host, it can communicate with the host via a Bridge injected into the WebView. The host injects a global window.AssistMeBridge object that provides:

  • Navigation (back, open another app)
  • Native toast / alert
  • Permission requests
  • Haptic feedback
  • Sandboxed key-value storage (namespaced per app as agentapp_{slug}_{key})
  • Host context (user ID, auth tokens, color scheme)

The auth session is automatically available — the host writes Supabase auth tokens into localStorage (using the key format sb-{project_ref}-auth-token) before your app's JavaScript loads, so createClient() from @supabase/supabase-js will find the user already authenticated.

WebView Bridge Protocol

Communication between the WebView and host uses postMessage. Each message has a type field:

WebView → Host messages:

Message TypeDescription
bridge:readyApp has loaded and is ready
bridge:navigateBackNavigate back
bridge:navigateToAppNavigate to another app
bridge:showToastShow a native toast
bridge:setTitleSet the navigation bar title
bridge:hapticsTrigger haptic feedback
bridge:openUrlOpen a URL in the system browser
bridge:setStorageItemStore a value (includes request id)
bridge:getStorageItemRetrieve a value (includes request id)
bridge:requestPermissionRequest a device permission (includes request id)
bridge:logLog a message to the host console

Host → WebView messages:

Message TypeDescription
host:contextDelivers the host context (user info, auth tokens)
host:responseResponse to an async request (correlated by id)
host:eventHost-initiated events

Async requests (storage, permissions) include an id field. The host responds with a host:response message containing the same id. Storage requests time out after 10 seconds; permission requests time out after 60 seconds (to allow the user time to respond to the native dialog).

Security

The WebView blocks HTTP/HTTPS navigation — any link clicks open in the system browser instead. Only file:// URLs are allowed within the WebView. This prevents the app from navigating away from its bundle.

Building the Bundle

Your app needs to be packaged as a single self-contained index.html file with all JS and CSS inlined (no external network requests needed for initial load).

With Expo

# Export as web build
npx expo export --platform web --output-dir dist

# Package into single index.html (inline all JS/CSS)
# You can use any bundling tool, or the reference script below

Reference: Build Script

The AssistMe monorepo includes a reference build script (scripts/agent-app-build.sh) that demonstrates the bundling process:

  1. Runs expo export --platform web
  2. Finds all JS and CSS files in the output
  3. Inlines them into a single index.html
  4. Reports the SHA256 hash and file size

You can adapt this approach for your own CI/CD pipeline, or use any tooling that produces a self-contained HTML file.

With Other Frameworks

Any framework that outputs a web app works:

# React (Create React App)
npx react-scripts build
# Then inline the JS/CSS into index.html

# Vite (React, Vue, Svelte, etc.)
npx vite build
# Then inline the JS/CSS into index.html

# Plain HTML/JS
# Just write your index.html directly

Publishing Your Bundle

After building, publish by calling the API:

# 1. Upload bundle to any accessible URL
#    (Supabase Storage, S3, CDN, GitHub Releases, etc.)

# 2. Call the publish endpoint
curl -X POST "${SUPABASE_URL}/functions/v1/agent-app-publish" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "my-app",
    "version": "1.0.0",
    "changelog": "Initial release",
    "bundle_url": "https://your-cdn.com/my-app/1.0.0/index.html",
    "bundle_hash": "<sha256-hash-of-index.html>",
    "bundle_size": 524288
  }'

The bundle_hash is the SHA256 hash of your index.html file. The host verifies this hash after downloading to ensure bundle integrity.

# Compute SHA256 hash
sha256sum dist/index.html

See Submitting for Review for full details.

Sandboxed Storage

Each Agent App has isolated key-value storage, namespaced as agentapp_{slug}_{key}. Data is stored securely on the device:

// Save data
window.AssistMeBridge.setStorageItem("user-preference", "dark");

// Load data
const pref = await window.AssistMeBridge.getStorageItem("user-preference");

Storage is per-app — one app cannot read another app's data.

Permissions

If your app needs device capabilities, request them at runtime via the Bridge:

const granted = await window.AssistMeBridge.requestPermission("camera");
if (granted) {
  // Access camera
}

Available permissions: camera, location, notifications, contacts, storage, microphone, haptics, biometrics.

The host shows a native permission dialog. The user has 60 seconds to respond — after which the request is automatically denied.


Built-in Apps (Static Registration)

This section only applies to the AssistMe core team developing built-in apps within the monorepo. External developers should use the dynamic loading approach described above.

Built-in apps live in agent-apps/native-{slug}/ directories in the monorepo and are statically registered — their components are bundled directly into the host app binary at build time.

Registration

Register built-in apps in mobile-app/src/agent-apps/registry.ts:

import { registerMicroApp } from "@assistme/agent-app-sdk";
import MyAppComponent from "../../native-my-app/src/App";

registerMicroApp({
  slug: "my-app",
  name: "My App",
  component: MyAppComponent,
  permissions: ["notifications"],
  onMount: () => console.log("App mounted"),
  onUnmount: () => console.log("App unmounted"),
});

SDK Types (for static apps)

See the full MicroAppProps interface in the SDK Reference.

import type {
  MicroAppProps,
  MicroAppBridge,
  MicroAppHostContext,
} from "@assistme/agent-app-sdk";

Using the Bridge (static apps)

Static apps access the bridge via React props or the hook:

// Via props
function MyApp({ bridge, hostContext }: MicroAppProps) {
  bridge.showToast("Hello!");
}

// Via hook
import { useMicroApp } from "@assistme/agent-app-sdk";

function MyComponent() {
  const { bridge, hostContext } = useMicroApp();
  bridge.setTitle("My Screen");
}

See the full Bridge API Reference for all available methods.