GemmaPoddocs
Introduction

Architecture

The six components of GemmaPod, how the bytes flow between them, and where the trust boundaries are.

The component map

GemmaPod is six things working together. Five of them are layers of software; the sixth is the agent itself.

   POD          one signed .html capsule  =  one agent

   ├─ TOOLKIT   build & sign      @gemmapod/toolkit
   ├─ HOST      device runtime    @gemmapod/host
   │   ├─ MODEL   local LLM bridge (Ollama or OpenAI-compatible)
   │   └─ BUS     pod registry, event stream, conversation store
   ├─ SIGNAL    WebRTC relay     @gemmapod/signal
   ├─ EMBED     browser runtime  @gemmapod/embed  (+ @gemmapod/shim, @gemmapod/core)
   └─ gemmapod  unified CLI       npm i -g gemmapod

A single sentence to hold the whole thing:

Pods are the agents. The Toolkit builds them. Your Host runs them. The Model thinks for them. Signal lets them reach visitors. Embed lets a webpage hold one.

Glossary

Pod

A single signed .html file containing one agent. The capsule bundles:

  • A signed manifest (Ed25519 over CBOR) describing identity, persona, signed tool allow-list, and transport hints.
  • A WASM core (Rust) that verifies the manifest at boot and signs DARTC envelopes at runtime.
  • A Preact widget that hosts the chat UI and falls back to a small in-browser model when the Host is unreachable.

You make one with gemmapod create or by hand-writing pod.toml and running gemmapod build.

Toolkit — @gemmapod/toolkit

The build and signing library. Reads pod.toml, signs the manifest with the owner's Ed25519 key, inlines the WASM core and the embed runtime, and writes a self-mounting .html.

Also produces fresh owner keypairs (gemmapod keygen) and validates manifests against the schema (gemmapod doctor <pod.toml>).

The unified CLI uses Toolkit as a library; you don't import it directly unless you're scripting a build pipeline.

Host — @gemmapod/host

The local runtime that runs on your device. One Host process can run many Pods. It owns:

  • The pod registry — which pods are loaded, their signal state, resume on restart.
  • The event bus — every pod-scoped and host-scoped event flows here, available to dashboards and gemmapod logs.
  • The conversation store — SQLite at ~/.gemmapod/host.sqlite, keyed by (podId, conversationId).
  • The HTTP API at http://127.0.0.1:<port>/api/v1/* — used by the CLI and the bundled dashboard. Loopback-only, token-gated.

Start with gemmapod start (or implicitly via the first gemmapod run). Stops with gemmapod stop or Ctrl-C.

Model

