Give it a record: the tables a worker runs against, the audit trail, and the MCP wire
Where the book continues after this room's basic worker, from The AI Agent Factory: every worker runs against a system of record; four tables every worker keeps; Postgres with pgvector over a separate vector store; the embedding pipeline; the audit trail written in the same transaction as the action; what MCP is and is not; a development plane that never reaches runtime; when a custom MCP server earns its keep over a plain function tool; reads run free, writes are gated, schema changes never reach production through the agent.
- Status
- stable
- Trust
- human-reviewedHuman: Raza Rizwan ·
- Owner
- Human: Raza Rizwan
- Approved
- Human: Raza Rizwan ·
The worker this room builds keeps its conversation in a session file and its knowledge in the tool bodies. The book's next invariant is the one that turns an agent into a worker: every worker runs against a system of record. The engine is what a worker runs on; the system of record is what it runs against. Two open standards keep that portable: skills let capabilities travel between tools, and MCP is the wire the agent uses to reach the record. 1
This page is where the book continues after test end to end, and it is also the answer to the most common way a text worker fails to feel natural. A worker with no way to list the records a person owns cannot answer "where is my order"; the test build on design the experience failed exactly there. The record is not an optional layer for later. It is what the tools read.
Four kinds of data a worker cannot afford to forget
The store holds four things: 1
| Kind | What it is | Where it lives |
|---|---|---|
| Business records | Customers, tickets, orders: the truth the worker reads and updates | Ordinary relational tables the agent reaches through tools |
| Reference library | Policies, knowledge-base articles, summaries of past resolved cases: knowledge searched by meaning | documents and embeddings |
| State | The live conversation, its turns written by the SDK's session; one row beside it as the envelope: who, when, a closing summary | The SDK's own tables plus conversations |
| Trace | The replayable record of what the worker did | audit_log |
A failure in production traces to one of three places: a skill that never got discovered, a system of record two workers disagree about, or a wire that drops events. The map tells you which. 1
Why Postgres, and why not a separate vector store
Pick which Postgres, not Postgres versus a vector database. Three reasons that hold in 2026: one database means one transaction and one auth boundary, with the vectors next to the records they relate to so a join stays a join rather than a network hop; Postgres already does the hard parts, transactions, indexes, foreign keys, row-level security, point-in-time recovery; and MCP servers exist for Postgres at every layer. A dedicated vector store wins only when search by meaning is the product rather than a feature beside your business data: more vectors than fit in one server's memory, search traffic heavy enough to need an engine built only for vectors, or vectors used on their own by many services. A worker with a tickets table and its embeddings beside it is nowhere near that point. 1
A managed Postgres that scales to zero costs nothing while the worker is idle, which matters when many workers are each busy only in bursts; one that branches lets the agent try a schema change on a copy and delete the branch if it goes wrong; and one with an official MCP server lets your coding agent create projects, manage branches and run migrations in plain language while you build. Any Postgres with the vector extension works; the branching and the MCP server are the conveniences, not the requirement. 1
The tables every worker keeps
Four tables are the shared spine, and one more you add when you want usage analytics: 1
conversations: one row per conversation, who it was with, when, and a short summary at the end. The turn-by-turn messages are stored separately by the SDK's session, which creates and writes its own tables, so you never design those. Picture a transcript and a cover sheet: the transcript is the SDK's, the cover sheet is yours, linked by the same session id.documentsandembeddings: the reference library. Documents hold the text; embeddings make it searchable by meaning. One embeddings table serves both documents and past conversations, with a check constraint that makes each row point at exactly one, so "have we answered this before?" uses one index, not two.audit_log: the trace, every meaningful action in order, including business events such as a refund being issued. It uses an auto-incrementing integer key rather than a UUID, because audit rows pile up fast and a plain integer keeps writes quick and the order obvious.capability_invocations, optional: one row each time the worker runs a skill or calls a tool, with how long it took, whether it succeeded, and a rough cost. Both share the one table with a column that says which.
Two more tables live outside the spine: your business tables (customers,
tickets, orders, refunds), and run_states, which stores a paused approval
when a human signs off later or in another process rather than right away.
Every table carries a JSONB metadata column as an escape hatch; use it
sparingly, and promote anything you query often to its own column. 1
Among the tables you build, the audit log grows fastest, because one interaction can write several action rows while it adds one conversations row and no new documents. Plan its retention and indexing first. 1
Search by meaning: the embedding pipeline
An embedding turns a piece of text into a point in space; text about refunds lands near other text about refunds, so "find similar tickets" becomes "find the nearest points". The rest is plumbing, four steps with one decision each: 1
- Chunk long text at natural breaks, headings and paragraphs, into pieces of a few hundred words that each carry one idea, with a little overlap so an idea that straddles a boundary is still found. Do not chunk what is already short.
- Embed each chunk with the model, in batches.
- Store the text, its point and a little metadata in the embeddings table.
- Query by turning the question into a point too and asking for the nearest stored points, cosine distance, nearest first.
The one rule that never bends: embed your stored text and your search queries with the same model, or the matches come back as noise. The vector column's size is decided by the model: 1536 for OpenAI's small text embedding model, 3072 for the large one. Start with an HNSW index, the fast-search default; reach for IVFFlat only if build speed matters more than search speed. Re-embed when the source text changed, when you switched embedding models (every old point now lives on a different map), or when you changed the chunk size. When results look wrong, the cause is almost always one of three: the query and the stored text went through different models, you forgot to filter by source type, or your chunks are too small to carry meaning. 1
Filtering by source matters more than it sounds. If documents and past conversations share one table and a search returns both unfiltered, the agent can treat a snippet of an old chat as if it were policy. Ask for documents only, or run two searches and weigh them. 1
The audit trail is a discipline, not a log
Two things sit close together and get confused. The truth itself is what is currently the case, a customer's tier, a ticket's status, a policy's text; it lives in the business records and the reference library. The audit trail is the replayable record of what the worker did to that truth: which tool it called, what it changed, what it returned, who approved it. It answers a different question, not "what is true?" but "what did the worker do, and can you prove it?" 1
What goes in: every tool or skill call with its name, inputs, result and duration; every change to the record; every guardrail decision; each model call with its token cost. What stays out: the full conversation text, which the session already holds; raw sensitive data in a row a person can read; the model's private reasoning. 1
The writing discipline is one sentence: whatever code issues a refund writes the refund and its audit row in one transaction; both land or neither does. Give every action a name from a small, agreed set and do not let those names drift; enforce the vocabulary with a check constraint, so introducing a new verb is a deliberate one-line migration. The test of an audit trail: given a conversation and a time, you can reconstruct what the worker did and why without re-running the model. And the business reason the book gives: workers only become governable when a ledger makes them legible, and you cannot charge for an outcome you cannot prove happened. 1
Write the audit rows over their own connection, not the pool the business tools use. Audit must succeed even when the data pool is saturated, and audit writes must not compete with business writes. An audit subsystem that can be starved by the system it is auditing is not an audit subsystem. 1
What MCP is, and is not
The Model Context Protocol is an open client-server protocol for how an agent connects to external tools, data and prompts: one protocol, many implementations, swap any side without breaking the other. A server exposes three primitives: tools the model can invoke, by far the most used; resources, read-only data the agent can fetch; and prompts, reusable templates a team can publish. Three transports: a local subprocess for agent and server on one machine; streamable HTTP for remote servers and production, one endpoint over plain HTTPS; and the older server-sent-events transport that many servers still expose and new ones increasingly leave behind. Streamable HTTP comes in two flavours: stateless, where each call is an independent request so many copies can sit behind a load balancer, and stateful, which keeps a session open for streaming partial results but pins each client to one server instance. Use stateless unless you have a specific reason. 1
Four things MCP is not: 1
- Not a framework. Your agent's MCP client speaks the protocol to an MCP server; the SDK includes the client.
- Not a service. There is no MCP cloud; servers are programs you run or that vendors run for you.
- Not a security boundary. MCP defines transport and protocol; what tools a server exposes and what they can do is the server's responsibility. A malicious server can do anything its code does. The trust boundary is still the agent loop deciding which tools to call and the sandbox the tools run in.
- Not a replacement for a function tool. Both have a place; the decision is below.
To the model, an MCP tool and a function tool look the same: a callable with a schema. The difference is where the implementation lives. An agent can connect to several servers at once and sees the union of their tools. 1
The development plane never reaches runtime
The single most important line in the book's MCP part: the database vendor's own MCP server, which exposes projects, branches, schema, migrations and ad-hoc SQL as plain-language tools, is a development tool, not a production one. Use it to design the schema, test a migration on a throwaway branch and merge it, explore your own data, and look things up. A live worker should never hold a run-arbitrary-SQL tool, because that is a door you cannot fully lock. It needs a few narrow abilities, not the power to run any query. 1
The two production patterns are a custom MCP server that exposes only the specific operations the worker needs, or a direct database connection wrapping them. The book's worked build uses both: a scoped server with three tools for the business operations, and a direct connection only for the audit subsystem. The rule of thumb for the running worker's tools: reads run free, writes are gated behind human approval, and structural changes never reach production through the agent at all. Schema changes happen at development time, on a branch, before they touch main. 1
Four habits when the SDK's built-in MCP client connects to a server: open and close the connection as a managed block so a subprocess or session never leaks; cache the tool list in production so the agent asks "what tools do you have?" once rather than on every run, and refresh it when the tools change; stack servers, the model sees the combined tool set; and gate the dangerous tools behind approval, which is also the practical knob for using the vendor server by hand with its destructive tools behind a prompt. 1
A function tool, your own server, or a vendor's
The value of MCP rises with the value of the boundary it creates. A boundary you do not need is overhead. 1
| You want to expose | Use | Why |
|---|---|---|
| One function with one input, used by one agent | A function tool | No protocol overhead; a local call is fine |
| Capabilities reused across agents, sensitive data, or a scope you want enforced | A custom MCP server | Scope: only the tools you define, no escape hatch. Isolation: its own process and pool. Reusability: a second worker talks to the same server |
| A third party's API with a vendor server that exists | The vendor's MCP server | Do not wrap a third-party API in your own server unless you need to add policy on top |
The shape of a custom server is simpler than it sounds: a small program that declares a handful of named tools, each with a plain-language description that tells the model when to reach for it and a short list of typed inputs. No general SQL, no escape hatch. The trade-off is real: another process to deploy, another set of logs, another hop, another version. Do not write one for a single function used by a single agent; write one when the capability will be reused, when scoping matters, or when isolation buys safety. And read every tool's description in the built server the way you read a skill's description: it is what the model reads to decide when to call it, and a vague one fires at the wrong time. A tool whose input is "query: string" is a run-SQL tool in disguise; a lookup should take an identifier. 1
Under real traffic three pressures appear, each with a plain fix: move from a local subprocess to the remote transport the moment more than one agent shares the server; connect once at boot and keep the connection, cache the tool list, and keep a pool of database connections inside the server; and put a ceiling on everything, a step cap per request, a couple of retries on a failed tool call, a rate limit on the server, with the trace following the call across the boundary so a slow query inside the server is visible from outside. 1
The gate on the one action that moves money
The book adds the approval gate last, on purpose. A gate added before the
worker runs is untestable theatre: you cannot tell a working gate from a
broken one when nothing flows through it. Added on a worker you have watched
search, draft and audit, you can prove both halves: approve lets the refund
through, with the refund row, the order status and the audit row landing in
one transaction; reject stops it, with no refund written and the trace showing
the action was declined. The model proposes and a human disposes. When the
approver is not at the terminal, the paused run is serialised into
run_states and resumed from another process, the refund is made idempotent
on the order plus a request id so a retry cannot pay twice, and one active
turn per conversation is enforced with a lock. 1
That gate is needs_approval from build the text agent,
now writing to a real record. The eval for it is on
evals before shipping: both halves, every time.
What this page asks of the tools in this room
Before the record exists, the room's rules already point here. Every record a person owns gets a tool that lists it, scoped by the identity the harness supplies; every lookup takes an identifier, never free text the tool turns into a query; every write is gated; every meaningful action leaves a row. When the worker outgrows its session file, the tables above are where those rules land, and the wire between them is MCP.
Continue to the worked example, which runs the room's basic worker through all of it and says exactly where this page takes over.
Footnotes
Sources
Test end to end: laptop, container, live URL
The done-when checks from The AI Agent Factory applied at each stage of the basic agent: the harness on your laptop, the same harness inside the container, the deployed URL on Vercel, then the eval set and the harness gates against the live endpoint.
Worked example: a first-reply support agent, start to URL
The book's own Tier-1 support agent taken through every page of this room: the three gates, the spec, the first test, the agent and its two tools, the harness, the eval that catches the wrong-account reply, and the done-when checks at the live URL.