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
- The user's MTA POSTs a parsed email to
/postboxIngestwith the project key. postboxIngestresolvesto_addr → agentId, scores the body for prompt injection, and stores the row.- A raw MIME blob is uploaded to the
postbox-mimebucket; a back-reference is stamped on the row viastamp_body_ref(). - Any registered inbound webhook is delivered via
Cloud Functions fetchwith an HMAC signature (seepostbox.deliver_inbound_webhooks). - The agent polls
postboxListMessagesor receives the webhook and replies viapostboxSendwith the run'ssessionId. - 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.