Skip to main content

Connections release across cognition — a pooled DB connection is never held idle across…

The canonical failure: a daemon (or a request flow) reads from a store, runs a slow LLM step, then writes back. If it holds one pooled Postgres connection for the whole flow — the v1.32.0 pin — that connection sits checked out and idle for the entire LLM call. Multiply by the flow's concurrency and the pool (say 20 connections behind a bounded session pooler) is exhausted by connections doing nothing but waiting on an LLM. A cheap metadata read elsewhere (a rotate sweep's list_class) then pool timed out while waiting for an open connection.

The law. A pooled database connection is a scarce, coherent resource. A flow holds one only for the duration of a store operation, never across a cognition (LLM) step between two operations. The connection belongs to the retrieve/persist/rotate, not to the ask that sits between them.

Why the pin exists — and when it must not

The v1.32.0 connection pin holds one connection per postgresql axonstore for the flow's whole life, so every store op routes through the same physical backend. That is REQUIRED under a transaction-mode pooler (pgBouncer pool_mode=transaction, Supabase Supavisor :6543, Neon, RDS Proxy): successive checkouts land on different sessions, so a prepared statement or SET minted on one is gone on the next — the pin keeps them on one session for coherence.

Under a session pooler (Supabase session mode :5432) or a direct connection, each pool connection IS a stable, coherent session. Coherence is automatic; the pin buys nothing and costs everything — it keeps a scarce connection checked out across the LLM I/O.

AXON_DB_POOLER_MODE makes the pin pooler-aware:

  • transaction (default, or unset) → pin ON. Unchanged behaviour; the coherence the transaction pooler needs.
  • session | direct → pin OFF. Store ops acquire per-op and release between them. A flow's connection is returned to the pool the instant a store op finishes — so a slow LLM step (or a whole quiescent stretch of a flow) holds nothing.

Committed data stays globally visible across per-op checkouts (MVCC), and the tenant GUC is set per-transaction inside begin_tenant_tx — so read-your-writes and RLS remain correct without the pin. Only the transaction pooler's cross-checkout session-state loss needs it.

Relation to the other laws

  • dispatch_vs_cognition (v2.9.0) applied to a RESOURCE: the runtime holds the connection for the dispatch (the store op); cognition (the LLM step) holds nothing. The pin, under a coherent pooler, would blur that line — this law restores it.
  • time_is_an_explicit_input / graceful degradation: a custody enumeration that can't get a connection fails closed with a witness and retries, rather than blocking a pool slot — the same fail-closed posture as rotation_without_revelation, tuned so a transient contention does not become a permanent quiesce.

The honest test: if a flow's slowest step is an LLM call and it is still holding a database connection during it, the pool's effective size is not its connection count — it is its connection count minus every in-flight flow. Under a coherent pooler, AXON releases the connection so the pool is sized by concurrent DATABASE work, not by concurrent COGNITION.