Normandy.Tools.Memory
(normandy v2.2.0)
View Source
Persistent agent memory: the memory tool. The model reads and writes text files under
/memories with Anthropic's memory-tool commands (view, create, str_replace,
insert, delete, rename); this tool executes them against a
Normandy.Behaviours.MemoryStore. Normandy.LLM.ClaudioAdapter sends it as Anthropic's
native memory tool (memory_20250818, native_definition/1), so the model gets the API's
own instructions for it; description/1 is what other consumers see (an A2A agent card).
A view of /memories names the mounts and their access.
The namespace comes from the session's tool_context (namespace_key, default
:memory_namespace), never from the tool struct, so it survives Turn.Server
rehydration and the model cannot change it. A missing namespace raises: there is no
default namespace.
BaseAgent.init(%{client: c, model: m, tool_context: %{memory_namespace: "acme/user-42"}})
|> BaseAgent.register_tool(Memory.new(store: {MemoryStore.Postgres, MyApp.Repo}))Options (new/1): :store (required, {module, handle}), :namespace_key, :mounts
(%{"/memories/org" => {"acme/org-kb", :read_only | :read_write}}),
:max_document_bytes (100_000), :max_namespace_bytes (1_000_000),
:max_namespace_documents (1000), :max_view_chars (16_000), :max_cas_retries (3),
:telemetry_include_paths (false).
:max_namespace_bytes bounds the total content bytes of a namespace's documents and
:max_namespace_documents their number; each is a positive integer, or nil for no
cap, and they apply independently. A create, str_replace or insert that would push
the namespace past either is refused with error type :namespace_full and writes
nothing. The store checks the caps in the same atomic step as the write
(Normandy.Behaviours.MemoryStore put/6), so they hold under concurrent writers and
count every document. A write that does not grow the namespace is always allowed, so a
namespace over a lowered cap can still be shortened. A mount's caps apply to the
mount's namespace.
Memory carries content across sessions, so it is a prompt-injection channel that persists: keep a namespace no wider than the trust boundary of the people whose input reaches the agent. Never store secrets in memory.
A mount's access holds only while no session can write its namespace directly, so a
session namespace equal to a mount's namespace raises: when the agent is built
(check_context!/2), and on a call whose context was
replaced (the call fails; the model sees the mount path, not the namespace), and two mounts of one namespace with different
access raise in new/1. Never build a namespace from a user-controlled string (a
username, an org slug) without a fixed prefix that mount namespaces do not use, such as
"users/" <> user_id beside "shared/org-kb".
Summary
Functions
Refuses a tool_context whose session namespace equals a mount's namespace, when the
agent is built. A context without the namespace passes: one can be supplied per call
(Normandy.A2A.Server's :tool_context), and run/2 checks it there.
Builds the tool, validating every option; raises ArgumentError naming a bad one.
Takes a keyword list or a map.
Types
@type t() :: %Normandy.Tools.Memory{ max_cas_retries: non_neg_integer(), max_document_bytes: pos_integer(), max_namespace_bytes: pos_integer() | nil, max_namespace_documents: pos_integer() | nil, max_view_chars: pos_integer(), mounts: %{required(String.t()) => {String.t(), :read_only | :read_write}}, namespace_key: atom(), store: {module(), term()}, telemetry_include_paths: boolean() }
Functions
Refuses a tool_context whose session namespace equals a mount's namespace, when the
agent is built. A context without the namespace passes: one can be supplied per call
(Normandy.A2A.Server's :tool_context), and run/2 checks it there.
Builds the tool, validating every option; raises ArgumentError naming a bad one.
Takes a keyword list or a map.
Options
:store({module, handle}) - Required. TheNormandy.Behaviours.MemoryStoremodule and its handle.:namespace_key(atom/0) - Thetool_contextkey holding the session namespace. The default value is:memory_namespace.:mounts(%{path => {namespace, :read_only | :read_write}}) - Shared directories mounted under/memories. The default value is%{}.:max_document_bytes(pos_integer/0) - Largest document. The default value is100000.:max_namespace_bytes(pos_integer/0ornil) - Total content bytes of a namespace;nil= no cap. The default value is1000000.:max_namespace_documents(pos_integer/0ornil) - Documents in a namespace;nil= no cap. The default value is1000.:max_view_chars(pos_integer/0) - Longestviewresult. The default value is16000.:max_cas_retries(non_neg_integer/0) - Compare-and-set retries of a write. The default value is3.:telemetry_include_paths(boolean/0) - Put the namespace and path in telemetry metadata. The default value isfalse.