Skip to main content

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

ParameterTypeRequiredDescription
historystringYesThe session trace as a JSON string (see Trace Formats below)
session_idstringNoIdentifier for the trace; auto-generated if omitted
titlestringNoA title for the session
formatstringNo"vercel", "hyperdoc", or "openclaw". Auto-detected if omitted
extractstring[]NoMemory types to extract. Defaults to ["procedure"]
metadataobjectNoCustom key-value pairs for filtering
datestring (ISO 8601)NoTimestamp 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.

Trace formats

Hyperspell accepts traces in three formats. The format is auto-detected, but you can specify it explicitly with the format parameter.
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."}
]

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.

What Gets Extracted

Not every trace produces procedures. The extraction is selective:
ExtractedSkipped
Completed multi-step tasksFailed or errored attempts
Tasks with tool calls that succeededTrivial actions (greetings, single lookups)
Each distinct task in a traceTasks 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.