# Memory and History

Let an agent keep durable memories and search its session's earlier turns.

## Agent memory

An agent with long-term memory enabled (`harness.memory.enabled` in its configuration) keeps durable facts it learns as memory records. A record belongs to one agent, and its `scope` decides which sessions recall it:

- `SESSION`: only the session that wrote it. What the agent learns about one session's subject, such as one account, never reaches another session.
- `AGENT`: every session of the agent that reads agent scope, so what the agent learns in one session informs the next.

`harness.memory.scope` sets which scopes an agent reads and writes, each a list of `session` and `agent`:

```json
"memory": {"enabled": true, "scope": {"read": ["session", "agent"], "write": ["session", "agent"]}}
```

`read` is what recall and search see, and `write` is what the agent may write: a record has session scope when `write` includes `session`, unless the agent chooses `agent` for it, which it may only when `write` includes `agent`. Both default to `["session"]`. Allow `agent` writes only when facts from one session's data may inform every other session. An agent without a session has no session scope, so it must set both to `["agent"]`.

When a turn ends, the agent reviews the conversation for what is worth remembering, compares each candidate with the most similar memories it already has, and adds it, has it replace one it corrects, deletes one it shows wrong, or skips it, so restated facts do not pile up. `harness.memory.extraction` sets how: `prompt` replaces the default instructions for what to keep, and `every` is `"turn"`, the default, or `{"messages": N}` to review only once at least N messages were added since the last review. These reviews are model calls with purpose `memory`. They write agent scope only when `scope.write` includes `agent`.

It is written in the transaction of the turn that learned it, so a turn that rolls back leaves no record. Before each model call the agent recalls the records that best match its current work, and it can search its memory with the `search_memory` tool. Search combines meaning, keywords, and recency: records are found by the similarity of their text to the query, and by the words they share with it, and newer records win ties. `harness.memory.search` tunes it: `mode` is `hybrid`, the default, `lexical`, or `vector`; `max_results` is how many records a recall returns, 5 by default; and `min_score`, from -1 to 1, is the least similarity a record needs to be found by meaning alone. Each search's embedding is recorded in the transcript as usage of purpose `embed`, which no limit counts. Recalled memory reaches the model marked as memory: it is data, never instructions. An agent's memory is its own record, as its executions are: neither its `policies` nor the policies of the client or execution that starts or calls it need to grant `financial.AgentMemory` for it to recall, search, save, or delete its memories. Only the agent's memory works this way: a tool the agent calls reaches `financial.AgentMemory` only as the [policy chain](https://www.twisp.com/docs/reference/actions/actions.md#policy-chain) allows. A search the model makes is an execution of the native `search_memory` tool whose output lists the records it returned. A recall that finds records is recorded the same way, with `recall: true` in its input. A recall that finds nothing is not recorded, and a recall that finds the same record versions an earlier recall already put before the same conversation, the agent's or a helper's, is not recorded again. Each recorded recall and search whose records reached the model is a `RETRIEVED` [output use](https://www.twisp.com/docs/reference/actions/skills.md#output-uses). An agent never sees another agent's memory.

Each record has a one-line `summary`, enough to act on without reading further, and an optional `content` with the evidence and specifics. It also has a `kind`, one of the kinds the agent declares in `harness.memory.kinds` (by default `guidance`, `fact`, `event`, and `reference`), and may carry label values. Labels are declared by name, each with a fixed `number` from 5 to 18 and a description the agent reads:

```json
"labels": {"customer_id": {"number": 5, "description": "The customer the memory is about."}}
```

A label is an exact value, such as an ID, that searches can filter on. Once a version declares a label, later versions keep it with the same number; they may add labels.

`search_memory(query, kind, labels, ids, limit, after)` searches memory: with a `query` it ranks, and without one it lists records newest first, a page at a time, with the same filters. `ids` reads records by ID. With `harness.memory.tools`, a list of the tool names it offers, such as `["add_memory", "delete_memory"]`, the agent can also save, read, and delete memories:

