One Durable Object
per tenant.
Herald's privacy isn't a policy checkbox — it's the topology. Every Product you connect gets its own Cloudflare Durable Object with its own SQLite database. Cross-tenant queries can't exist; not disallowed, architecturally impossible.
A single-tenant server, allocated to you.
A Cloudflare Durable Object is a single-tenant stateful worker
with its own SQLite database. When you create a Product in
Herald, a DO is spun up, keyed by
product_id, and
it's the only place your Product's state lives.
The DO wakes on each request — events, webhooks, chat — serves the request, and hibernates. You don't pay for idle. We can't pay for idle. The architecture is the unit economics.
The right store for each shape.
Hot tenant state on SQLite next to the agent. Raw events in a columnar OLAP. Blobs in object storage. Embeddings in a vector index. Every key, object, and row is prefixed by your Product's ID — no shared tables.
| Store | Holds | Retention |
|---|---|---|
| Durable Object SQLite | Tenant state — settings, rollups, feedback clusters, semantics, briefings, watchlists, agent memory. | Lifetime of the Product |
| Analytics Engine | Raw append-only product events at scale. Columnar OLAP; cheap at billions of rows. | 365 days |
| R2 | Large blobs — sales-call transcripts, email attachments, chat attachments. | 90 days unless referenced |
| Vectorize | Feedback + call + review embeddings. One namespace per Product for semantic dedup and search. | Lifetime of the Product |
| Workers KV | Shared non-tenant lookups — SDK key → Product mapping, hostname cache, feature flags. | While connection is active |
| Cloudflare Secrets Store | Platform secrets. Per-Product OAuth tokens are encrypted with AES-GCM before being written to the DO. | While connection is active |
Structured context, never raw payloads.
Every LLM call goes through Cloudflare AI Gateway — one audit log, one cost ceiling per tenant. What we send per call is deliberately narrow: your schema shape, your semantic annotations, structured sub-agent outputs, and the question at hand.
We do not send raw event payloads, full customer records, full email bodies, or full call transcripts as LLM context. When a model needs one specific row — a feedback quote for a briefing, an objection from a sales call — we pass only that row, with identifiers redacted where possible. Under our contracts with Cloudflare and Anthropic, data routed through these channels is not used to train foundation models.
Chat-generated analysis code runs in an isolated Cloudflare Dynamic Worker with no network, no bindings, and a read-only SQL view of a sandbox copy of your data. It can't reach your main tenant state. It can't call out.
See it run on your data.
Magic link. No password, no credit card.
By submitting, you agree Herald can email you the briefing and occasional product updates. See our Privacy Policy and Terms. Unsubscribe any time.