memoryTool
JavaScript · package toolnexus · SPEC §7E · js/src/agents/home.ts
function memoryTool(dir: string): ToolBuilds the memory tool — one tool, three actions (add, replace, remove) over two files in
dir: MEMORY.md (target: "self", the default — the agent’s own durable notes) and USER.md
(target: "user" — its model of the user). It is source: "native" under the hood (built with
defineTool), and it is not one of the default
builtins — it exists only where a persona wires it in, either
via fromDir (on by default) or by adding
memoryTool(dir) to uses.tools yourself.
When to use it
Section titled “When to use it”Wire memoryTool in whenever a long-lived agent needs to remember things across sessions without
you writing the file I/O by hand — “the user prefers metric units,” “last week’s incident was a
DNS misconfiguration,” anything worth carrying forward. Every action writes straight to disk; there
is no in-memory-only mode.
Why this and not the alternative
Section titled “Why this and not the alternative”The tool deliberately does not touch the running session — the description string handed to
the model says so, and the mechanics back it up: a write lands on disk immediately, but the
composed soul is a frozen snapshot for the whole run (§7E), so the change is visible only from
the next session onward. That is what keeps a long-lived persona cache-stable: the system
prompt never mutates mid-conversation just because a tool call happened.
Examples
Section titled “Examples”1. add — append an entry to MEMORY.md
Section titled “1. add — append an entry to MEMORY.md”import assert from "node:assert"import fs from "node:fs"import os from "node:os"import path from "node:path"import { agents } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))const memory = agents.memoryTool(dir)
assert.equal(memory.name, "memory")assert.equal(memory.source, "native")assert.deepEqual(memory.inputSchema.properties?.action.enum, ["add", "replace", "remove"])
const res = await memory.execute({ action: "add", text: "prefers metric units" })
assert.equal(res.isError, false)assert.match(fs.readFileSync(path.join(dir, "MEMORY.md"), "utf8"), /prefers metric units/)
console.log("ok:", res.output)2. replace and remove — a missing substring is a loud error, never a silent no-op
Section titled “2. replace and remove — a missing substring is a loud error, never a silent no-op”import assert from "node:assert"import fs from "node:fs"import os from "node:os"import path from "node:path"import { agents } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))const memory = agents.memoryTool(dir)
await memory.execute({ action: "add", text: "prefers metric units" })
const replaced = await memory.execute({ action: "replace", text: "prefers metric units", with: "prefers SI units" })assert.equal(replaced.isError, false)assert.match(fs.readFileSync(path.join(dir, "MEMORY.md"), "utf8"), /prefers SI units/)
// The substring is no longer there — replace/remove on a miss is a LOUD isError, not a no-op.const missed = await memory.execute({ action: "remove", text: "prefers metric units" })assert.equal(missed.isError, true)assert.equal(missed.output, "not found: prefers metric units")
console.log("ok:", replaced.output, "|", missed.output)3. target: "user" writes USER.md instead of MEMORY.md
Section titled “3. target: "user" writes USER.md instead of MEMORY.md”import assert from "node:assert"import fs from "node:fs"import os from "node:os"import path from "node:path"import { agents } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))const memory = agents.memoryTool(dir)
const self = await memory.execute({ action: "add", text: "shipped the v2 migration" })const user = await memory.execute({ action: "add", text: "likes terse status updates", target: "user" })
assert.match(fs.readFileSync(path.join(dir, "MEMORY.md"), "utf8"), /shipped the v2 migration/)assert.match(fs.readFileSync(path.join(dir, "USER.md"), "utf8"), /likes terse status updates/)// Both report the SAME session-boundary contract — the write lands now, loads next session.assert.match(self.output, /loads next session/)assert.match(user.output, /loads next session/)
console.log("ok:", self.output, "|", user.output)Options
Section titled “Options”The tool’s own inputSchema — what the model fills in on a call:
| Field | Type | What it does |
|---|---|---|
action |
"add" | "replace" | "remove" |
Required. Which mutation to apply. |
target |
"self" | "user" |
self → MEMORY.md (default). user → USER.md. |
text |
string |
Required. For add: the entry. For replace/remove: the existing substring to match. |
with |
string |
For replace: the replacement text. |
See also
Section titled “See also”composeSoul— Build a persona’s system prompt from its home directory: identity, memory, skills.fromDir— Point at an agent home directory and get a configured agent back; wires this tool by default.defineTool— WhatmemoryToolis built with.