> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hyperspell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Procedural Memory

> Teach your agent how to perform tasks by learning from past sessions

## 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.

<Note>
  #### 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.
</Note>

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.

<CodeGroup>
  ```python Python theme={null}
  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"],
  )
  ```

  ```typescript TypeScript theme={null}
  import Hyperspell from 'hyperspell';

  const client = new Hyperspell({ apiKey: 'API_KEY', userID: 'YOUR_USER_ID' });

  const response = await client.traces.add({
      history: '[{"role": "user", "content": "Find the Q4 report and email it to the team"}, ...]',
      title: 'Email file to team',
      extract: ['procedure'],
  });
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.hyperspell.com/trace/add" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "history": "[{\"role\": \"user\", \"content\": \"Find the Q4 report and email it to the team\"}, ...]",
      "title": "Email file to team",
      "extract": ["procedure"]
    }'
  ```
</CodeGroup>

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](#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

```json theme={null}
{
  "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.

<Tabs>
  <Tab title="Vercel AI SDK">
    If you're using the [Vercel AI SDK](https://sdk.vercel.ai), pass the message history directly:

    ```json theme={null}
    [
      {"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."}
    ]
    ```
  </Tab>

  <Tab title="OpenClaw JSONL">
    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..."}}
    ```
  </Tab>

  <Tab title="Hyperdoc (native)">
    Hyperspell's native format — a JSON array of typed steps:

    ```json theme={null}
    [
      {"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."}
    ]
    ```
  </Tab>
</Tabs>

## 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.

```python theme={null}
# 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.

```python theme={null}
# 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.

<Note>
  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.
</Note>

## Querying Procedures

To retrieve procedural memories, use the standard search endpoint with the `memory_types` filter set to `["procedure"]`:

<CodeGroup>
  ```python Python theme={null}
  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
  ```

  ```typescript TypeScript theme={null}
  const response = await client.memories.search({
      query: 'how do I send an email with an attachment?',
      sources: ['vault'],
      options: {
          memory_types: ['procedure']
      }
  });

  for (const doc of response.documents) {
      console.log(doc.title);  // "Find a file and email it to a team"
      console.log(doc.text);   // Step-by-step instructions
  }
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.hyperspell.com/memories/query" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "how do I send an email with an attachment?",
      "sources": ["vault"],
      "options": {
        "memory_types": ["procedure"]
      }
    }'
  ```
</CodeGroup>

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:

```python theme={null}
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:

```python theme={null}
# 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:

| 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](mailto: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.
