Agent memory
Let an Agent keep Markdown notes across sessions, wakes, and new versions.
{
"$schema": "https://docs.nylorun.com/schemas/agent-manifest/v8.json",
"manifestSchemaVersion": 8,
"name": "releases",
"harness": {
"instructions": ["Help the team ship releases."],
"memory": { "access": "read_write", "instructions": "Keep the team's release conventions." }
},
"runtime": { "kind": "none" }
}import { readFile } from "node:fs/promises";
await client.agentManifests.save(JSON.parse(await readFile("releases.json", "utf8")));
await client.agents.put("teams/web/releases", { manifest: "releases" });
// Seed it: the agent finds this note in its first session.
await client.agents.memories.put(
"teams/web/releases",
"conventions/release.md",
"---\ndescription: How the web team tags and ships a release\n---\n\nTag releases vX.Y.Z from main.\n",
);harness.memory in an agent manifest gives each
Agent made from it a memory: Markdown files under
/memories that the agent reads and edits, and that stay with the Agent
across its sessions, triggered turns and wakes, and its
moves to new versions.
Turn it on
Set memory wherever you write the harness section:
| Where | How |
|---|---|
| A built agent | client.agentManifests.save({ agent, runtime, memory: { access: "read_write" } }); plan() and agentManifest() take it too. |
| An agent manifest | memory: { … } in the frontmatter of AGENT.md, which npx nylorun apply saves. |
| A harness file (the deprecated three-file format) | "memory": { … } in harness.manifest.json, which npx nylorun apply saves. |
| The manifest document | harness.memory, as above, with client.agentManifests.save(document) or PUT /v1/agent-manifests/{name}. The SDK drops $schema; a direct PUT leaves it out. |
| Field | Meaning |
|---|---|
access | read_write (the default) or read_only: the agent views its memories but does not change them. |
instructions | What this agent should remember, at most 4,096 characters. They follow our harness's own memory rules. |
// releases is the Agent() from agents/releases/agent.ts.
await client.agentManifests.save({
agent: releases,
runtime: { kind: "none" },
memory: { access: "read_write", instructions: "Keep the team's release conventions." },
});A flow's harness section has no memory; the SDK refuses it for a flow.
Each turn reads harness.memory from the manifest version it runs, so a save
that adds it turns memory on at each session's next turn on an Agent that
tracks the manifest.
What the agent gets
Our harness gives the agent one memory tool, the commands of Anthropic's
memory tool: view, create, str_replace, insert, delete, and rename.
- Paths start with
/memories/, end in.md, and are at most 512 characters. Folders are the prefixes of memory paths. - An index opens each session: a line per memory, newest first, with the
descriptionfrom its YAML frontmatter when it has one, withinmemoryIndexLines(200) lines andmemoryIndexBytes(25 KiB). When some are left out, the agent views folders for the rest. A compaction refreshes it. - Instructions: our harness's memory rules, which tell the agent never to
keep secrets, then
harness.memory.instructions. - Access holds for every turn, whoever starts it: a person, an app, a
trigger, or a wake. With
read_only, and for an agent used as a tool, a write fails withmemory.forbidden; only the session's own agent writes.
Each call is a tool.completed with kind: "runtime": the Runtime serves it
in the turn's own record, so a replayed turn applies a write once. The
tool policy does not hold memory calls. A call that
changed something writes memory.updated in the session (effectId, and
changes, each { path, operation, version }) and agent.memory_updated on
the Agent's stream, with its sessionId and turnId.
Where it lives
Memories are the Agent's, kept in the Runtime with it:
| When | Memories |
|---|---|
| The Agent moves to another version | Kept. |
| The Agent is forked | Copied to the new Agent, each at version 1, without history. |
The Agent is deleted, or nylorun tenant reset --agents | Deleted. |
Every change is a version that names who made it: agent:<manifest>@<version>
with the session, turn, and trigger run for a turn, or the caller for an API
write. Versions keep the content each change left.
Manage it
const id = "teams/web/releases";
const { memories } = await client.agents.memories.list(id, { pathPrefix: "conventions" });
const memory = await client.agents.memories.get(id, "conventions/release.md");
await client.agents.memories.put(id, "conventions/release.md", `${memory.content}\nShip on Tuesdays.\n`, {
contentSha256: memory.contentSha256,
});
const { versions } = await client.agents.memories.versions(id, { path: "conventions/release.md" });| Call | CLI | Does |
|---|---|---|
client.agents.memories.list(id, { pathPrefix?, limit?, cursor? }) | agents memories ls <id> [--prefix <folder>] | Memories by path: path, bytes, contentSha256, description, version, updatedAt. |
client.agents.memories.get(id, path) | agents memories cat <id> <path> | One memory with its content. |
client.agents.memories.put(id, path, content, { contentSha256? }) | agents memories put <id> <path> <file|-> | Create or replace it whole: how an app seeds an Agent's memory. |
client.agents.memories.delete(id, path) | agents memories rm <id> <path> | Delete a memory, or every memory in a folder. |
client.agents.memories.versions(id, { path?, limit?, cursor? }) | agents memories versions <id> [--path <path>] | Every change, newest first: operation, actor, sessionId, turnId, triggerRunId, fromPath for a rename. |
client.agents.memories.version(id, versionId) | One version with the content it left (null for a delete). |
A path may be given as conventions/release.md or
/memories/conventions/release.md. put with contentSha256 writes only
while the memory still has that content, and "" writes only while no memory
has the path; otherwise it is 409 memory_conflict with the current memory.
Reading takes agents:read, agents:write, or sessions:own for an Agent the
caller reaches; writing takes agents:write. Request and response shapes are
on Write a memory and
List an Agent's memories.
In Studio, the Agent page's Memory tab lists each memory with its description, version, and who changed it last. A row opens its content and history, and Delete removes it.
Keep secrets out
Our harness tells the agent never to keep secrets, but nothing scans what it writes. Read an Agent's memory before you share it, fork it, or give another person's sessions access to it.
Limits
| Limit | Default | Crossing it |
|---|---|---|
memoryBytes | 100 KiB per memory | 413 on a put; memory.too_large for the agent's call. |
memoriesPerAgent | 10,000 | 409 limit_exceeded on a put; memory.limit for the agent's call. |
memoryIndexLines, memoryIndexBytes | 200 lines, 25 KiB | The index leaves out the oldest memories. |
They are Runtime limits, set per Tenant.