What is Procedural Memory?
Procedural memory is “how-to” knowledge — the steps an agent followed to complete a task. When you send agent session traces to Hyperspell, we automatically extract reusable step-by-step procedures from them. Later, when your agent encounters a similar task, it can query for relevant procedures and follow the same steps instead of figuring things out from scratch.
Think of it as muscle memory for your agent. A human customer support rep doesn’t re-learn how to process a refund every time — they remember the steps. Procedural memory gives your agent the same capability.
Why not just use regular memories?
Regular memories store what happened — facts, documents, conversations. Procedural memories store how to do things — the sequence of tool calls, decision points, and arguments needed to accomplish a task. When your agent asks “how do I send an email with an attachment?”, you want a step-by-step guide, not a transcript.
Without procedural memory, agents repeat the same trial-and-error on tasks they’ve already solved. This leads to:
- Wasted tokens re-discovering workflows that worked before
- Inconsistent behavior across sessions — the agent might solve the same task differently each time
- Slower responses because the agent reasons from scratch instead of following a known procedure
With procedural memory, agents get better over time. Every successful task becomes a reusable template.
Adding Traces
To build procedural memory, send your agent’s session traces to Hyperspell using the /trace/add endpoint. Hyperspell will analyze the trace, identify completed multi-step tasks, and extract generalized procedures from them.
from hyperspell import Hyperspell
client = Hyperspell(api_key="API_KEY", user_id="YOUR_USER_ID")
response = client.traces.add(
history='[{"role": "user", "content": "Find the Q4 report and email it to the team"}, ...]',
title="Email file to team",
extract=["procedure"],
)
The extract parameter defaults to ["procedure"], so you can omit it if procedure extraction is all you need.
Request parameters
| Parameter | Type | Required | Description |
|---|
history | string | Yes | The session trace as a JSON string (see Trace Formats below) |
session_id | string | No | Identifier for the trace; auto-generated if omitted |
title | string | No | A title for the session |
format | string | No | "vercel", "hyperdoc", or "openclaw". Auto-detected if omitted |
extract | string[] | No | Memory types to extract. Defaults to ["procedure"] |
metadata | object | No | Custom key-value pairs for filtering |
date | string (ISO 8601) | No | Timestamp of the session |
Response
{
"source": "trace",
"resource_id": "abc123",
"status": "pending"
}
Procedure extraction happens asynchronously. The trace is stored immediately, and procedures are extracted and indexed in the background.
Hyperspell accepts traces in three formats. The format is auto-detected, but you can specify it explicitly with the format parameter.
Vercel AI SDK
OpenClaw JSONL
Hyperdoc (native)
If you’re using the Vercel AI SDK, pass the message history directly:[
{"role": "user", "content": "Find the sales report and email it"},
{"role": "assistant", "content": "I'll search for the report...",
"toolCalls": [{"toolName": "search_files", "args": {"query": "sales report"}}]},
{"role": "tool", "toolResults": [{"toolName": "search_files", "result": [{"name": "Q4-Sales.pdf"}]}]},
{"role": "assistant", "content": "Found it. Sending the email now...",
"toolCalls": [{"toolName": "send_email", "args": {"to": "team@co.com", "attachments": ["Q4-Sales.pdf"]}}]},
{"role": "tool", "toolResults": [{"toolName": "send_email", "result": "sent"}]},
{"role": "assistant", "content": "Done! I emailed the Q4 sales report to the team."}
]
Newline-delimited JSON with session, message, and custom event types:{"type":"session","id":"sess_1","createdAt":"2025-01-15T10:00:00Z"}
{"type":"message","id":"msg_1","parentId":"sess_1","message":{"role":"user","content":"Find the sales report"}}
{"type":"message","id":"msg_2","parentId":"msg_1","message":{"role":"assistant","content":"Searching..."}}
Hyperspell’s native format — a JSON array of typed steps:[
{"type": "trace_message", "role": "user", "text": "Find the sales report and email it"},
{"type": "trace_tool_call", "tool": "search_files", "input": {"query": "sales report"}},
{"type": "trace_tool_result", "tool": "search_files", "output": [{"name": "Q4-Sales.pdf"}]},
{"type": "trace_message", "role": "assistant", "text": "Done! Sent the report to the team."}
]
When to Add Traces
There are two natural moments to send traces to Hyperspell:
At the end of a session
The simplest approach. When the user’s session ends (or after a timeout), send the full conversation history. This captures the complete context of what happened.
# After the agent session completes
client.traces.add(
history=json.dumps(session.messages),
session_id=session.id,
title=session.summary,
)
Best for: Short, task-focused sessions where the full trace is a reasonable size.
At context compaction
Many agent frameworks compact or summarize the conversation when it gets too long. This is a natural point to flush the trace to Hyperspell — you’re already processing the history, and the pre-compaction trace contains the richest detail.
# Inside your compaction hook
def on_compact(messages_being_compacted, session_id):
client.traces.add(
history=json.dumps(messages_being_compacted),
session_id=f"{session_id}-{compact_count}",
)
# Then proceed with your normal compaction logic
Best for: Long-running agent sessions where the context window fills up multiple times. Each compaction window becomes its own trace, and procedures are extracted from each segment independently.
You can send multiple traces for the same session. Each trace is processed independently, so procedures from different segments won’t be duplicated — the extractor only captures completed tasks within each trace.
Querying Procedures
To retrieve procedural memories, use the standard search endpoint with the memory_types filter set to ["procedure"]:
response = client.memories.search(
query="how do I send an email with an attachment?",
sources=["vault"],
options={
"memory_types": ["procedure"]
}
)
for doc in response.documents:
print(doc.title) # "Find a file and email it to a team"
print(doc.text) # Step-by-step instructions
Each procedure has:
- A title — a short imperative sentence describing the task (e.g., “Find a file and email it to a team”)
- A body — numbered step-by-step instructions with tool names, arguments, and decision points
Combining with regular memories
You can search both regular memories and procedures at once by passing multiple types:
response = client.memories.search(
query="quarterly report",
sources=["vault"],
options={
"memory_types": ["memory", "procedure"]
}
)
If you omit memory_types, only regular memories are returned — procedures require an explicit opt-in.
Using Procedures in Your Agent
The most common pattern is to query for relevant procedures at the start of a task and inject them into the agent’s system prompt:
# When the agent receives a new task
procedures = client.memories.search(
query=user_message,
sources=["vault"],
options={"memory_types": ["procedure"], "max_results": 3}
)
# Build context from procedures
if procedures.documents:
procedure_context = "\n\n".join(
f"## {doc.title}\n{doc.text}"
for doc in procedures.documents
)
system_prompt += f"""
You have done similar tasks before. Follow these procedures when applicable:
{procedure_context}
"""
This gives the agent a head start — instead of reasoning from scratch, it can follow proven steps and adapt them to the current request.
Not every trace produces procedures. The extraction is selective:
| Extracted | Skipped |
|---|
| Completed multi-step tasks | Failed or errored attempts |
| Tasks with tool calls that succeeded | Trivial actions (greetings, single lookups) |
| Each distinct task in a trace | Tasks that were discussed but not executed |
Procedures are also generalized — specific names, IDs, and file paths are replaced with descriptive placeholders so the procedure is reusable for similar future tasks.
Example: A trace where the agent found “Q4-2025-Sales.pdf” and emailed it to “sales-team@acme.com” produces a procedure titled “Find a file and email it to a team” with generic steps that work for any file and any recipient.