Chapter 06 Planned

# Two memories. One slow and forever, one fast and forgetful.

Houston Cloud has two databases that work as a team. Postgres is the boring permanent record: workspaces, users, agents, who can talk to whom. Redis is the fast scratchpad: who's online right now, which session is open, where the WebSocket is. Both matter. They do completely different jobs.

## Postgres, the long term memory

Postgres is the most boring, most reliable database in existence. Tables, rows, SQL. It's what your bank's bank uses. When data has to be there next Tuesday, it goes in Postgres.

In Houston Cloud, Postgres holds:

- Workspaces: "Acme Corp" has these settings, these billing details, this namespace.
- Users: Juan is in Acme, his email is juan@acme.com, his Supabase ID is xyz.
- Agents: "HR agent" belongs to Acme, lives at this Knative URL, costs this much.
- Permissions: Juan can talk to HR. Cannot talk to Sales. Map of (user, agent, can_use).
- Billing: how many messages this month, how many tokens, how much owed.
- Audit log: who did what when. For compliance.

None of this changes fast. Permissions get set once and read a thousand times. Postgres is perfect.

## Why Supabase

Supabase is "Postgres in a box, plus auth, plus realtime, plus file storage, plus a Node.js library." You get a managed Postgres database and a bunch of helpful extras around it. We already chose Supabase for auth, so we get the database for free (well, for cheap).

The alternative is hosting Postgres ourselves on AWS RDS or Google Cloud SQL. We could. Supabase is faster because we don't have to wire up auth separately. Honest tradeoff: Supabase has ops headroom limits if we grow huge. We can migrate to dedicated Postgres when it actually hurts.

## Redis, the short term memory

Redis is a different shape of database. Postgres stores everything on disk. Redis stores everything in RAM. That makes it stupid fast (microseconds per query) and stupid forgetful (if it restarts, things you didn't explicitly save can vanish).

Trade-off: speed for permanence. For some data, that's the right trade.

In Houston Cloud, Redis holds:

- Active WebSocket sessions: which user is currently chatting with which agent, on which control plane pod.
- Routing cache: "agent HR-Acme is at this Knative URL." Avoids hitting Postgres for every message.
- Rate limits: "Juan has sent 47 messages this hour." Resets every hour. Lives in RAM.
- Permission cache: "Juan → HR: allowed." Avoids re-checking Postgres on every message in a chat.
- Pub/sub: when an agent's pod emits an event, Redis fans it out to all the control plane pods that have subscribers.

Notice the pattern: anything we'd be sad about losing goes in Postgres. Anything we can rebuild from Postgres in a second goes in Redis.

## The four tables you actually need to remember

The first version of the schema is roughly four tables in Postgres.

Table | What's in it | How often it changes
--- | --- | ---
`workspaces` | Tenant ID, name, billing plan, K8s namespace | Rarely (new customer signs up)
`users` | User ID, Supabase ID, email, workspace, role | Sometimes (invites, removals)
`agents` | Agent ID, workspace, name, Knative URL, settings | Sometimes (new agent published)
`permissions` | User ID + Agent ID + can_use bool | Sometimes (admin grants/revokes)

Plus an audit log table and a billing-events table. That's it for v1. Grow from there.

## How they work together

01
Juan opens Houston
Frontend calls control plane: "give me my agents."
02
Control plane → Postgres
Look up Juan's permissions. Returns: [HR, Recruiter]. Caches in Redis for 5 minutes.
03
Juan clicks HR
Frontend opens WebSocket to control plane.
04
Control plane → Redis
Lookup HR's Knative URL. Cached. Microseconds.
05
Control plane → HR pod
Open WebSocket to HR. Stream the conversation.
06
Postgres ← Control plane
After the turn ends, write an audit entry: "Juan asked HR a question at 10:42."
Postgres is the source of truth. Redis is the speed layer in front of it.

## Why we don't just use Postgres for everything

We could. Postgres can do rate limiting (with row locks), can do pub/sub (with LISTEN/NOTIFY). It would just be slower and more expensive.

Adding Redis is a 30-minute decision that buys us 100x throughput on the hot path. Cheap insurance against scale problems we don't want to debug at 3am.

## Where the agent's own memory lives

Important distinction. The agent's own memory (its conversations with the user, its CLAUDE.md, its skills) is not in Postgres. That lives in the agent's pod, on its persistent volume, in the `.houston/` directory. Postgres only holds the platform metadata around the agent.

The pod is the agent. Postgres is the registry of which pods exist and who's allowed to talk to them.

Two databases, one rule

Lose Redis: site is slow for a few minutes, no data lost. Lose Postgres: catastrophe, everyone's locked out, payments break, this is what backups are for. Treat them accordingly: Postgres gets daily backups + read replicas + point-in-time recovery. Redis gets a restart script and a shrug.

Concrete services

Postgres via Supabase (managed). Redis via Upstash (managed, serverless, cheap) or a small self-hosted instance on the cluster for v1. Connection pooling via PgBouncer in front of Postgres to keep connection counts sane as the control plane scales.

← Previous
The control plane
Next →
Storage