Course · Part 3, Memory · Lesson 3.2 · 14 min
State and MCP: state that lives outside the prompt
A three day onboarding run kept its progress in the conversation, lost it at compaction, and sent the welcome email twice; the state belonged in a record the agent reads through a tool.
Before you read: find the line
Read the run below once. Decide which line is the mistake. The lesson tells you.
- 1systemrun 7731 resumed. Context compacted to: "Invite sent to admin@northline.io on day 1. Waiting for acceptance."
- 2toolevent: invite accepted by admin@northline.io
- 3llmNo account exists yet for Northline. Creating it.
- 4toolcreate_account(name="Northline", owner=admin@northline.io) -> ok: acc_4412
- 5toolprovision_workspace(account=acc_4412) -> ok
- 6toolsend_email(to=admin@northline.io, body=welcome, workspace=acc_4412) -> sent
- 7donestatus: success
What broke
An onboarding agent sets up a new company account. The steps: create the account, invite the admin, wait for the admin to accept, provision the workspace, send the welcome email. The wait in the middle can take days. The agent ran as one long conversation, and its memory of what it had done was the conversation itself.
On day one it created account acc_4411 and sent the invite. On day three the admin accepted and the run resumed. The conversation was long by then, so the harness compacted it into a summary before the next call. The summary said the invite had been sent. It did not say the account had been created. The model saw no account, called create_account again, got acc_4412, provisioned that one, and sent the welcome email. The admin now had two accounts and two welcome emails pointing at two different workspaces.
Status at the end: success.
Why it broke
The state of a long run was stored in the one place that gets rewritten: the prompt. A conversation is not a database. It gets summarised, truncated and rebuilt, and each of those can drop a fact. The summary at line 1 dropped the account id and nothing else in the system knew it.
The second cause is that create_account was not safe to call twice. An action that changes the world needs an idempotency key, so a second call with the same key returns the first result instead of making a new thing. Payment APIs have worked this way for years; Stripe's Idempotency-Key header is the usual example.
Both are old lessons. Web servers learned to keep session state out of the process so a restart does not lose it. Agents are the same, with a worse twist: the process here also forgets on purpose, to make room.
What a senior engineer does
Keep the state of the run in a record, not in the prompt. A row in a table: run_id, the step list, a status per step, the ids created, timestamps. The agent reads the record at the start of every call and writes to it after every action. The prompt carries a pointer to the record and a short rendering of it, never the only copy. Compaction can then drop anything, because the next call re-reads the record.
This is where MCP comes in, and it is worth being exact about what it is. The Model Context Protocol (Anthropic, November 2024) is an open protocol for connecting a host application, the thing that runs the model, to servers that offer three kinds of things: tools the model can call, resources the host can read (a file, a database row, a task record, each with a URI), and prompts, which are reusable templates. The messages are JSON-RPC 2.0 over stdio or HTTP. That is all it is. MCP does not store anything. It is the plug, not the drawer.
So the task record lives in Postgres. An MCP server in front of it exposes task://onboarding/7731 as a resource and get_task and mark_step_done as tools. The agent's host connects to that server, and the model reads and writes state through it. The same server can serve a different agent, a dashboard, or a person with a command line, which is the point of a protocol. When someone says "we use MCP for memory", ask where the data is. The answer is a database. MCP is how the model reaches it.
Then make every write idempotent: create_account(email, idempotency_key=run_id + step). The second call returns acc_4411.
What to say in the interview
This round asks you to design a long running workflow without unbounded context. The answer has three parts and this lesson is the first. State lives in a record outside the prompt. The prompt gets a pointer and a summary and is rebuilt each call. Every tool that changes the world takes an idempotency key. Say that in three sentences, then say how the model reaches the record: a tool or a resource, over MCP if the shop uses it, a plain function if it does not. Interviewers notice when a candidate knows MCP is a protocol and not a store.
The line
- 1systemrun 7731 resumed. Context compacted to: "Invite sent to admin@northline.io on day 1. Waiting for acceptance."
- 2toolevent: invite accepted by admin@northline.io
- 3llmNo account exists yet for Northline. Creating it.
- 4toolcreate_account(name="Northline", owner=admin@northline.io) -> ok: acc_4412
- 5toolprovision_workspace(account=acc_4412) -> ok
- 6toolsend_email(to=admin@northline.io, body=welcome, workspace=acc_4412) -> sent
- 7donestatus: success
Line 1. The only record of day one was the conversation, and the compaction summary dropped the account id. By line 3 the model concluded, correctly from what it could see, that no account existed. The fix is not a better summary. It is a task record outside the prompt that the model re-reads at every call, and an idempotency key on create_account so a repeat returns acc_4411.
The pause
Name the three things an MCP server offers to a host, and say in one line where the state of a long run should live.
In the interview
“You have a workflow that runs for days and the context window is finite. Where does the state live, and how does the model get to it?”
Say your answer out loud, in under a minute. Then compare.
Notes
- Reference entry: State Management (MCP), pattern 10 of 21.
- For product managers: Tools, APIs & MCP Integrations.
- Introducing the Model Context Protocol, Anthropic (2024-11-25)
- Model Context Protocol specification: tools, resources, prompts, transports (2025)
- Idempotent requests, Stripe API reference
- Effective context engineering for AI agents, Anthropic: compaction (2025)