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:

"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 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. 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:

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

OperationBehavior
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.
FieldTypeMeaning
idID!Global identifier for the node(id) query.
memoryIdUUID!Unique identifier of the record.
created, modifiedTimestamp!When the record was first written, and the time of its last change.
versionInt!The record's current version. Previous versions are in history.
actionIdUUID!The agent the record belongs to.
summaryString!One line the agent can act on, at most 512 bytes.
contentString!The evidence and specifics behind the summary, at most 16 KiB; empty when the summary says it all.
metadataValue!Metadata the agent attached to the fact.
statusActionAgentMemoryStatus!ACTIVE, DELETED, or SUPERSEDED.
scopeActionAgentMemoryScope!SESSION or AGENT: which sessions recall it.
kindString!One of the agent's memory kinds.
sourceActionAgentMemorySource!EXTRACTION, TOOL, or OPERATOR: what wrote it.
pinnedBoolean!Whether every turn lists its summary.
labels[ActionAgentMemoryLabel!]!Its label values, each a number and a value, in number order.
replacesUUIDThe record it replaced, if any.
replacedByUUIDThe record that replaced it, when it is SUPERSEDED.
previewBoolean!Whether a turn in preview mode wrote it. Live turns never recall a preview record.
executionIdUUID!The execution whose turn wrote the record. execution reads it, subject to your read permissions.
invocationNumberInt!The invocation that wrote the record.
actionVersionInt!The agent version that wrote the record.
sessionIdStringThe session whose turn wrote the record. Null for an agent without sessions.
modifiedByString!Principal that made the last change: the writing agent's principal, or whoever deleted the record.
actionAction!The agent at actionVersion.
sessionActionSessionThe 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.

{ "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 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. Its embedding is recorded in the transcript as usage of purpose embed, which no limit counts.
  • Replays. A replay of a turn searches the history its original turn saw: the session's turns before it.
  • Preview mode. What a turn in preview mode 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.

Previous
Sessions