- `add_memory(summary, content, kind, labels, scope, pinned, replaces)` saves a record as it learns something. `scope` is offered only when `scope.write` includes `agent`. `replaces` names a record the new one corrects: that record becomes `SUPERSEDED`, and its `replacedBy` names the new one.
- `read_memory(id)` reads one record, whatever its status.
- `delete_memory(id)` deletes one record. It reaches only the scopes the agent writes.

A record written by a turn in [preview mode](https://www.twisp.com/docs/reference/actions/executions.md#preview-calls) has `preview: true`. Live turns never recall, search, read, or delete a preview record. Turns in preview mode recall and search every record, except the records a preview record names in `replaces`: a turn in preview mode never changes a record a live turn wrote, so it replaces or deletes one with a preview record that names it. `Action.memories` lists both kinds, and each such deletion, a preview record of status `DELETED`.

Each call is an execution of the native tool of its name. An `add_memory` or `delete_memory` call's output lists the records it wrote or deleted, in `memories`; a `read_memory` call's lists the record it read, in `sources`. A call the agent's configuration does not allow, such as a pin over budget, fails with a message the agent reads, and changes nothing. Only the agent itself changes memory: a [subagent](https://www.twisp.com/docs/reference/actions/agents.md#configuring-agent-tools) helper and a program run by `programmatic_tool_caller` can search memory but never write it.

With `harness.memory.pinned: {"max_tokens": 2000}`, the agent may pin a record. Every turn's instructions list the pinned records it reads, one line each: the summary and the record's ID. Each turn's transcript records the record versions it listed, in a `memory.pinned` event. A pin that would take the list over `max_tokens`, counting about four characters per token, is refused, and the agent is told to consolidate: to replace several pins with one. Without `pinned`, nothing can be pinned.

Each record names the execution, invocation, agent version, and session that wrote it, and reads each of them. Deleting a record removes it from recall and from the agent's `memories`; the record and its versions remain readable through `agentMemory` and its `history`. Reading and deleting records through the API needs these grants, whoever asks, including whoever starts or calls the agent: its use of its own memory grants nothing to anyone else. To list and read them, grant `SELECT` on `financial.AgentMemory.indexes.*` and on `financial.AgentMemory.document`; to delete them, grant both `SELECT` and `UPDATE`.

| Operation                                                      | Behavior                                                                                                                                                                                                                        |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Action.memories(sort: SortOrder, first: Int!, after: String)` | The agent's memory records ordered by creation time, newest first unless `sort: ASC`. A record appears here shortly after the turn that wrote it commits. Deleted and superseded records are omitted, except preview deletions. |
| `agentMemory(actionId: UUID!, id: UUID!, version: Int)`        | One of the agent's records, deleted or not, or an exact retained version. Null if none exists. `Action.memory(id, version)` reads the same record from its agent.                                                               |
| `deleteAgentMemory(actionId: UUID!, id: UUID!)`                | Mutation. Deletes one of the agent's records: the agent no longer recalls it. Deleting a deleted record returns it unchanged.                                                                                                   |

| Field                   | Type                           | Meaning                                                                                            |
| ----------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------- |
| `id`                    | `ID!`                          | Global identifier for the `node(id)` query.                                                        |
| `memoryId`              | `UUID!`                        | Unique identifier of the record.                                                                   |
| `created`, `modified`   | `Timestamp!`                   | When the record was first written, and the time of its last change.                                |
| `version`               | `Int!`                         | The record's current version. Previous versions are in `history`.                                  |
| `actionId`              | `UUID!`                        | The agent the record belongs to.                                                                   |
| `summary`               | `String!`                      | One line the agent can act on, at most 512 bytes.                                                  |
| `content`               | `String!`                      | The evidence and specifics behind the summary, at most 16 KiB; empty when the summary says it all. |
| `metadata`              | `Value!`                       | Metadata the agent attached to the fact.                                                           |
| `status`                | `ActionAgentMemoryStatus!`     | `ACTIVE`, `DELETED`, or `SUPERSEDED`.                                                              |
| `scope`                 | `ActionAgentMemoryScope!`      | `SESSION` or `AGENT`: which sessions recall it.                                                    |
| `kind`                  | `String!`                      | One of the agent's memory kinds.                                                                   |
| `source`                | `ActionAgentMemorySource!`     | `EXTRACTION`, `TOOL`, or `OPERATOR`: what wrote it.                                                |
| `pinned`                | `Boolean!`                     | Whether every turn lists its summary.                                                              |
| `labels`                | `[ActionAgentMemoryLabel!]!`   | Its label values, each a `number` and a `value`, in number order.                                  |
| `replaces`              | `UUID`                         | The record it replaced, if any.                                                                    |
| `replacedBy`            | `UUID`                         | The record that replaced it, when it is `SUPERSEDED`.                                              |
| `preview`               | `Boolean!`                     | Whether a turn in preview mode wrote it. Live turns never recall a preview record.                 |
| `executionId`           | `UUID!`                        | The execution whose turn wrote the record. `execution` reads it, subject to your read permissions. |
| `invocationNumber`      | `Int!`                         | The invocation that wrote the record.                                                              |
| `actionVersion`         | `Int!`                         | The agent version that wrote the record.                                                           |
| `sessionId`             | `String`                       | The session whose turn wrote the record. Null for an agent without sessions.                       |
| `modifiedBy`            | `String!`                      | Principal that made the last change: the writing agent's principal, or whoever deleted the record. |
| `action`                | `Action!`                      | The agent at `actionVersion`.                                                                      |
| `session`               | `ActionSession`                | The session identified by `sessionId`. Null for an agent without sessions.                         |
| `history(first, after)` | `ActionAgentMemoryConnection!` | Every version of the record, deleted or not, newest first.                                         |

## Session history

A session agent's context is bounded: as it fills, older messages are summarized, and earlier turns' tool results are cleared. With `harness.history`, what earlier turns said stays searchable, so the agent can find a detail a summary left out instead of guessing or asking again.

```json
{ "harness": { "history": { "index": "messages_and_tools" } } }
```

- **What is kept.** With `messages`, each turn's message, as the agent received it, and the agent's replies, with the tools each reply called. With `messages_and_tools`, also the results of the agent's tools, and their errors. Skill loads and the agent's own retrievals are never kept. Only what a turn commits is kept: when a turn fails and is retried, what the failed attempt said stays in its [transcript](https://www.twisp.com/docs/reference/actions/agents.md#transcripts) but is never searchable.
- **Searching.** The agent has `search_history(query, ids, limit)`. A search ranks the session's kept events from turns before the current one, combining meaning, keywords, and recency, as a memory search does. Each result has the event's `id`, its `turn`, and its text, after a line saying who it is from, such as `turn 12, user`. A long event is kept in parts, and a result is one part, with `part` and `parts`; pass its `id` in `ids` to read the event whole. A result also names the execution and sequence of the event, and for a tool's result, the call.
- **Recording.** Each search is an execution of the native `search_history` tool whose output lists the events it returned, and a `RETRIEVED` [output use](https://www.twisp.com/docs/reference/actions/skills.md#output-uses). Its embedding is recorded in the transcript as usage of purpose `embed`, which no limit counts.
- **Replays.** A [replay](https://www.twisp.com/docs/reference/actions/executions.md#replays) of a turn searches the history its original turn saw: the session's turns before it.
- **Preview mode.** What a turn in [preview mode](https://www.twisp.com/docs/reference/actions/executions.md#preview-calls) keeps is searched only by turns in preview mode, never by a live turn.

The agent is told to search its history before guessing at, or asking again for, something an earlier turn established. History stays with the session: an agent never searches another session's history, and an agent without sessions has none.
