Today we started the memory module properly: the taxonomy first, then a deliberately forgetful agent to make the problem concrete.
1. Five Types of Memory
| Type | What it holds |
|---|---|
| Working | The immediate context passed to the model on this one call |
| Short-term | The conversation, for the length of this session |
| Semantic | Durable facts about a user — preferred language, contact channel |
| Episodic | A record of specific past events |
| Procedural | How to carry out a task |
The framing that matters: memory is a lifecycle, not a database.
The hard questions are when do I write this, and what is it for — not where it’s stored. Files, SQL, a vector store: the storage choice is downstream of the lifecycle design, and it’s the easy part. Get the lifecycle wrong and no database saves you.
This is also why the model itself has no memory to speak of. Working memory is just what you chose to send this time.
2. The Build: Quick Byte Support
A customer support agent for a fictional food-delivery app. We’re building it stateless first, deliberately, so the gaps are obvious before we fill them.
The system prompt rules:
- The agent is a support representative for Quick Byte.
- For anything about order status, ETA or items, it must always call
get_order_status. - It must never answer order details from memory.
That last rule is the interesting one. Order status changes in real time. A model answering from what it saw earlier in the conversation will confidently tell a customer their food is on the way after it’s already been delivered. The rule exists to force a fresh lookup every time.
3. The Tool
@tool
def get_order_status(
customer_id: str,
order_id: Annotated[str, "The order reference, e.g. QB-5521"],
) -> dict:
"""Look up the live status of an order for a given customer."""
order_id = order_id.strip().upper()
...
.strip()and.upper()meanqb-5521,QB-5521andQB-5521all resolve. Users are not careful typists.Annotatedisn’t decoration — the description lands in the schema the model reads when deciding how to call the tool.- Success returns the order as JSON through a shared
ok()helper. - Failure goes through
fail(), which marks the result as an error so the model knows the lookup failed rather than treating the error text as order data.
4. Project Structure
common.py holds shared logic and the mock data:
ORDERS = {
"101": {"order_id": "QB-5521",
"status": "Out for delivery",
"items": "2 veg biryani"},
}
A build_tools() function returns the full list of tools in one place. Adding a tool means appending to one array, not hunting through files. Small discipline now, large payoff at twenty tools.
Environment: CLAUDE_CODE_DISABLE_AUTO_MEMORY controls the SDK’s automatic memory handling. We’re setting it now so the next session’s memory work is explicit rather than fighting something invisible.
One practical note: the first run failed on an invalid API key. Check your .env before assuming the code is wrong — the error message isn’t always obvious about it.
5. Where This Goes
Next session adds the chat loop, then working memory — and from there, the move from stateless to genuinely stateful.
✅ Action Items
- Build the same agent in a different domain — bus booking, for instance. Same structure, different tools. That’s the fastest way to make the pattern yours.
- Get the stateless version running end to end, including the failure path for an unknown order.
- Be ready to explain the five memory types and when you’d reach for each.
⏭️ Next up: the chat loop, working memory, and making this agent remember.
