Skip to content

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 other

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

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.

  • A persona should remember across sessions — preferences, decisions, facts about you.
  • You want the agent to curate its own notes, not just append forever: replace and remove are what make MEMORY.md maintainable.
  • You are using :flush-to-memory on compactor — that reminder tells the model to save durable facts through this tool before the transcript head is summarised.
(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")
(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")