Not a package — a role. The LLM the Host proxies pod requests to. Default is local Ollama (http://localhost:11434) running gemma4:e4b. Override per pod via pod.toml or per run via gemmapod run --model <name>. Any OpenAI-compatible endpoint will work in place of Ollama.

Signal — @gemmapod/signal

The signaling broker that lets a pod in a visitor's browser find your Host. It carries:

  • WebRTC SDP rendezvous — short-lived offer/answer exchange. Once the data channel is open the broker is out of the loop.
  • Pod registryPOST /pods uploads the signed capsule; GET /:id serves it. Pluggable storage; memory + SQLite ship.

It never sees chat plaintext. The reference broker runs at wss://signal.gemmapod.com/signal; self-host with @gemmapod/signal if you want your own.

Embed — @gemmapod/embed

The browser-side runtime that brings a pod to life inside a web page, email, or any HTML host. Republishes the two @gemmapod/shim IIFEs (full UI + headless) under stable CDN paths, with bundled .d.ts for module consumers. The shipped pod .html carries its own copy of the runtime inlined — Embed is what you reach for when you want to load a pod from a <script> tag without using the capsule format.

gemmapod — the unified CLI

gemmapod create   # interactive wizard → pod.toml + owner.key
gemmapod build    # sign & bundle into agent.html
gemmapod run     # add a pod to your Host (starts one if none running)
gemmapod start   # boot the Host with no pods
gemmapod list    # see running pods
gemmapod logs    # tail events for one pod
gemmapod status  # probe Host + Ollama + Signal broker
gemmapod stop    # stop a pod or shut the Host down

Full reference: /docs/reference/cli.

Bytes-flow walkthrough

The six nouns describe the shape. Below is what actually happens when a visitor opens a pod.

1. Pod build

gemmapod build pod.toml --key owner.key --out my-pod.html

Toolkit reads pod.toml, runs GemmaPodCore.signManifest(manifest, key) via WASM (Node target), inlines the CBOR + signature + the prebuilt shim IIFE + the WASM bytes (as a data: URL) into a 960 KB HTML template, and writes the artefact. No network. Toolkit also round-trip-verifies before emitting; a bad signature is a CLI error, not a runtime surprise.

2. Pod boot

The visitor opens the .html. The inlined <script> calls GemmaPod.boot(el):

  1. Init WASM core from inlined bytes (~0 ms; no fetch).
  2. GemmaPodCore.verifyManifest(b64ToBytes(manifestB64)). Fails closed — a tampered manifest shows the user a red refusal box.
  3. Translate the verified manifest into a PodConfig (browser-shape).
  4. mountPod(el, config, { fallbackUi: "default" }) — constructs the runtime, mounts the Preact widget, attaches the WebGPU prepare panel if a fallback transport is configured.

3. Transport selection

selectTransport(config):

  1. WebRTC first. Open a WebSocket to transport.dartc.signalUrl. Wait for the data channel labelled dartc.v0 to open. If the broker reports the Host as offline, drop through.
  2. Fallback. Construct (but do not prepare) a FallbackTransport. The host UI shows a panel; nothing downloads until the user clicks.
  3. Direct. A development convenience — straight HTTP to a local Ollama. Only useful when visitor and Ollama are on the same LAN.

4. DARTC handshake (WebRTC path)

Once the data channel opens, both peers exchange signed dartc.hello envelopes. The visitor side generates an ephemeral Ed25519 session key; the Host side signs with its own session key. Both sign a copy of the signed pod manifest into the hello payload so the Host can verify identity and the visitor knows it's talking to the right owner.

The Host also sends an A2A-shaped AgentCard on a2a.discovery, exposing the signed tools as A2A skills.

5. Chat

Visitor sends gemmapod.chat.request over the channel. Host streams back gemmapod.chat.delta frames and, interleaved, signed gemmapod.ui.event envelopes — run lifecycle, tool calls, state snapshots, custom events. The runtime auto-applies STATE_* events into runtime.state, MESSAGES_SNAPSHOT into runtime.chat, and re-emits everything as ui.event on the bus.

6. Conversation memory

The browser keeps a stable conversationId per (pod, host) pair in localStorage. A page refresh creates a fresh WebRTC peer and a new ephemeral DARTC session key, but the runtime sends the same conversationId in the next dartc.hello so the Host can reattach to the existing thread.

The Host stores conversation memory in a local SQLite at ~/.gemmapod/host.sqlite keyed by (podId, conversationId). The WebRTC peer is short-lived; the conversation is durable.

Trust boundaries

BoundaryTrust assumption
Manifest signatureVisitors trust the owner pubkey, the way you trust an email sender.
WASM coreSame Rust source on every side. pkg/ (web) and pkg-node/ (node) cannot drift.
DARTC frame signingEphemeral session keys; replay window enforced by msg_id.
Tool executionHost enforces the manifest's signed allow-list. Pods cannot escalate.
Signal brokerCarries SDP + signed blobs only. Never sees chat plaintext.
In-browser fallbackVisitor's machine runs the model entirely; no network during chat.

Detailed threat model: Security model.

Package map

ComponentPackage(s)Role
Pod(the capsule itself — not a package)One signed .html = one agent.
Toolkit@gemmapod/toolkitBuild, sign, validate. Used by the CLI; usable as a library.
Host@gemmapod/hostLocal runtime: registry, bus, conversation store, HTTP API, dashboard.
Model(external — Ollama or OpenAI-compatible)The LLM the Host proxies to. Not a @gemmapod/* package.
Signal@gemmapod/signalWebRTC handshake relay + pod registry. Self-host if you want your own.
Embed@gemmapod/embed, @gemmapod/shim, @gemmapod/coreBrowser runtime. embed is the public CDN/npm face; shim is the source; core is the Rust/WASM signer/verifier.
CLIgemmapodThe developer front door. Calls into Toolkit and Host.
Protocol@gemmapod/dartcDARTC envelope types, canonical JSON, topic helpers, UI-event types. No deps.