Skip to content

teff.memory.tool

teff.memory.tool

Agent-facing tool for long-term memory (remember / recall / forget).

Classes:

Name Description
MemoryTool

Tool that lets an agent read and write long-term memory.

Functions:

Name Description
memory_from_config

Build a :class:MemoryStore from a config dict.

MemoryTool

Bases: Tool

Tool that lets an agent read and write long-term memory.

Usage::

memory = MemoryTool(
    store=SQLiteVectorStore(path="./memory.db", dim=768),
    embedder=Embedder(provider="ollama", model="nomic-embed-text"),
    namespace=("users", "u1"),
)
await memory.arun(action="remember", text="prefers email over Slack")
result = await memory.arun(action="recall", query="how to reach them?")

Actions (passed as action):

  • remember — upsert a fact (text plus optional metadata). When similarity_threshold is set and a semantically close item already exists in the namespace, the new text overwrites that item instead of creating a duplicate.
  • recall — return top-k memories for a query (or the most recent if no query is given), formatted for a prompt.
  • forget — delete the memory at key.
  • list — enumerate stored keys.

Can be built from a config dict (e.g. a tools: entry in a workflow YAML)::

{
  "name": "memory",
  "store": {"type": "sqlite", "path": "./memory.db", "dim": 768},
  "embedder": {"provider": "ollama", "model": "nomic-embed-text"},
  "namespace": ["users", "${USER_ID}"],
  "default_k": 5,
  "similarity_threshold": 0.6,
}

Supported store types match RAGTool: in_memory (default), sqlite, chroma, qdrant, pgvector, faiss, lance, milvus, weaviate, pinecone.

Methods:

Name Description
arun

Run a memory operation and return a human-readable result.

Source code in teff/memory/tool.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
class MemoryTool(Tool):
    """Tool that lets an agent read and write long-term memory.

    Usage::

        memory = MemoryTool(
            store=SQLiteVectorStore(path="./memory.db", dim=768),
            embedder=Embedder(provider="ollama", model="nomic-embed-text"),
            namespace=("users", "u1"),
        )
        await memory.arun(action="remember", text="prefers email over Slack")
        result = await memory.arun(action="recall", query="how to reach them?")

    Actions (passed as ``action``):

    - ``remember`` — upsert a fact (``text`` plus optional ``metadata``).
      When ``similarity_threshold`` is set and a semantically close item
      already exists in the namespace, the new text overwrites that item
      instead of creating a duplicate.
    - ``recall`` — return top-*k* memories for a ``query`` (or the most
      recent if no query is given), formatted for a prompt.
    - ``forget`` — delete the memory at ``key``.
    - ``list`` — enumerate stored keys.

    Can be built from a config dict (e.g. a ``tools:`` entry in a
    workflow YAML)::

        {
          "name": "memory",
          "store": {"type": "sqlite", "path": "./memory.db", "dim": 768},
          "embedder": {"provider": "ollama", "model": "nomic-embed-text"},
          "namespace": ["users", "${USER_ID}"],
          "default_k": 5,
          "similarity_threshold": 0.6,
        }

    Supported store types match ``RAGTool``: ``in_memory`` (default),
    ``sqlite``, ``chroma``, ``qdrant``, ``pgvector``, ``faiss``, ``lance``,
    ``milvus``, ``weaviate``, ``pinecone``.
    """

    name = "memory"
    description = (
        "Long-term memory: remember facts, recall relevant memories, "
        "forget, and list what is stored."
    )

    def __init__(
        self,
        config: dict | None = None,
        *,
        store: VectorStore | None = None,
        embedder: Embedder | None = None,
        namespace: tuple[str, ...] | list[str] = (),
        default_k: int = 5,
        similarity_threshold: float | None = None,
        ttl: float | None = None,
    ):
        self._memory: MemoryStore | None = None
        self._namespace = tuple(namespace)
        self._default_k = default_k
        self._threshold = similarity_threshold
        self._ttl = ttl
        if isinstance(config, dict):
            self._apply_config(config)
        elif store is not None and embedder is not None:
            self.memory = MemoryStore(store=store, embedder=embedder, ttl=ttl)

    @property
    def memory(self) -> MemoryStore:
        if self._memory is None:
            raise RuntimeError("memory store not initialised")
        return self._memory

    @memory.setter
    def memory(self, value: MemoryStore) -> None:
        self._memory = value

    def _apply_config(self, config: dict) -> None:
        self.memory = memory_from_config(config, default_ttl=self._ttl)
        ns = config.get("namespace")
        if ns:
            self._namespace = tuple(str(part) for part in ns)
        if config.get("default_k") is not None:
            self._default_k = int(config["default_k"])
        if config.get("similarity_threshold") is not None:
            self._threshold = float(config["similarity_threshold"])

    async def arun(  # type: ignore[override]
        self,
        action: str = "recall",
        key: str = "",
        text: str = "",
        value: dict | None = None,
        query: str = "",
        metadata: dict | None = None,
        k: int | None = None,
    ) -> str:
        """Run a memory operation and return a human-readable result.

        The namespace is fixed at construction time and can never be
        overridden by the caller — an agent cannot address another owner's
        memories by passing a namespace.  Per-owner isolation is achieved by
        building one tool per owner (``namespace=("users", owner)``).
        """
        ns = self._namespace
        eff_k = int(k) if k is not None else self._default_k
        mem = self.memory

        if action == "remember":
            return await self._remember(ns, key, text, value, metadata)
        if action == "recall":
            items = await mem.search(ns, query=query or None, k=eff_k)
            return _format_recall(items)
        if action == "forget":
            if not key:
                return "forget requires a `key`"
            await mem.delete(ns, key)
            return f"forgotten {key!r}"
        if action == "list":
            keys = await mem.list(ns, limit=1000)
            return "\n".join(keys) if keys else "(no memories)"
        raise ValueError(f"unknown memory action: {action!r}")

    async def _remember(
        self,
        ns: tuple[str, ...],
        key: str,
        text: str,
        value: dict | None,
        metadata: dict | None,
    ) -> str:
        if value is None:
            if not text:
                return "remember requires `text`"
            value = {"text": text, **(metadata or {})}
        elif "text" not in value:
            return "remember `value` requires a 'text' field"

        if self._threshold is not None:
            similar = await self.memory.search(ns, query=value["text"], k=1)
            if (
                similar
                and similar[0].score is not None
                and similar[0].score >= self._threshold
            ):
                key = similar[0].key

        final_key = key or uuid.uuid4().hex[:12]
        await self.memory.put(ns, final_key, value)
        return f"remembered {final_key!r}"

