toolnexus.agents.home/memory-tool
Clojure (JVM) + cljgo · package net.clojars.muthuishere/toolnexus · SPEC §7E · clojure/src/toolnexus/agents/home.cljc
(memory-tool "./personas/ava");; => a Tool named "memory" — add it to a toolkit like any otherOne tool, three actions, over two files in the persona’s home:
| action | effect |
|---|---|
add |
append an entry |
replace |
swap an existing substring for with |
remove |
delete an existing substring |
target is self (MEMORY.md, the default) or user (USER.md, the agent’s model of you).
Not a default builtin. Unlike the §4A tools, memory exists only when you wire a home
directory — a read-only persona simply never gets it.
Two rules that surprise people
Section titled “Two rules that surprise people”A replace or remove whose substring is absent is a loud isError — never a silent no-op. An
agent that thinks it edited its memory and did not is worse than one that is told it failed.
When to use it
Section titled “When to use it”- A persona should remember across sessions — preferences, decisions, facts about you.
- You want the agent to curate its own notes, not just append forever:
replaceandremoveare what makeMEMORY.mdmaintainable. - You are using
:flush-to-memoryoncompactor— that reminder tells the model to save durable facts through this tool before the transcript head is summarised.
Examples
Section titled “Examples”1. Add, and read it back off disk
Section titled “1. Add, and read it back off disk”(require '[toolnexus.agents.home :as home] '[toolnexus.native :as native] '[koine.fs :as fs])
(def dir (str (fs/temp-dir! "ava")))(def mem (home/memory-tool dir))
(println "tool name:" (:name mem))(assert (= "memory" (:name mem)))
(def r (native/execute-native mem {:action "add" :text "the user prefers Clojure"}))(println "result:" (:output r))(assert (false? (:isError r)))
;; The claim is that it PERSISTS — so read the file, not the return value.(println "MEMORY.md =>" (pr-str (fs/read-file (str dir "/MEMORY.md"))))(assert (= "- the user prefers Clojure\n" (fs/read-file (str dir "/MEMORY.md"))))
;; A second add appends; it does not overwrite.(native/execute-native mem {:action "add" :text "and Go"})(assert (= "- the user prefers Clojure\n- and Go\n" (fs/read-file (str dir "/MEMORY.md"))))
(fs/delete-tree! dir)(println "OK")2. Curate — replace, remove, and a loud miss
Section titled “2. Curate — replace, remove, and a loud miss”(require '[toolnexus.agents.home :as home] '[toolnexus.native :as native] '[clojure.string :as str] '[koine.fs :as fs])
(def dir (str (fs/temp-dir! "ava")))(fs/write-file (str dir "/MEMORY.md") "- likes tea\n- ships on Fridays\n")(def mem (home/memory-tool dir))
(native/execute-native mem {:action "replace" :text "tea" :with "coffee"})(println (fs/read-file (str dir "/MEMORY.md")))(assert (str/includes? (fs/read-file (str dir "/MEMORY.md")) "- likes coffee"))
(native/execute-native mem {:action "remove" :text "- ships on Fridays\n"})(assert (= "- likes coffee\n" (fs/read-file (str dir "/MEMORY.md"))))
;; A substring that is not there is an ERROR, not a quiet success.(def miss (native/execute-native mem {:action "replace" :text "absent" :with "x"}))(println "miss =>" (:isError miss) (:output miss))(assert (true? (:isError miss)))(assert (str/includes? (:output miss) "not found"));; …and the file is untouched by the failed edit.(assert (= "- likes coffee\n" (fs/read-file (str dir "/MEMORY.md"))))
(fs/delete-tree! dir)(println "OK")3. target user writes USER.md
Section titled “3. target user writes USER.md”(require '[toolnexus.agents.home :as home] '[toolnexus.native :as native] '[koine.fs :as fs])
(def dir (str (fs/temp-dir! "ava")))(def mem (home/memory-tool dir))
(native/execute-native mem {:action "add" :target "user" :text "works in IST"})
(assert (= "- works in IST\n" (fs/read-file (str dir "/USER.md"))))(assert (not (fs/exists? (str dir "/MEMORY.md")))) ; the agent's own notes are untouched
(fs/delete-tree! dir)(println "OK")4. In a toolkit, alongside everything else
Section titled “4. In a toolkit, alongside everything else”The memory tool is an ordinary Tool — it goes into :tools with your own functions, MCP tools
and built-ins.
(require '[toolnexus.agents.home :as home] '[toolnexus.core :as toolnexus] '[toolnexus.native :as native] '[koine.fs :as fs])
(def dir (str (fs/temp-dir! "ava")))
(def tk (toolnexus/build {:tools [(home/memory-tool dir) (native/native-tool {:name "now" :description "The current time, as a fixed string." :input-schema {:type "object" :properties {}} :run (fn [_] "2026-08-02T00:00:00Z")})]}))
(println "tools:" (pr-str (sort (toolnexus/tool-names tk))))(assert (some #(= "memory" %) (toolnexus/tool-names tk)))
(fs/delete-tree! dir)(println "OK")See also
Section titled “See also”toolnexus.agents.home/compose-soul— the home directory this tool writes into, and howMEMORY.mdreaches the prompttoolnexus.agents.compaction/compactor—:flush-to-memorypoints the model at this tool