Architecture
How Extentos fits together — AI agent, MCP server, native Kotlin/Swift SDK, four transports (system audio, Meta DAT, browser sim, local in-memory sim), and the backend.
Extentos is a five-component system: an AI agent (Claude Code, Cursor, Windsurf, Cline) drives the MCP server (@extentos/mcp-server, a deterministic tool surface for discovery + scaffolding + simulation), the MCP server scaffolds Extentos into the developer's app, the developer (or their agent) writes handler classes that subscribe to capability primitives on the Extentos native library (Android com.extentos:glasses on Maven Central or the iOS Swift package from github.com/extentos/swift-glasses — products GlassesCore, GlassesUI, GlassesDebug, GlassesLifecycle, GlassesTesting), the library exposes one stable glasses.* API over four swappable transport implementations (the vendorless system-audio baseline, real Meta DAT, the browser simulator at extentos.com/s, or Extentos's own local in-memory simulator), and the Extentos backend brokers simulator sessions over WebSocket. The same handler code runs identically against all four transports — simulator and production — because the library translates capability calls into transport-specific operations underneath. This page is the system diagram, the data flow, and the design rationale.
The three-layer model
Extentos models app development in three conceptual layers, top-down:
| Layer | What lives here | Owned by |
|---|---|---|
| 1. Customer code | Handler classes that subscribe to SDK primitives and run business logic — wake-phrase matching, LLM calls, photo persistence, UI updates | Agent + developer |
| 2. Capability vocabulary | The vendor-agnostic SDK surface — audio / camera / hardware events / toggles / connection state. Identical across vendors. | Extentos library (defines the contract) |
| 3. Runtime targets | Where capability calls actually execute — any Bluetooth audio device, real Ray-Ban Meta hardware, browser simulator, on-device local simulator | The library's four transports |
This model is intentional: one capability vocabulary drives everything below it. There's no "browser version" and "production version" of your handler — there's one class, and the library dispatches each capability call against whichever transport is active. Edit your handler in Kotlin/Swift, rebuild + reinstall, and the same code runs in all four runtimes; the simulator picks up the new binary automatically thanks to auto-bind.
Component diagram
┌─────────────────────────────────────┐
│ AI agent │
│ (Claude Code, Cursor, Windsurf, │
│ Cline, or any MCP-compatible host)│
└────────────────┬────────────────────┘
│ MCP stdio
▼
┌──────────────────────────────────────────┐
│ @extentos/mcp-server (npm) │
│ │
│ Deterministic tools: │
│ • discovery: getPlatformInfo, get- │
│ CapabilityGuide, getCodeExample, │
│ searchDocs │
│ • scaffold: generateConnectionModule │
│ • validation: inspectIntegration, │
│ validateIntegration │
│ • simulation: createSimulatorSession, │
│ getSimulatorStatus, getEventLog, │
│ completeAuthLink │
│ • production: getProductionChecklist, │
│ getCredentialGuide, │
│ getVoiceCommandGuidance, │
│ getPermissions │
└─────┬─────────────────────┬──────────────┘
│ │
│ writes │ HTTPS
│ code │
▼ ▼
┌─────────────────────┐ ┌──────────────────────────┐
│ Dev's app code │ │ Extentos backend │
│ (Android / iOS) │ │ api.extentos.com │
│ │ │ │
│ - Handler classes │ │ - Session store │
│ - Connection page │ │ - WebSocket hub │
│ - Bootstrap (gen) │ │ - Device-code auth │
│ - Extentos library │ │ - Event log retention │
└──────────┬──────────┘ └────────┬─────────────────┘
│ │
│ links library │ WSS sessions
│ (4 transports) │
▼ ▼
┌──────────────────────┐ ┌────────────────────────┐
│ GlassesTransport │ │ Browser surrogate │
│ (internal interface)│ │ extentos.com/s/{id} │
│ ┌────────────────┐ │ │ │
│ │ RealMeta │──┼──┼──► (none) │
│ │ wraps DAT SDK │ │ │ │
│ ├────────────────┤ │ │ - real webcam frames │
│ │ BrowserSim │──┼──┼──► WSS to backend ─────┼─┐
│ │ WebSocket │ │ │ - real mic audio │ │
│ ├────────────────┤ │ │ - hardware injection │ │
│ │ LocalSim │──┼──► (none, no network) │ │
│ │ in-memory sim │ │ │ - replay │ │
│ └────────────────┘ │ └────────────────────────┘ │
└──────────┬───────────┘ │
│ │
│ Bluetooth (BLE / HFP / A2DP) │
▼ │
┌──────────────────────┐ │
│ Real Ray-Ban Meta │ │
│ (production) │ │
└──────────────────────┘ │
│
┌───────────────────────────────────────────┘
│
▼
same Extentos backend ─► getEventLog reads from hereThe agent talks to the MCP server. The MCP server scaffolds Extentos into the developer's app. The developer (or their agent) writes handler classes against the library. The library routes the same glasses.* API through one of four transports based on config. The browser simulator and the library both connect to the backend over WebSocket for the same sessionId, putting them in the same room.
What runs where
| Component | Where it runs | Persists across |
|---|---|---|
| AI agent | Developer's editor / terminal | Conversation only |
@extentos/mcp-server | Developer's machine, spawned as MCP subprocess | Per agent session, fresh each launch |
| Handler classes + manifest | Developer's repository (committed) | Forever (source code) |
| Extentos library | Linked into developer's Android / iOS app | App's lifetime |
SystemAudioTransport (OS Bluetooth audio; no vendor SDK) | Developer's app process | App's lifetime |
RealMetaTransport (Meta DAT calls) | Developer's app process | App's lifetime |
BrowserSimTransport (WebSocket client) | Developer's app process | App's lifetime |
LocalSimTransport (in-memory) | Developer's app process | App's lifetime |
| Browser simulator UI | Developer's browser tab at extentos.com/s/{id} | Tab lifetime |
| Extentos backend (WebSocket hub, sessions, auth) | api.extentos.com | Long-lived; saved sims persist until archived |
| Event log on-device | 512-entry ring buffer in the library | App's lifetime, no disk persistence |
| Event log on backend | Per-session storage | 48 hours after each event |
| Install ID + auth token | ~/.extentos/install.json, ~/.extentos/auth.json | Forever (per machine) |
| Real Ray-Ban Meta hardware | Meta's BLE protocol stack on the glasses | Hardware lifetime |
A typical dev loop, end to end
This is the data flow agents and developers care about — what happens when the agent says "add a voice-driven photo capture to my app":
1. Developer: "Add a wake phrase 'describe this' that takes a photo and reads what it sees."
2. Agent: Calls getPlatformInfo({ glasses: "meta_rayban" })
→ MCP returns capability catalog (audio, camera, ...)
3. Agent: Calls getCodeExample({ pattern: "photo_describe_voice" })
→ MCP returns the full Kotlin + Swift composition
4. Agent: Calls getCapabilityGuide for any primitives it needs to look up
(capture_photo, transcription_incremental, speak)
5. Agent: Calls generateConnectionModule({ platform: "android", ... })
→ MCP writes ExtentosBootstrap.kt, manifest, Gradle wiring
6. Agent: Writes CoachHandler.kt (or PhotoDescribeHandler.kt) — subscribes
to glasses.audio.transcriptions(), matches "describe this",
calls glasses.camera.capturePhoto(), forwards to vision LLM,
speaks the response via glasses.audio.speak(). Updates
extentos.manifest.json's `capabilities` array.
7. Agent: Calls validateIntegration()
→ MCP checks dependency declared, bootstrap calls
ExtentosGlasses.create(), permissions cover capabilities
→ Returns ✓ all good
8. Agent: Calls createSimulatorSession()
→ MCP HTTPs the Extentos backend, gets sessionId
→ Backend opens WSS endpoint
→ MCP auto-opens browser to extentos.com/s/{sessionId}
9. Library: On app start, reads BuildConfig.EXTENTOS_SESSION_URL (Android) or
extentos.session.plist (iOS), or auto-binds via MCP probe;
selects BrowserSimTransport, opens WSS to api.extentos.com/
ws/{sessionId} with role: "app". CoachHandler subscribes to
glasses.audio.transcriptions().
10. Browser: Opens extentos.com/s/{sessionId}, joins same session as role:
"browser". Backend now has both halves in the same room.
11. Developer: Speaks "describe this" into laptop mic
Browser → transcript "describe this" relayed over WSS
Library emits Final transcript on the transcriptions Flow
CoachHandler matches the string, calls glasses.camera
.capturePhoto() and glasses.audio.speak("Let me see…")
BrowserSimTransport sends capture_photo, gets back photo bytes
Handler forwards the photo to the customer's vision LLM
Handler calls glasses.audio.speak(answer) — TTS plays in browser
12. Agent: Calls getEventLog({ filter: "voice" }) or filter:"errors" if
something didn't work as expected
→ MCP queries backend, returns the structured trace
→ Agent confirms the flow worked end to endEvery capability call in step 11 emits structured events — audio.transcriptions_subscribed, audio.record_discrete_started, camera.capture_photo_started, camera.capture_photo_completed, speak.started, speak.completed, transport.frame.relayed — into the library's ring buffer, forwarded to the backend over the WebSocket session, queryable by the agent via getEventLog. The same flow on real glasses (step 11 with RealMetaTransport) produces the same event shapes — only the transport changes.
The four transports
The library exposes one internal interface (GlassesTransport) and ships four implementations. Selection happens once at ExtentosGlasses.create() time based on config; the developer's code never branches on which transport is active.
SystemAudioTransport— the vendorless baseline. Microphone and speaker over the phone's own Bluetooth audio routing, with no vendor SDK in the path; serves the complete agent runtime on any hands-free audio route (smart glasses, ordinary earbuds, or the phone itself). No camera, no display — those are what a vendor transport adds.RealMetaTransport— wraps the real Meta DAT SDK (mwdat-core+mwdat-camera+mwdat-displayon Android,MWDATCore+MWDATCameraon iOS). Production path. BLE link to real Ray-Ban Meta hardware.BrowserSimTransport— Extentos-original. WebSocket toapi.extentos.com, browser surrogate atextentos.com/sdrives real webcam, microphone, and TTS playback. The headline dev-loop simulator.LocalSimTransport— Extentos's own in-memory deterministic transport (no DAT SDK, no network). On-device simulation for unit tests, the fast inner loop, and CI.
The same glasses.camera.capturePhoto(), glasses.audio.recordDiscrete(), glasses.audio.speak("hello") calls work identically against all four. See transport vs app simulation for the deep dive on why this layering matters and what each transport simulates.
The capability vocabulary — the universal contract
Every Extentos app composes handler code against the same capability primitives — see concepts/capabilities for the full vocabulary. The contract:
| Surface | Examples |
|---|---|
| Audio | glasses.audio.transcriptions(config) (continuous Partial + Final), glasses.audio.recordDiscrete(config) (silence-VAD bounded clip + auto-STT), glasses.audio.speak(text), glasses.audio.cancelSpeak(), glasses.audio.audioChunks(config), glasses.audio.earcon(sound) |
| Camera | glasses.camera.capturePhoto(config), glasses.camera.captureVideo(config), glasses.camera.videoFrames(config) (continuous stream) |
| Toggles | glasses.toggles.state (observable), glasses.toggles.update { … } |
| Connection | glasses.connection.state (observable), glasses.connection.connect(), glasses.connection.disconnect() |
| Voice | glasses.voice.onPhrase(phrase) { … } — wake-phrase sugar over transcriptions(); also the wake trigger for the assistant |
| Runtime events | glasses.runtime.events (Flow / AsyncStream of RuntimeEvent: toggle changes, coexistence warnings, assistant lifecycle, logs). Hardware/sensor events (thermal, hinges, …) aren't delivered here yet — only connection.state surfaces them today, as Disconnected causes |
| Display | glasses.display.show { … }, glasses.display.isAvailable — Ray-Ban Display, gated per device (display) |
| Assistant | glasses.assistant.start(provider) { tool(…) } — Phase-4 voice AI via the managed gateway (assistant) |
Vendor-agnostic by design — the API doesn't reference Meta DAT, BLE, or any specific hardware. Your handler says for await t in glasses.audio.transcriptions() { … }. The Android XR and Brilliant Labs transports proved it: a second vendor with a completely different app model runs the same handler, and only the transport translation changes.
extentos.manifest.json's top-level capabilities array (the list of SDK features your handler uses) drives getPermissions and getProductionChecklist. Each declared capability has a known set of platform-permission requirements; the toolchain writes the right Android manifest entries and iOS Info.plist keys for you.
State and persistence — what lives where
Extentos is intentionally lean about what persists:
- The customer's handler code lives in the developer's repo. Source-controlled, durable, the source of truth for behavior.
extentos.manifest.jsonlives in the developer's repo. Records library version, declared capabilities, permissions, and per-platform build metadata.- The on-device event log is a 512-entry ring buffer in memory. ~256 KB. FIFO eviction. Does not persist across app restarts — it's debugging data, not telemetry.
- The backend session log is forwarded copies of the on-device events, stored per
sessionIdfor 48 hours after each event is recorded. After that it's gone. This is whatgetEventLogqueries. ~/.extentos/install.jsonholds a per-machine install ID (inst_…). Persists forever. Identifies the install when the browser-simulator gate triggers the device-code flow (account-required for simulator minting, thegenerateConnectionModulescaffold step, and the account-scoped project tools).~/.extentos/auth.jsonis the auth token after a free-account device-code flow. Persists forever untilextentos-mcp logout.- No end-user data is persisted by Extentos in production.
RealMetaTransportdoesn't touch the backend. Your shipped app's runtime emits zero traffic to Extentos servers.
This matters for the privacy and compliance story: the only thing Extentos's backend ever sees is dev-time simulator session activity. End-user runtime activity on real glasses never leaves the developer's app.
The auto-bind dev loop
When the developer's app is built with config.debug = true and the Extentos library is linked, the library opens a persistent WebSocket to the backend's /ws/pending endpoint and probes the host machine's local bridge at 127.0.0.1:31337/whoami (or 10.0.2.2:31337 from the Android emulator, or localhost on iOS Simulator). The MCP server, when running, listens on the same port.
Result: every time the agent calls createSimulatorSession, the backend pushes a session_attached message over the pending socket, and the running app instantly switches to the new session — no rebuild, no URL paste, no developer typing. The library logs which transport it picked and why; the simulator UI opens automatically in the browser.
If the bridge can't be reached (cellular phone on a different network, headless CI, cloud-hosted agent, port 31337 already in use), the developer uses the URL-bake path: paste the BuildConfig.EXTENTOS_SESSION_URL snippet (Android) or write the extentos.session.plist payload (iOS) that createSimulatorSession returns, then rebuild the app once. The happy-path experience (auto-bind on the same machine) is "agent asks for a session, app and browser are both already connected"; the URL-bake path adds one rebuild step but works on any topology.
Why this architecture
A few design choices that recur across the components:
- One handler, many runtimes. The same handler code runs against the browser simulator, the on-device local simulator, and real Ray-Ban Meta hardware. No "dev version" vs "prod version" — there's one class, and the library dispatches each capability call against whichever transport is active.
- Same
glasses.*API in simulation and production. Developer code is portable acrossSystemAudio,RealMeta,BrowserSim, andLocalSim. Failures in simulation are real failures (validateIntegrationerrors, handler errors, hardware-ready gating) — not transport artifacts. Because the simulator runs the same library code as production, it's designed to behave identically; the final-mile fidelity (camera quality, BT latency, real-world A2DP/HFP coexistence) is being validated on real hardware now — treat a clean simulator run as strong evidence, not a hardware guarantee. - Vendor-agnostic SDK. Your handler code doesn't reference Meta or DAT. Each vendor gets its own transport implementation inside Extentos, and existing apps reach it with a config change rather than a rewrite — as Android XR did in 2026-07.
- Agent-native. The MCP server's deterministic tools (the full catalog) are designed for an AI agent to compose. There's no planning tool — agents are better planners than regex; the tools are deterministic primitives the agent calls in sequence (
generateConnectionModule→ write handler classes →validateIntegration→createSimulatorSession). - Honest simulator.
BrowserSimTransportdoesn't mock Bluetooth — it uses a different transport (WebSocket) through the same interface. Hardware-ready gating, permission denials, coexistence warnings, and session-expired states all surface as real events. The simulator is not a fake; it's a different but truthful runtime. - Production runtime cost is zero.
RealMetaTransportdoesn't talk to Extentos's backend. Voice and audio use the platform-native STT and TTS over Bluetooth. Your shipped app pays Extentos nothing per end-user. The cost base scales with developer population, not user population.
Related concepts
- Transport vs app simulation — the deep dive on what each transport simulates and why both layers exist
- Capabilities — the vendor-agnostic capability primitives your handler composes against
- Vendors: Meta Ray-Ban — the GA target, what the Meta DAT toolkit exposes, and the distribution state
- MCP server — the deterministic tool surface and the agent-driven flow
- Quickstart with an AI agent — install the MCP server and walk through a real dev loop
Related
Transport vs app simulation
Meta's Mock Device Kit simulates the transport layer; Extentos simulates the app layer — voice, photo capture, and the wearing experience. Both matter.
Capabilities
The Extentos capability vocabulary — the vendor-agnostic SDK primitives (audio, camera, voice, assistant, display, hardware events) your handler subscribes to.
Quickstart with an AI agent
Install the Extentos MCP server and let your AI agent scaffold Meta Ray-Ban smart-glasses capabilities into a native iOS or Android app. Free to start.
MCP server
The Extentos MCP server (`@extentos/mcp-server`) is an npm package an AI agent (Claude Code, Cursor, Windsurf, Cline) installs once and then uses to add Meta Ray-Ban smart-glasses capabilities to a native iOS or Android app. It exposes a tight set of deterministic tools across 10 categories — discovery, generation, agent configuration, credentials, analytics, guidance, validation, simulation, production-readiness, and documentation — plus a CLI for account linking, telemetry consent, and update checks. This is the agent's operating manual.
Meta smart glasses (Meta DAT)
Meta smart glasses developer guide: Wearables Device Access Toolkit (DAT 0.8.0) capabilities, supported models (Ray-Ban Meta, Oakley Meta, Ray-Ban Display), 2026 distribution state, and how Extentos abstracts the toolkit.
Concepts
The mental-model pages for Extentos — transport vs app simulation, architecture, the capability vocabulary, the AI gateway, the assistant, and display.
Capabilities
The Extentos capability vocabulary — the vendor-agnostic SDK primitives (audio, camera, voice, assistant, display, hardware events) your handler subscribes to.