Skip to main content

Architecture overview

Ujex is a Firebase-native agent substrate. Agents speak Firebase callable HTTP through the SDKs. Cloud Functions authenticate, scope the request, enforce policy, and persist state in Firestore / Cloud Storage.

One-line mental model

A Firebase project with one callable surface per subsystem, Firestore as the control plane, Cloud Storage for bytes, and scheduled functions for background work.

Topology

┌──────────────────────┐ Bearer ID token ┌──────────────────────┐
│ Agent / Pocket PWA │ ───────────────────────► │ Edge functions │
│ SDK / CI / MTA │ session exchange │ (Cloud Functions) │
│ │ or bridge key │ │
└──────────────────────┘ └────────┬─────────────┘
│ service_role

┌──────────────────────────────┐
│ Firestore (Firebase) │
│ schemas: identity, postbox, │
│ approvals, tools, policy, │
│ governor, audit, memory │
└────────┬─────────────────────┘

┌──────────────────────────────────┴─────────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ Cloud Scheduler / Cloud Functions fetch │ │ Cloud Storage │
│ retention, relay │ │ postbox-mime, │
│ signed webhooks │ │ ujex-artifacts │
└──────────────────┘ └──────────────────────┘

Design principles

1. Policy in callables, ergonomics in SDKs

The Cloud Function authenticates the caller, resolves the agent identity from Firebase claims, and enforces quota / approval / audit rules before touching Firestore. The SDKs hide the auth dance; the backend remains the source of truth.

2. Device keys become Firebase ID tokens

Agents hold Ujex device keys (ap_live_*) or scoped inbox keys (apk_sc_*). The session callable mints a Firebase custom token, the SDK exchanges it for an ID token, and agent callables use Authorization: Bearer <firebase-id-token>. The agent id and scopes come from token claims, not request bodies.

3. Hash-chained audit, always on

Every token revoke, settings change, bulk action, and secret rotation appends to audit.events with a prev_hash link. audit.verify_chain() can detect tampering, and a daily cron records audit.monitor so ops dashboards can page on drift.

4. Server-side scoping

Agent identity is resolved from Firebase token claims on every callable. Even if a buggy client puts agentId: "victim" in a body, Inbox reads and writes under the authenticated agent id.

5. Idempotent RPCs

Writes use Firestore transactions, stable document ids where possible, and explicit idempotency rows for operations like Inbox send.

6. Boring operations

No queues, no brokers, no sidecars. Cloud Scheduler runs retention; Cloud Functions fetch issues outbound HTTP; Cloud Storage holds bytes; Firestore holds indexed metadata and BM25-backed Memory search. One platform, one backup, one place to look.

Data flow example: an inbound email

  1. The user's MTA POSTs a parsed email to /postboxIngest with the project key.
  2. postboxIngest resolves to_addr → agentId, scores the body for prompt injection, and stores the row.
  3. A raw MIME blob is uploaded to the postbox-mime bucket; a back-reference is stamped on the row via stamp_body_ref().
  4. Any registered inbound webhook is delivered via Cloud Functions fetch with an HMAC signature (see postbox.deliver_inbound_webhooks).
  5. The agent polls postboxListMessages or receives the webhook and replies via postboxSend with the run's sessionId.
  6. The bridge dispatch path ships pending outbound messages.

Each of those arrows is one RPC and one row. No message bus.

Where to go next

  • Data model — schemas, tables, and the critical indexes.
  • Threat model — what we defend, what we don't.
  • Reliability — how we think about retention, DR, and SLOs.