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-lang releases the connection so the pool is sized by concurrent DATABASE work, not by concurrent COGNITION.