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:
- Your app is built as a web bundle (a single
index.htmlwith inlined JS/CSS) - You upload the bundle to any accessible URL (Supabase Storage, CDN, S3, etc.)
- You call the
agent-app-publishAPI with the bundle URL and SHA256 hash - When a user opens your app, the host downloads the bundle, verifies its integrity, and loads it in a WebView
- The host injects the user's auth session into
localStoragebefore 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 webThen 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 Type | Description |
|---|---|
bridge:ready | App has loaded and is ready |
bridge:navigateBack | Navigate back |
bridge:navigateToApp | Navigate to another app |
bridge:showToast | Show a native toast |
bridge:setTitle | Set the navigation bar title |
bridge:haptics | Trigger haptic feedback |
bridge:openUrl | Open a URL in the system browser |
bridge:setStorageItem | Store a value (includes request id) |
bridge:getStorageItem | Retrieve a value (includes request id) |
bridge:requestPermission | Request a device permission (includes request id) |
bridge:log | Log a message to the host console |
Host → WebView messages:
| Message Type | Description |
|---|---|
host:context | Delivers the host context (user info, auth tokens) |
host:response | Response to an async request (correlated by id) |
host:event | Host-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 belowReference: Build Script
The AssistMe monorepo includes a reference build script (scripts/agent-app-build.sh) that demonstrates the bundling process:
- Runs
expo export --platform web - Finds all JS and CSS files in the output
- Inlines them into a single
index.html - 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 directlyPublishing 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.htmlSee 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.