Lesson 4: Module 04: State and Memory
Give your machines the ability to remember, within a session and across sessions.
Learning Objectives
- Define machine state (session-scoped data that persists across steps) with
stateblock - Read and update state from compute steps
- Use
remembersteps for persistent knowledge retrieval (RAG, Retrieval-Augmented Generation, which means feeding real documents to the AI so it answers based on facts, not guesses) - Know when to use state vs memory vs step outputs
Complexity Ladder: Applies to all levels; state and memory enhance machines at any complexity.
The Concept: Giving Machines Memory
Think of state as a notepad the machine carries during a single conversation — it can write things down and read them back. Memory is like a filing cabinet — information stored there survives after the conversation ends and can be retrieved in future sessions.
So far, our machines have been stateless — data flows from inputs through steps to outputs, then disappears. That works for single-shot tasks, but agents often need to:
- Track progress within a multi-turn interaction (state)
- Remember facts across separate sessions (memory)
- Ground responses in retrieved knowledge (RAG)
Step Outputs State Memory (between steps) (within a run) (across runs)┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│ steps.a.field │ │ state.counter │ │ remember step ││ │ │ │ │ ││ Flows A -> B │ │ Persists across │ │ Survives between ││ One direction │ │ steps in a run │ │ separate runs ││ Simplest option │ │ Session-scoped │ │ Permanent storage││ │ │ │ │ ││ Use for: passing │ │ Use for: counters│ │ Use for: knowledge││ data between │ │ conversation │ │ base, cache, ││ adjacent steps │ │ history, flags │ │ user preferences │└──────────────────┘ └──────────────────┘ └──────────────────┘Mashin provides two mechanisms: state for session-scoped data and memory for persistent knowledge.
Start With Koda
Koda requires a free account. Sign in or create an account to use Koda exercises throughout this course. If you’re not signed in yet, read on; the exercises will be here when you’re ready.
Before diving into the syntax, try building a stateful machine with Koda:
“Build a counter machine that remembers how many times it’s been called. Each time it runs, it should increment its count, remember the current message, and respond with both the count and the previous message.”
Check that Koda uses a state block with fields for the count and last message. Study the structure; the rest of this module explains each piece in detail.
State: Session-Scoped Data
State persists across steps within a machine run and across multiple invocations of the same machine instance. Define it under implements:
implements state messages as list, default: [] // A list of message maps, starts empty iteration as number, default: 0 // A counter, starts at zero user_preference as string // Optional stringEach field has a name, type, and optional default value. State is available to all steps via state.<field>.
Reading State
In compute steps:
compute check_count {current_count: state.iteration, total_messages: length(state.messages)}In ask step prompts:
ask reason_with_history, using: "anthropic:claude-sonnet-4" with task "Previous messages: ${state.messages}\nCurrent iteration: ${state.iteration}\n\nRespond to: ${input.message}"Updating State
A compute step can update state by including state fields in its output:
compute increment { state: { iteration: state.iteration + 1, messages: state.messages ++ [{role: "user", content: input.message}] } }The state key in the return value tells mashin to update those specific state fields. Fields not mentioned keep their current values.
Building It: Conversation Counter
A simple machine that tracks how many times it’s been called and remembers the last message:
machine conversation_tracker "Conversation Tracker"
accepts message as string, is required
responds with response as string call_count as number
implements state call_count as number, default: 0 last_message as string
// Pure computation step, updates state compute update_count { state: { call_count: state.call_count + 1, last_message: input.message }, previous_message: state.last_message, current_count: state.call_count + 1 }
// LLM step, generates a response ask respond, using: "anthropic:claude-haiku-4" with task "The user said: ${input.message}\nThis is call number ${steps.update_count.current_count}.\nTheir previous message was: ${steps.update_count.previous_message}.\n\nAcknowledge their message and mention the call count." returns response as string, is requiredMemory: Persistent Knowledge
While state tracks session data, remember steps store and retrieve knowledge that persists across sessions and machines. There are three memory types:
| Memory Type | Purpose | Example |
|---|---|---|
| Semantic (search by meaning, finds similar content even with different wording) | Find content by meaning | ”What do we know about shipping?” |
| Structured (key-value lookup, exact match by a known key) | Key-value lookup | Cache, user preferences |
| Episodic (event timeline, a chronological log of what happened) | Event timeline | Interaction logs, activity history |
Semantic Memory (RAG)
The most powerful memory pattern: store documents, retrieve by semantic similarity (matching meaning rather than exact words), and ground LLM responses in real data.
machine knowledge_qa "Knowledge QA"
accepts question as string, is required
responds with answer as string citations as list grounded as boolean
implements // Step 1: Retrieve relevant context from the knowledge base recall retrieve collection: "knowledge_base" query: input.question limit: 5
// Step 2: Generate grounded answer using retrieved context ask answer, using: "anthropic:claude-sonnet-4" with role "You answer questions based ONLY on the provided context. If the context doesn't contain the answer, say 'I don't have that information.' Always cite sources. NEVER make up information not in the context." with task "Context:\n${steps.retrieve.results}\n\nQuestion: ${input.question}" returns answer as string, is required citations as list grounded as booleanThis is the RAG pattern. It reduces hallucinations by grounding the LLM’s response in retrieved documents rather than relying on training data alone.
Storing to Memory
remember store_fact collection: "knowledge_base" content: input.document metadata: {source: input.source}Structured Memory (Key-Value)
For caching and preferences, exact lookups by key, not similarity search:
// Write a value to structured memoryremember cache_result key: "user_prefs:${input.user_id}" value: {theme: "dark", language: "en"}
// Read a value from structured memoryrecall get_prefs key: "user_prefs:${input.user_id}"When to Use What
| Need | Use | Why |
|---|---|---|
| Track iteration count in a loop | state | Session-scoped, ephemeral |
| Store conversation messages | state | Scoped to this machine run |
| Cache an API response | :remember (structured) | Persist across runs, TTL support |
| Search knowledge base | :remember (semantic) | Similarity search, RAG grounding |
| Log user interactions | :remember (episodic) | Timeline, audit trail |
| Pass data between steps | Step outputs | steps.name.field, simplest option |
Rule of thumb: If data only matters during this run, use state. If data needs to survive between runs, use memory. If data only needs to flow from one step to the next, just use step outputs.
Key Syntax
// State definition (under implements)state name as type, default: value items as list, default: []
// State access in expressionsstate.field
// State access in ask prompts${state.field}
// Memory storeremember name collection: "name" content: input.document
// Memory retrieverecall name collection: "name" query: input.question limit: 5Common Mistakes
-
Using state when step outputs suffice. If data just flows from Step A to Step B, use
steps.a.fieldin Step B. Don’t store it in state just to read it back. -
Forgetting default values. State fields need defaults. Without them, you’ll get
nilon first access, which may crash downstream steps. -
Storing everything in semantic memory. Semantic search (search by meaning) is powerful but not free. Use structured memory (key-value lookup) for exact lookups (user preferences, cache) and semantic memory only when you need similarity search.
What’s Next
In Module 05, you’ll learn how to control execution flow: chaining steps, branching conditionally, and building loops.