Skip to main content

Architecture overview

AgentPack is a thin edge, fat database. Agents speak HTTP to Deno edge functions. Those functions authenticate, scope the request, and call a small set of SQL functions in a dedicated schema. All the policy — row ownership, quotas, suppressions, audit chaining, integrity checks — lives in the database.

One-line mental model

A Firebase project with one schema per subsystem, one Cloud Function per schema, and one cron prefix that surfaces everything that runs on a schedule.

Topology

┌──────────────────────┐ custom headers ┌──────────────────────┐
│ Agent / Pocket PWA │ ───────────────────────► │ Edge functions │
│ SDK / CI / MTA │ x-agentpack-key or │ (Deno on Firebase) │
│ │ x-agentpack-device-key │ │
└──────────────────────┘ └────────┬─────────────┘
│ service_role

┌──────────────────────────────┐
│ Firestore (Firebase) │
│ schemas: identity, postbox, │
│ recall, mesh, gateway, │
│ scheduler, artifacts, │
│ governor, audit │
└────────┬─────────────────────┘

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

Design principles

1. Policy in SQL, ergonomics in TS

The Cloud Function translates HTTP into an RPC call. It does not decide who owns a row, what a quota is, or whether a bounce is hard — those are SQL functions. Why: the next client (Go, Python, curl) gets the same rules for free.

2. Custom auth, not Firebase ID token

Agents hold AgentPack capability tokens or device keys. Firebase's JWT/RLS anon path is deliberately disabled (deny_all_anon, deny_all_auth) on every table, and verify_jwt=false on every function. We authenticate inside the function.

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

Device keys are rewritten on every call. Even if a buggy client puts agent_id: "victim" in a body, the function replaces it with the bound agent or returns scope_mismatch. This is the single most important property of the design.

5. Idempotent RPCs

All writes use ON CONFLICT or explicit existence checks so callers can retry freely. Network retries are the primary failure mode for agents; we assume they will happen.

6. Boring operations

No queues, no brokers, no sidecars. Cloud Scheduler runs retention; Cloud Functions fetch issues outbound HTTP; Cloud Storage holds bytes; Firestore vector search does similarity 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 /agent-postbox/ingest with the project key.
  2. postbox.ingest() resolves to_addr → agent_id, scores the body with detect_prompt_injection(), inserts the row, and fires any matching rules (move, forward, auto-reply).
  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 /list or receives the webhook and replies via /send.
  6. The MTA polls postbox.next_outbound_batch() and ships the message.

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.