Postgres JSONB as the Scratchpad for Multi-Agent State
By wGrow Project Team ·
The Split State Penalty

A team builds an internal HR agent crew for leave applications — an orchestrator delegating to a retrieval agent for policy lookup, a logic agent for balance checks, a write agent to commit deductions to the HR system. Standard multi-agent pattern. Retrieval and memory run on a managed vector database. Core HR data and tool execution logs live in Postgres.
That architecture is fragile.
It seems sensible at design time. The vector store handles semantic search over HR policy documents and persists conversational scratchpads between agent turns. Postgres handles employee records, leave balances, and the audit trail needed for compliance. Two databases, two responsibilities, clean separation. It looks reasonable on paper.
Multi-agent systems do not cooperate with clean separation. Concurrent tool calls from different agents land simultaneously — and when the leave logic agent commits a balance deduction at the same moment the orchestrator flushes a conversation summary to the vector store, there are two writes in flight across two systems with no coordinating transaction. The vector database has no concept of the Postgres commit. Postgres has no concept of what the vector store is doing. The result is race conditions and state drift.
Splitting state across specialised databases imposes a coordination tax. At modest session volumes, that tax is hard to justify. Keeping state in Postgres avoids it.
Anatomy of a Split-State Failure
One failure the split architecture allows is a partial write during a network hiccup. Nothing exotic.
The leave logic agent approves a leave application. The balance deduction commits to Postgres. Simultaneously, the orchestrator attempts to write the updated conversation context — including the approval — into the vector store scratchpad. The connection times out. The write fails silently. Retry logic fires asynchronously, after the agent has already moved to the next turn.
Postgres records the approved leave and the deducted balance. The vector store holds the previous turn’s context. The agent’s next response treats the leave as still pending. A staff member can submit the same application twice and get it approved twice.
One fix is reconciliation scripts and retry wrappers. Those scripts become their own maintenance liability: every schema change to the vector store metadata requires a matching update to the sync logic. More glue. More surface area to break.
Dedicated vector databases excel at approximate nearest-neighbour search at low latency. Pinecone and Milvus are built for exactly that problem. They are not designed to be transactional state stores for business logic that touches leave balances or money. They cannot participate in the same transaction as a Postgres business write — that is the design contract a split architecture violates. The vector store can persist the scratchpad update, but it has no mechanism to make that write and the leave-balance deduction commit or roll back as one unit. Using them this way pushes them outside their intended boundary.
Moving the Scratchpad to Postgres JSONB
| 1 | CREATE TABLE agent_state ( | |
| 2 | session_id UUID PRIMARY KEY, | |
| 3 | scratchpad JSONB NOT NULL, | ← ① |
| 4 | embedding VECTOR(1536), | ← ② |
| 5 | updated_at TIMESTAMPTZ DEFAULT NOW() | |
| 6 | ); |
- ① Stores raw conversational memory and tool execution logs.
- ② Enables semantic search alongside relational state data.
A different design for a similar crew is an SME quotation generator handling pricing inquiries, inventory checks, and quote assembly. Same multi-agent pattern. Different architectural choice.
Everything lives in a single Postgres table. There is no standalone vector cluster.
One table, agent_sessions, holds a JSONB column called scratchpad. That column stores the full agent state: raw message history, tool call logs with inputs and outputs, intermediate reasoning traces, metadata tags. Each row is a session. Updates are standard Postgres writes inside transactions.
Semantic retrieval — still needed for policy document lookup and historical quote matching — lives in the same table via an embedding column using pgvector. Embeddings are generated via a hosted model API and the vectors are stored alongside the structured data. A single SQL query can combine a cosine similarity filter on the embedding column with a JSONB condition on the scratchpad metadata. One round trip. No cross-system coordination.
The JSONB scratchpad imposes no rigid schema on agent developers. When a tool call completes, the result appends to the tool_calls array. When a context window flush happens, the summary appends to memory_snapshots. Additional keys land without migrations. The structure grows with the agent’s needs rather than requiring anticipation upfront.
ACID Guarantees for Agentic Tool Calls

A quotation generator does real work with real financial consequences. Quotes lock pricing. Inventory reservations get committed. External supplier APIs get called. When agent state and business state diverge, the system issues a wrong quote or overcommits stock — both of which cost client relationships that are hard to rebuild.
A Postgres transaction block addresses the local write path directly. The execution pattern: BEGIN. The agent retrieves relevant prior quotes via pgvector similarity search. The tool executes — pricing logic runs, the inventory API responds. The tool result, the reservation record, and the scratchpad update all write together in the same transaction. COMMIT. If that local commit fails, the entire block rolls back: the scratchpad returns to its pre-call state and no reservation row lands in the database. The external supplier call already happened outside the transaction boundary, though — it needs its own idempotency key and a compensating action if the local commit doesn’t follow through. Postgres keeps the agent’s local state consistent; distributed side-effect safety is a separate problem you still have to solve.
This local atomicity is difficult to replicate in a split architecture. When Postgres rolls back cleanly, the vector store has already accepted a write with no corresponding rollback mechanism. You’re left with an orphaned embedding representing a tool call that never happened. Your retrieval agent finds it on the next similarity search and acts on stale state.
That is a hallucination sourced from your infrastructure, not from the model.
LLM hallucination is a probabilistic problem. You address it with better prompts, retrieval grounding, and evaluation harnesses. Database state corruption is an engineering choice. You address it by not creating the conditions for it.
Cutting Infrastructure Complexity
- DB Clusters
- 1
- Transactional State
- Atomic
One system to operate
Local writes
Consolidating can cut database spend. A managed vector cluster with high-availability configuration, replicas, and dedicated network egress can be the majority of infrastructure cost for a low-volume crew, and dropping it removes that line item (LLM API costs are a separate matter).
pgvector on an existing Postgres instance adds little marginal cost. The extension is simple to install, and index creation on a modest embedding column is quick. At low session volume, similarity query latency is not the bottleneck — the LLM API round-trip dominates. At significantly higher vector counts — millions of rows, high-throughput recall requirements — a dedicated ANN index will outperform pgvector’s HNSW implementation, and the trade-off calculus shifts. A small agent workload does not reach that range.
The operational simplification matters as much as the cost saving, maybe more. One set of Postgres migrations covers both relational schema changes and embedding column management. One backup schedule. One set of monitoring alerts. One on-call runbook. Engineers who already know Postgres can work on the agent state layer without learning a second operational surface. That last point is underrated — the cognitive load of a second system compounds over time, and it compounds fastest when something breaks at 2am.
The pattern generalises. Agent scratchpads, tool call logs, semantic memory, and structured business data are not fundamentally different kinds of information. They are rows with different shapes. Postgres has been storing rows with different shapes since 1996. pgvector added the embedding dimension. The infrastructure for this problem existed long before anyone labelled it an “agentic workload.”
Teams that consolidate around Postgres spend less time writing synchronisation glue and more time shipping. AI workloads do not require reinventing data persistence. The boring choice is the right choice.