AiHummer docs
v1.0.x
RU EN

Multitenancy & idempotency

v1.0.x · updated 2026-06-26

AiHummer is multitenant: one gateway can serve many workspaces from one database while keeping their data apart. Two mechanisms make that safe — Postgres Row-Level Security (RLS) for read/write isolation, and an idempotency layer so that retries and turn recovery never duplicate a real-world side-effect.

Tenant isolation with Row-Level Security

Isolation is enforced at the database, not just in application code. AiHummer runs queries through a restricted Postgres role (aihummer_app) that has RLS policies applied, so the database itself rejects rows that do not belong to the active tenant.

RLS is opt-in. You activate it by giving the gateway a second connection string for the restricted role:

# gateway.env
# Owner pool — runs migrations and privileged maintenance
AIHUMMER_DATABASE_URL=postgres://aihummer:***@localhost:5432/aihummer?sslmode=disable
# Restricted role — activates RLS for normal request traffic
AIHUMMER_DB_APP_URL=postgres://aihummer_app:***@localhost:5432/aihummer?sslmode=disable

[!NOTE] For host-native installs the installer sets AIHUMMER_DB_APP_URL automatically, so RLS is on by default in a standard deployment. If the variable is absent the gateway runs on the owner pool only and RLS is not active.

Per-tenant scoping

Within the restricted role, every request is scoped to its tenant: the gateway sets the current tenant context on the connection so that RLS policies resolve against the right workspace. WHERE tenant_id = … filters are not hand-rolled everywhere; the policy enforces it centrally, which means a missed filter cannot leak another tenant’s rows.

System / bypass mode

Some work is legitimately cross-tenant — background workers, schedulers and maintenance jobs that operate across the whole instance. These run in a system / bypass mode so they can see what they need to. Bypass is reserved for trusted internal workers, not for request-handling paths.

[!WARNING] Migrations always run on the owner pool, never under the restricted role. The owner connection has the privileges to alter schema and apply RLS policies; the restricted role deliberately does not. Keep the two connection strings distinct and grant the aihummer_app role only what it needs.

Idempotent side-effects

A turn can produce real-world side-effects: sending mail, posting a message back to a channel. If a turn is retried — because of a transient error, or because the gateway restarted mid-turn and recovery replays it — those side-effects must not happen twice. AiHummer guarantees this with two cooperating pieces.

A resume-stable ledger key

Each side-effect is recorded against a resume-stable ledger key: a key that is derived deterministically from the turn, so a replay of the same turn computes the same key rather than a fresh one. The ledger remembers which keys have already been actioned.

A side-effect barrier

Before an effect such as mail or channel-send is performed, it passes through a side-effect barrier that checks the ledger. If this key has already fired, the barrier short-circuits and the effect is skipped; if not, the effect runs and the key is committed.

side-effect requested
   └─▶ compute resume-stable ledger key
         └─▶ barrier: key already committed?
               ├─ yes ─▶ skip (no double-send)
               └─ no  ─▶ perform effect ─▶ commit key

The result is exactly-once external behaviour even though delivery is at-least-once internally. Retries are safe by construction, which is what lets turn recovery (see Delivery, outbox & recovery) replay an interrupted turn without a customer receiving the same email or the same reply twice.

[!TIP] This is why AiHummer can offer guaranteed delivery and automatic turn recovery without the usual risk of duplicate messages — idempotency is the foundation the delivery guarantees are built on.

Where to next