Skip to content

memoryTool

JavaScript · package toolnexus · SPEC §7E · js/src/agents/home.ts

function memoryTool(dir: string): Tool

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

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.

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.

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)

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" selfMEMORY.md (default). userUSER.md.
text string Required. For add: the entry. For replace/remove: the existing substring to match.
with string For replace: the replacement text.
  • 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 — What memoryTool is built with.