arun async

arun(action='recall', key='', text='', value=None, query='', metadata=None, k=None)

Run a memory operation and return a human-readable result.

The namespace is fixed at construction time and can never be overridden by the caller — an agent cannot address another owner's memories by passing a namespace. Per-owner isolation is achieved by building one tool per owner (namespace=("users", owner)).

Source code in teff/memory/tool.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
async def arun(  # type: ignore[override]
    self,
    action: str = "recall",
    key: str = "",
    text: str = "",
    value: dict | None = None,
    query: str = "",
    metadata: dict | None = None,
    k: int | None = None,
) -> str:
    """Run a memory operation and return a human-readable result.

    The namespace is fixed at construction time and can never be
    overridden by the caller — an agent cannot address another owner's
    memories by passing a namespace.  Per-owner isolation is achieved by
    building one tool per owner (``namespace=("users", owner)``).
    """
    ns = self._namespace
    eff_k = int(k) if k is not None else self._default_k
    mem = self.memory

    if action == "remember":
        return await self._remember(ns, key, text, value, metadata)
    if action == "recall":
        items = await mem.search(ns, query=query or None, k=eff_k)
        return _format_recall(items)
    if action == "forget":
        if not key:
            return "forget requires a `key`"
        await mem.delete(ns, key)
        return f"forgotten {key!r}"
    if action == "list":
        keys = await mem.list(ns, limit=1000)
        return "\n".join(keys) if keys else "(no memories)"
    raise ValueError(f"unknown memory action: {action!r}")

memory_from_config

memory_from_config(config, *, default_ttl=None, providers=None, default_provider=None)

Build a :class:MemoryStore from a config dict.

Mirrors RAGTool / MemoryTool config: {"store": {...}, "embedder": {...}, "ttl": ...}. Used by workflow YAML loading and by :class:~teff.node.agent.ReActAgent context injection.

providers (a registry) lets the embedder inherit a provider's base_url / api_key_env when the config does not set them explicitly.

Source code in teff/memory/tool.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def memory_from_config(
    config: dict,
    *,
    default_ttl: float | None = None,
    providers=None,
    default_provider: str | None = None,
) -> MemoryStore:
    """Build a :class:`MemoryStore` from a config dict.

    Mirrors ``RAGTool`` / ``MemoryTool`` config: ``{"store": {...},
    "embedder": {...}, "ttl": ...}``.  Used by workflow YAML loading and
    by :class:`~teff.node.agent.ReActAgent` context injection.

    *providers* (a registry) lets the embedder inherit a provider's
    ``base_url`` / ``api_key_env`` when the config does not set them
    explicitly.
    """
    embedder = embedder_from_config(
        config,
        providers=providers,
        default_provider=default_provider,
    )
    store = _build_store(config.get("store") or {})
    return MemoryStore(
        store=store,
        embedder=embedder,
        ttl=config.get("ttl", default_ttl),
    )