Toolnexus.Agents.Home.memory_tool
Elixir · package toolnexus · SPEC §7E · elixir/lib/toolnexus/agents/home.ex
@spec memory_tool(String.t()) :: Toolnexus.Tool.t()def memory_tool(dir)
# input_schema: {action: "add" | "replace" | "remove", target?: "self" | "user", text, with?}# action=add → appends "- <text>" to the target file# action=replace → swaps an existing `text` substring for `with` (isError if absent)# action=remove → deletes an existing `text` substring (isError if absent)# target=self (default) → MEMORY.md; target=user → USER.mdOne tool, three actions, over two files under dir: MEMORY.md (the agent’s own durable notes)
and USER.md (its model of the user). Every write lands on disk immediately; the current
session’s prompt is not touched — the frozen soul snapshot picks it up at the start of the
next session. A replace/remove whose target substring is not found in the file is a loud
is_error: true, never a silent no-op.
When to use it
Section titled “When to use it”- You’re building a persona that should remember things — this is the builtin
Toolnexus.Agents.Home.from_dir/2wires in by default; callmemory_tool/1directly only when you want it standalone (a custom agent definition, a test, a tool list you’re assembling by hand). - You want the model’s mental model of the user tracked separately from its own notes —
target: "user"writesUSER.mdinstead ofMEMORY.md.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — add, executed directly
Section titled “1. The smallest useful call — add, executed directly”alias Toolnexus.{Context, ToolResult}alias Toolnexus.Agents.Home
dir = Path.join(System.tmp_dir!(), "toolnexus_doc_memory_tool_1_#{System.unique_integer([:positive])}")File.rm_rf!(dir)File.mkdir_p!(dir)
tool = Home.memory_tool(dir)true = tool.name == "memory"
result = tool.execute.(%{"action" => "add", "text" => "prefers dark mode"}, %Context{})
%ToolResult{is_error: false} = resulttrue = String.contains?(result.output, "MEMORY.md")true = File.read!(Path.join(dir, "MEMORY.md")) == "- prefers dark mode\n"
IO.puts("ok: #{result.output}")2. The realistic case — replace and remove, and the loud miss
Section titled “2. The realistic case — replace and remove, and the loud miss”alias Toolnexus.{Context, ToolResult}alias Toolnexus.Agents.Home
dir = Path.join(System.tmp_dir!(), "toolnexus_doc_memory_tool_2_#{System.unique_integer([:positive])}")File.rm_rf!(dir)File.mkdir_p!(dir)File.write!(Path.join(dir, "MEMORY.md"), "- prefers dark mode\n- likes verbose logs\n")
tool = Home.memory_tool(dir)
replaced = tool.execute.(%{"action" => "replace", "text" => "verbose logs", "with" => "terse logs"}, %Context{})false = replaced.is_errortrue = File.read!(Path.join(dir, "MEMORY.md")) =~ "terse logs"
removed = tool.execute.(%{"action" => "remove", "text" => "- prefers dark mode\n"}, %Context{})false = removed.is_errorfalse = File.read!(Path.join(dir, "MEMORY.md")) =~ "dark mode"
# a replace/remove target that isn't present is a loud error, not a silent no-opmiss = tool.execute.(%{"action" => "replace", "text" => "nonexistent", "with" => "x"}, %Context{})true = miss.is_errortrue = String.contains?(miss.output, "not found")
IO.puts("ok: replace + remove applied; miss reported as isError")3. The full surface — target: "user" writes USER.md instead
Section titled “3. The full surface — target: "user" writes USER.md instead”alias Toolnexus.{Context, ToolResult}alias Toolnexus.Agents.Home
dir = Path.join(System.tmp_dir!(), "toolnexus_doc_memory_tool_3_#{System.unique_integer([:positive])}")File.rm_rf!(dir)File.mkdir_p!(dir)
tool = Home.memory_tool(dir)
self_note = tool.execute.(%{"action" => "add", "text" => "checked the invoice twice"}, %Context{})user_note = tool.execute.(%{"action" => "add", "target" => "user", "text" => "works in UTC+5:30"}, %Context{})
false = self_note.is_errorfalse = user_note.is_errortrue = String.contains?(self_note.output, "MEMORY.md")true = String.contains?(user_note.output, "USER.md")
true = File.read!(Path.join(dir, "MEMORY.md")) =~ "checked the invoice twice"true = File.read!(Path.join(dir, "USER.md")) =~ "works in UTC+5:30"# writes are on disk only — they say so, so a caller never assumes the live prompt changedtrue = String.contains?(user_note.output, "loads next session")
IO.puts("ok: self note → MEMORY.md, user note → USER.md")Input schema
Section titled “Input schema”| Field | Type | Required | What it is |
|---|---|---|---|
action |
"add" | "replace" | "remove" |
yes | The operation. |
target |
"self" | "user" |
no (default "self") |
self → MEMORY.md, user → USER.md. |
text |
String.t() |
yes | For add: the entry. For replace/remove: the existing substring. |
with |
String.t() |
for replace |
The replacement text. |
See also
Section titled “See also”Toolnexus.Agents.Home.compose_soul— Build a persona’s system prompt from its home directory: identity, memory, skills.Toolnexus.Agents.Home.from_dir— Point at an agent home directory and get a configured agent back.