hotmemory 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .env
11
+ .DS_Store
12
+
13
+ # The benchmark data, the caches, and the result file of each run.
14
+ bench/data/
15
+ bench/cache/
16
+ bench/results/
17
+
18
+ # Local state of Claude Code. The rest of .claude/ (agents, skills, settings.json) is shared.
19
+ .claude/worktrees/
20
+ .claude/settings.local.json
21
+ .claude/scheduled_tasks.lock
22
+
23
+ # Notes of the owner, kept out of git: the plan, the roadmap, the design brief, the
24
+ # research, and the local stack.
25
+ docs/internal/
@@ -0,0 +1,61 @@
1
+ # Changelog
2
+
3
+ Each change to a public surface of hotmemory has an entry here. The frozen surfaces are the
4
+ names in `hotmemory.__all__`, the method set of `Store`, the fields and field types of
5
+ `Record` for each schema version, and the filter keys of `list` and `search`. A test guards
6
+ each one, and its failure message points to this file.
7
+
8
+ New entries go under `## [Unreleased]`. A release moves them into a section named
9
+ `## [X.Y.Z] - YYYY-MM-DD`, and that section becomes the notes of the GitHub Release.
10
+ [RELEASING.md](RELEASING.md) gives the procedure.
11
+
12
+ ## [Unreleased]
13
+
14
+ ## [0.1.0] - 2026-10-23
15
+
16
+ The first release. It needs Python 3.11 or later. The core package has no dependencies.
17
+
18
+ ### Added
19
+
20
+ - `Record`, the frozen dataclass for schema version 1, and `normalize`, the form that
21
+ deduplication compares.
22
+ - `Store`, the protocol of the storage contract, with `put`, `get`, `history`, `list`,
23
+ `search`, `delete`, `sweep`, `list_namespaces`, and `writer`. `Writer` is the protocol of
24
+ the buffer that `writer` returns.
25
+ - Records are immutable. A put under an existing key writes a new revision. A put whose
26
+ content duplicates the current revision but brings a new source writes a new revision
27
+ with the merged sources.
28
+ - `put` takes `close_previous`, which closes the validity span of the current revision
29
+ in the same write.
30
+ - A put that moves a key between `episode` and another kind raises `ValueError`.
31
+ - `sweep` deletes every revision of each key whose current revision is past
32
+ `forget_after`, and returns the deleted ids.
33
+ - `Filter` and `TimeRange`, the exact filters of `list` and `search`, and `Hit`, one search
34
+ result with its distance. `Filter.kind` takes one kind or a tuple of kinds.
35
+ - `MemoryStore` and `MemoryWriter`, the in-process driver. `MemoryStore` takes `records`,
36
+ the revisions that it starts with, and `records()` returns every revision that it holds.
37
+ A writer flush in which one put raises writes nothing.
38
+ - `hotmemory.hotdata`, in the `hotdata` extra, with `HotdataStore`, the driver over one
39
+ Hotdata managed database, and `HotdataWriter`. The extra needs `hotdata-framework` 0.14.1
40
+ and `pyarrow` 16.0 or later.
41
+ - `HotdataStore.provision` opens the database with the name, or creates it.
42
+ `HotdataStore.open` opens a database by id. `LayoutError` reports a database whose
43
+ layout differs.
44
+ - A search ranks by content vector by default, as in `MemoryStore`. `ranking="fused"`
45
+ ranks by reciprocal rank fusion of BM25 and the vector rankings.
46
+ - `provision` and `open` take `vector_index`, an experimental option that builds and
47
+ uses the vector indexes, and `sorted_index`, which builds a sorted index on
48
+ `created_at`. Both are off by default.
49
+ - `Memory`, the memory contract over one `Store`, with `remember`, `recall`, `candidates`,
50
+ `supersede`, `forget`, `profile`, `capture`, and `load`. `Fact` is the frozen dataclass
51
+ of one structured fact that `remember` takes.
52
+ - `recall` and `profile` read the records of kind `fact`, `profile`, and `procedure`.
53
+ `recall` takes `episodes`, `"cited"` or `"search"`, to add episodes after the facts
54
+ inside the budget.
55
+ - `capture` takes an extractor callable. With `episode`, it also stores the text as an
56
+ episode, and with `chunk_chars`, as chunks of that size.
57
+ - `load` cuts a Markdown document into chunks by headings and fences, stores each chunk
58
+ as an episode, and runs an extractor on each chunk.
59
+ - `hotmemory.openai`, in the `openai` extra, with `OpenAIEmbedder`, an `Embedder` over the
60
+ OpenAI embeddings API.
61
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hotdata
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,263 @@
1
+ Metadata-Version: 2.5
2
+ Name: hotmemory
3
+ Version: 0.1.0
4
+ Summary: Agent memory as tables on Hotdata
5
+ Project-URL: Homepage, https://github.com/hotdata-dev/hotmemory
6
+ Project-URL: Documentation, https://github.com/hotdata-dev/hotmemory/tree/main/docs
7
+ Project-URL: Changelog, https://github.com/hotdata-dev/hotmemory/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/hotdata-dev/hotmemory/issues
9
+ Author: Hotdata
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,hotdata,llm,memory,vector-search
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Provides-Extra: hotdata
27
+ Requires-Dist: hotdata-framework>=0.14.1; extra == 'hotdata'
28
+ Requires-Dist: pyarrow>=16.0; extra == 'hotdata'
29
+ Provides-Extra: openai
30
+ Requires-Dist: openai>=1.0; extra == 'openai'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # hotmemory
34
+
35
+ Agent memory as tables on [Hotdata](https://hotdata.dev).
36
+
37
+ A memory record is a row in a managed table. The row carries the text, a scope, tags,
38
+ source references, and the time span in which the fact was true. The table is an ordinary
39
+ Hotdata table, so an agent can join its memory to its own data in one SQL query. None of
40
+ the memory systems that we surveyed stores memory as typed columns in the same engine as
41
+ the data of the consumer.
42
+
43
+ Status: alpha. A minor version before 1.0 can change a public surface, and [CHANGELOG.md]
44
+ lists each change. The storage contract exists in Python with two drivers. `MemoryStore`
45
+ runs in process memory. `HotdataStore` keeps records in one Hotdata managed database. The
46
+ memory contract, `Memory`, runs over either driver.
47
+
48
+ The library has two layers:
49
+
50
+ - A storage contract. Put, get, list, search, and delete records in a namespace. Records
51
+ are immutable. A new put under the same key creates a new revision.
52
+ - A memory contract. Remember facts, recall them inside a context budget, supersede a
53
+ fact, forget by id or by horizon, and load the profile of a subject. Extraction from raw
54
+ text is optional, and it takes an extractor callable that the caller supplies. `load`
55
+ cuts a Markdown document into chunks, stores each chunk as an episode, and extracts
56
+ facts that name their episode as a source. `capture` with an `episode` key stores its
57
+ text as an episode in the same way, or as chunks with `chunk_chars`. `profile` returns facts, profiles, and procedures,
58
+ never episodes. `recall` returns them too, and adds episodes after the facts only when
59
+ the caller asks for them.
60
+
61
+ The library runs in the process of the consumer and calls the Hotdata API with the API key
62
+ of the consumer. There is no hotmemory server. The first consumer is an incident
63
+ investigator built on Hotdata. The library is not specific to it. A plain Python agent, a
64
+ LangGraph agent, or any process with a Hotdata API key can use it.
65
+
66
+ ## Install
67
+
68
+ hotmemory needs Python 3.11 or later. The core package has no dependencies:
69
+
70
+ ```sh
71
+ pip install hotmemory
72
+ ```
73
+
74
+ Each driver and embedder that calls a service is in an extra. The `hotdata` extra adds
75
+ `HotdataStore`. The `openai` extra adds `OpenAIEmbedder`.
76
+
77
+ ```sh
78
+ pip install "hotmemory[hotdata,openai]"
79
+ ```
80
+
81
+ ## Try the storage contract
82
+
83
+ `MemoryStore` needs an embedder for a search with query text. An embedder is a callable
84
+ that turns a list of texts into a list of vectors. This example uses a toy embedder that
85
+ counts two words:
86
+
87
+ ```python
88
+ from hotmemory import Filter, MemoryStore
89
+
90
+
91
+ def embed(texts):
92
+ return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]
93
+
94
+
95
+ store = MemoryStore(embedder=embed)
96
+ store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at night.")
97
+ store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at noon.")
98
+ store.put(("team", "alerts"), "cpu", kind="fact", content="The cpu spikes after a deploy.")
99
+
100
+ print(store.get(("team", "alerts"), "disk").id) # team/alerts/disk@2
101
+ print([r.revision for r in store.history(("team", "alerts"), "disk")]) # [1, 2]
102
+ hits = store.search("disk", [("team",)], Filter(kind="fact"), k=1)
103
+ print(hits[0].record.content) # The disk fills at noon.
104
+ ```
105
+
106
+ ## Try the memory contract
107
+
108
+ `Memory` gives an agent the memory operations over any store. `remember` derives each key
109
+ from the subject and the content, so a retried call writes nothing new. `recall` returns
110
+ the records and one block of text, with one line for each record:
111
+
112
+ ```python
113
+ from hotmemory import Fact, Memory, MemoryStore
114
+
115
+
116
+ def embed(texts):
117
+ return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]
118
+
119
+
120
+ memory = Memory(MemoryStore(embedder=embed))
121
+ scope = ("team", "alerts")
122
+ memory.remember(
123
+ [Fact(kind="fact", subject="disk", content="The disk fills at night.", sources=("chat-1",))],
124
+ scope,
125
+ )
126
+ records, block = memory.recall("disk", [scope])
127
+ print(block) # - The disk fills at night. [sources: chat-1] [valid: unknown to now]
128
+ ```
129
+
130
+ `load` takes a document name, the Markdown text, a scope, and an extractor. The extractor
131
+ gets each chunk, with the first heading and the heading path of the chunk at its top:
132
+
133
+ ```python
134
+ from hotmemory import Fact, Memory, MemoryStore
135
+
136
+
137
+ def embed(texts):
138
+ return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]
139
+
140
+
141
+ def last_line(text, observed_at, current):
142
+ return [Fact(kind="fact", subject="disk", content=text.splitlines()[-1])]
143
+
144
+
145
+ memory = Memory(MemoryStore(embedder=embed))
146
+ text = "# Disk incident\n\n## Root cause\n\nThe disk fills at night.\n"
147
+ episodes, facts = memory.load("disk-incident", text, ("team", "alerts"), last_line)
148
+ print(episodes) # ['team/alerts/disk-incident-0001@1']
149
+ episode = memory.store.get(("team", "alerts"), "disk-incident-0001")
150
+ print(episode.content.splitlines()[0]) # # Disk incident
151
+ ```
152
+
153
+ ## Use the skill
154
+
155
+ [skills/hotmemory/SKILL.md] gives an agent the same operations as commands. The skill is
156
+ a folder with the skill file and one script for each operation. The package does not
157
+ include it, so you copy it from the repository.
158
+
159
+ 1. Install hotmemory in the Python that the agent runs. The extras are necessary only for
160
+ a Hotdata database:
161
+
162
+ ```sh
163
+ pip install "hotmemory[hotdata,openai]"
164
+ ```
165
+
166
+ 2. Copy the skill folder into the folder where your coding agent reads skills. Many agents
167
+ use `~/.<agent>/skills/` for every project, or `.<agent>/skills/` in one project. The
168
+ documentation of your agent gives the correct folder.
169
+
170
+ ```sh
171
+ git clone --depth 1 https://github.com/hotdata-dev/hotmemory
172
+ cp -r hotmemory/skills/hotmemory <skills-folder-of-your-agent>/
173
+ ```
174
+
175
+ The agent runs the scripts from the skill folder. With `--memory-file`, they need no
176
+ network and no key. With `--database`, they read the Hotdata and OpenAI keys from the
177
+ environment.
178
+
179
+ ## Store memory in Hotdata
180
+
181
+ `HotdataStore` needs the `hotdata` extra, and an embedder for every write and search. The
182
+ `openai` extra ships `OpenAIEmbedder`, which reads `OPENAI_API_KEY`. Any callable that turns
183
+ a list of texts into a list of vectors also works. `provision` opens the database with the
184
+ name, or creates it. It reads the connection from `HOTDATA_API_KEY`, `HOTDATA_WORKSPACE`,
185
+ and `HOTDATA_API_URL`:
186
+
187
+ ```python
188
+ from hotmemory.hotdata import HotdataStore
189
+ from hotmemory.openai import OpenAIEmbedder
190
+
191
+ embedder = OpenAIEmbedder()
192
+ store = HotdataStore.provision(
193
+ "agent-memory", embedder=embedder, model=embedder.model, dimensions=1536
194
+ )
195
+ store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at night.")
196
+ print([hit.record.id for hit in store.search("disk", [("team",)])])
197
+ ```
198
+
199
+ One process writes to a database. [docs/contracts.md] gives the tables,
200
+ the retrieval query, and the rules of the driver.
201
+
202
+ ## Benchmark
203
+
204
+ hotmemory ran LongMemEval-S, a public benchmark of 500 questions over long chat histories,
205
+ on 2026-10-10. [docs/benchmarks.md] gives every number with its
206
+ configuration.
207
+
208
+ On all 500 questions, on 2026-10-22, the memory arm scored 439 of 500 (87.8%). It ran
209
+ `capture` with chunks of 1,000 characters and a `gpt-4o-mini` extractor, and `recall` with
210
+ a budget of 16,000 characters, about 4,350 tokens of reader input. The reader and the judge
211
+ were `gpt-4o-2024-08-06`. This is one run, and its 95% confidence interval is about 2.9
212
+ points on each side. The 392 questions that no tuning used scored 88.0%.
213
+
214
+ Retrieval needs no model except the embedder. The runner stored each session of a question
215
+ as an episode in `HotdataStore` and searched with the question. The 419 scored questions
216
+ had these results with the vector ranking. For 98.3% of them, an evidence session was in
217
+ the top 5. For 99.3% of them, every evidence session was in the top 10.
218
+
219
+ Before that run, the end-to-end score ran on two samples of 54 questions, sample A on
220
+ 2026-10-10 and sample B on 2026-10-14. Later rows give their own date. The reader and the judge were
221
+ `gpt-4o-2024-08-06`.
222
+
223
+ | What the reader got | Sample A | Sample B |
224
+ |---|---|---|
225
+ | Every session of the question, about 114,000 tokens | 39 of 54 (72.2%) | 43 of 54 (79.6%) |
226
+ | The 10 sessions that the search ranked first | 45 of 54 (83.3%) | 48 of 54 (88.9%) |
227
+ | The block of `recall`, after `capture` with a `gpt-4o-mini` extractor and the `baseline` prompt | 19 of 54 (35.2%) | 27 of 54 (50.0%) |
228
+ | The same, on 2026-10-14, with the `detail` prompt and a session source on each fact | 31 of 54 (57.4%) | 32 of 54 (59.3%) |
229
+ | The same, on 2026-10-15, with each session stored as an episode, and `recall` with `episodes="search"` and a budget of 64,000 characters, about 12,800 tokens | 45 of 54 (83.3%) | 45 of 54 (83.3%) |
230
+ | The same, on 2026-10-18, with each session stored as chunks of 1,000 characters (`capture` with `chunk_chars=1000`), and `recall` with `episodes="search"` and a budget of 16,000 characters, about 4,300 tokens | 48 of 54 (88.9%) | 46 of 54 (85.2%) |
231
+
232
+ Facts alone score below the full history. With the `baseline` prompt, the extractor did
233
+ not store the answer in 19 of the 35 misses on sample A, and in 16 of the 27 misses on
234
+ sample B. With the `detail` prompt, these counts are 16 of 23 and 14 of 22. With the
235
+ whole sessions that a search adds after the facts, the extractor did not store the answer
236
+ in 5 misses on each sample. With chunks of 1,000 characters, this count is 2 on each
237
+ sample. The chunk row scores above the full history on both samples. It scores 3 more
238
+ than the 10 sessions on sample A and 2 less on sample B, with about one seventh of their
239
+ tokens. With 54 questions, each score can be about 10 points off. With the `baseline` prompt, the memory
240
+ arm differed by 14.8 points between the two samples. In the cloud, a search took 361 ms at
241
+ p50 with the vector ranking, and 998 ms with the fused ranking.
242
+
243
+ ## Documents
244
+
245
+ - [docs/contracts.md]: the record, the storage operations, the memory
246
+ operations, and the platform facts behind them.
247
+ - [docs/guarantees.md]: each behavior that a consumer can rely on, its
248
+ state, and its proof.
249
+ - [docs/benchmarks.md]: the numbers of hotmemory on LongMemEval-S,
250
+ each with its configuration. [bench/README.md] tells you how to run it.
251
+ - [CONTRIBUTING.md]: the one command that checks a change, and the rules
252
+ of the test suite.
253
+ - [CHANGELOG.md]: each change to a public surface.
254
+ - [docs/architecture.md]: how each operation runs on the Hotdata platform.
255
+
256
+ [CHANGELOG.md]: https://github.com/hotdata-dev/hotmemory/blob/main/CHANGELOG.md
257
+ [docs/contracts.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/contracts.md
258
+ [docs/benchmarks.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/benchmarks.md
259
+ [docs/guarantees.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/guarantees.md
260
+ [bench/README.md]: https://github.com/hotdata-dev/hotmemory/blob/main/bench/README.md
261
+ [CONTRIBUTING.md]: https://github.com/hotdata-dev/hotmemory/blob/main/CONTRIBUTING.md
262
+ [docs/architecture.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/architecture.md
263
+ [skills/hotmemory/SKILL.md]: https://github.com/hotdata-dev/hotmemory/blob/main/skills/hotmemory/SKILL.md
@@ -0,0 +1,231 @@
1
+ # hotmemory
2
+
3
+ Agent memory as tables on [Hotdata](https://hotdata.dev).
4
+
5
+ A memory record is a row in a managed table. The row carries the text, a scope, tags,
6
+ source references, and the time span in which the fact was true. The table is an ordinary
7
+ Hotdata table, so an agent can join its memory to its own data in one SQL query. None of
8
+ the memory systems that we surveyed stores memory as typed columns in the same engine as
9
+ the data of the consumer.
10
+
11
+ Status: alpha. A minor version before 1.0 can change a public surface, and [CHANGELOG.md]
12
+ lists each change. The storage contract exists in Python with two drivers. `MemoryStore`
13
+ runs in process memory. `HotdataStore` keeps records in one Hotdata managed database. The
14
+ memory contract, `Memory`, runs over either driver.
15
+
16
+ The library has two layers:
17
+
18
+ - A storage contract. Put, get, list, search, and delete records in a namespace. Records
19
+ are immutable. A new put under the same key creates a new revision.
20
+ - A memory contract. Remember facts, recall them inside a context budget, supersede a
21
+ fact, forget by id or by horizon, and load the profile of a subject. Extraction from raw
22
+ text is optional, and it takes an extractor callable that the caller supplies. `load`
23
+ cuts a Markdown document into chunks, stores each chunk as an episode, and extracts
24
+ facts that name their episode as a source. `capture` with an `episode` key stores its
25
+ text as an episode in the same way, or as chunks with `chunk_chars`. `profile` returns facts, profiles, and procedures,
26
+ never episodes. `recall` returns them too, and adds episodes after the facts only when
27
+ the caller asks for them.
28
+
29
+ The library runs in the process of the consumer and calls the Hotdata API with the API key
30
+ of the consumer. There is no hotmemory server. The first consumer is an incident
31
+ investigator built on Hotdata. The library is not specific to it. A plain Python agent, a
32
+ LangGraph agent, or any process with a Hotdata API key can use it.
33
+
34
+ ## Install
35
+
36
+ hotmemory needs Python 3.11 or later. The core package has no dependencies:
37
+
38
+ ```sh
39
+ pip install hotmemory
40
+ ```
41
+
42
+ Each driver and embedder that calls a service is in an extra. The `hotdata` extra adds
43
+ `HotdataStore`. The `openai` extra adds `OpenAIEmbedder`.
44
+
45
+ ```sh
46
+ pip install "hotmemory[hotdata,openai]"
47
+ ```
48
+
49
+ ## Try the storage contract
50
+
51
+ `MemoryStore` needs an embedder for a search with query text. An embedder is a callable
52
+ that turns a list of texts into a list of vectors. This example uses a toy embedder that
53
+ counts two words:
54
+
55
+ ```python
56
+ from hotmemory import Filter, MemoryStore
57
+
58
+
59
+ def embed(texts):
60
+ return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]
61
+
62
+
63
+ store = MemoryStore(embedder=embed)
64
+ store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at night.")
65
+ store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at noon.")
66
+ store.put(("team", "alerts"), "cpu", kind="fact", content="The cpu spikes after a deploy.")
67
+
68
+ print(store.get(("team", "alerts"), "disk").id) # team/alerts/disk@2
69
+ print([r.revision for r in store.history(("team", "alerts"), "disk")]) # [1, 2]
70
+ hits = store.search("disk", [("team",)], Filter(kind="fact"), k=1)
71
+ print(hits[0].record.content) # The disk fills at noon.
72
+ ```
73
+
74
+ ## Try the memory contract
75
+
76
+ `Memory` gives an agent the memory operations over any store. `remember` derives each key
77
+ from the subject and the content, so a retried call writes nothing new. `recall` returns
78
+ the records and one block of text, with one line for each record:
79
+
80
+ ```python
81
+ from hotmemory import Fact, Memory, MemoryStore
82
+
83
+
84
+ def embed(texts):
85
+ return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]
86
+
87
+
88
+ memory = Memory(MemoryStore(embedder=embed))
89
+ scope = ("team", "alerts")
90
+ memory.remember(
91
+ [Fact(kind="fact", subject="disk", content="The disk fills at night.", sources=("chat-1",))],
92
+ scope,
93
+ )
94
+ records, block = memory.recall("disk", [scope])
95
+ print(block) # - The disk fills at night. [sources: chat-1] [valid: unknown to now]
96
+ ```
97
+
98
+ `load` takes a document name, the Markdown text, a scope, and an extractor. The extractor
99
+ gets each chunk, with the first heading and the heading path of the chunk at its top:
100
+
101
+ ```python
102
+ from hotmemory import Fact, Memory, MemoryStore
103
+
104
+
105
+ def embed(texts):
106
+ return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]
107
+
108
+
109
+ def last_line(text, observed_at, current):
110
+ return [Fact(kind="fact", subject="disk", content=text.splitlines()[-1])]
111
+
112
+
113
+ memory = Memory(MemoryStore(embedder=embed))
114
+ text = "# Disk incident\n\n## Root cause\n\nThe disk fills at night.\n"
115
+ episodes, facts = memory.load("disk-incident", text, ("team", "alerts"), last_line)
116
+ print(episodes) # ['team/alerts/disk-incident-0001@1']
117
+ episode = memory.store.get(("team", "alerts"), "disk-incident-0001")
118
+ print(episode.content.splitlines()[0]) # # Disk incident
119
+ ```
120
+
121
+ ## Use the skill
122
+
123
+ [skills/hotmemory/SKILL.md] gives an agent the same operations as commands. The skill is
124
+ a folder with the skill file and one script for each operation. The package does not
125
+ include it, so you copy it from the repository.
126
+
127
+ 1. Install hotmemory in the Python that the agent runs. The extras are necessary only for
128
+ a Hotdata database:
129
+
130
+ ```sh
131
+ pip install "hotmemory[hotdata,openai]"
132
+ ```
133
+
134
+ 2. Copy the skill folder into the folder where your coding agent reads skills. Many agents
135
+ use `~/.<agent>/skills/` for every project, or `.<agent>/skills/` in one project. The
136
+ documentation of your agent gives the correct folder.
137
+
138
+ ```sh
139
+ git clone --depth 1 https://github.com/hotdata-dev/hotmemory
140
+ cp -r hotmemory/skills/hotmemory <skills-folder-of-your-agent>/
141
+ ```
142
+
143
+ The agent runs the scripts from the skill folder. With `--memory-file`, they need no
144
+ network and no key. With `--database`, they read the Hotdata and OpenAI keys from the
145
+ environment.
146
+
147
+ ## Store memory in Hotdata
148
+
149
+ `HotdataStore` needs the `hotdata` extra, and an embedder for every write and search. The
150
+ `openai` extra ships `OpenAIEmbedder`, which reads `OPENAI_API_KEY`. Any callable that turns
151
+ a list of texts into a list of vectors also works. `provision` opens the database with the
152
+ name, or creates it. It reads the connection from `HOTDATA_API_KEY`, `HOTDATA_WORKSPACE`,
153
+ and `HOTDATA_API_URL`:
154
+
155
+ ```python
156
+ from hotmemory.hotdata import HotdataStore
157
+ from hotmemory.openai import OpenAIEmbedder
158
+
159
+ embedder = OpenAIEmbedder()
160
+ store = HotdataStore.provision(
161
+ "agent-memory", embedder=embedder, model=embedder.model, dimensions=1536
162
+ )
163
+ store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at night.")
164
+ print([hit.record.id for hit in store.search("disk", [("team",)])])
165
+ ```
166
+
167
+ One process writes to a database. [docs/contracts.md] gives the tables,
168
+ the retrieval query, and the rules of the driver.
169
+
170
+ ## Benchmark
171
+
172
+ hotmemory ran LongMemEval-S, a public benchmark of 500 questions over long chat histories,
173
+ on 2026-10-10. [docs/benchmarks.md] gives every number with its
174
+ configuration.
175
+
176
+ On all 500 questions, on 2026-10-22, the memory arm scored 439 of 500 (87.8%). It ran
177
+ `capture` with chunks of 1,000 characters and a `gpt-4o-mini` extractor, and `recall` with
178
+ a budget of 16,000 characters, about 4,350 tokens of reader input. The reader and the judge
179
+ were `gpt-4o-2024-08-06`. This is one run, and its 95% confidence interval is about 2.9
180
+ points on each side. The 392 questions that no tuning used scored 88.0%.
181
+
182
+ Retrieval needs no model except the embedder. The runner stored each session of a question
183
+ as an episode in `HotdataStore` and searched with the question. The 419 scored questions
184
+ had these results with the vector ranking. For 98.3% of them, an evidence session was in
185
+ the top 5. For 99.3% of them, every evidence session was in the top 10.
186
+
187
+ Before that run, the end-to-end score ran on two samples of 54 questions, sample A on
188
+ 2026-10-10 and sample B on 2026-10-14. Later rows give their own date. The reader and the judge were
189
+ `gpt-4o-2024-08-06`.
190
+
191
+ | What the reader got | Sample A | Sample B |
192
+ |---|---|---|
193
+ | Every session of the question, about 114,000 tokens | 39 of 54 (72.2%) | 43 of 54 (79.6%) |
194
+ | The 10 sessions that the search ranked first | 45 of 54 (83.3%) | 48 of 54 (88.9%) |
195
+ | The block of `recall`, after `capture` with a `gpt-4o-mini` extractor and the `baseline` prompt | 19 of 54 (35.2%) | 27 of 54 (50.0%) |
196
+ | The same, on 2026-10-14, with the `detail` prompt and a session source on each fact | 31 of 54 (57.4%) | 32 of 54 (59.3%) |
197
+ | The same, on 2026-10-15, with each session stored as an episode, and `recall` with `episodes="search"` and a budget of 64,000 characters, about 12,800 tokens | 45 of 54 (83.3%) | 45 of 54 (83.3%) |
198
+ | The same, on 2026-10-18, with each session stored as chunks of 1,000 characters (`capture` with `chunk_chars=1000`), and `recall` with `episodes="search"` and a budget of 16,000 characters, about 4,300 tokens | 48 of 54 (88.9%) | 46 of 54 (85.2%) |
199
+
200
+ Facts alone score below the full history. With the `baseline` prompt, the extractor did
201
+ not store the answer in 19 of the 35 misses on sample A, and in 16 of the 27 misses on
202
+ sample B. With the `detail` prompt, these counts are 16 of 23 and 14 of 22. With the
203
+ whole sessions that a search adds after the facts, the extractor did not store the answer
204
+ in 5 misses on each sample. With chunks of 1,000 characters, this count is 2 on each
205
+ sample. The chunk row scores above the full history on both samples. It scores 3 more
206
+ than the 10 sessions on sample A and 2 less on sample B, with about one seventh of their
207
+ tokens. With 54 questions, each score can be about 10 points off. With the `baseline` prompt, the memory
208
+ arm differed by 14.8 points between the two samples. In the cloud, a search took 361 ms at
209
+ p50 with the vector ranking, and 998 ms with the fused ranking.
210
+
211
+ ## Documents
212
+
213
+ - [docs/contracts.md]: the record, the storage operations, the memory
214
+ operations, and the platform facts behind them.
215
+ - [docs/guarantees.md]: each behavior that a consumer can rely on, its
216
+ state, and its proof.
217
+ - [docs/benchmarks.md]: the numbers of hotmemory on LongMemEval-S,
218
+ each with its configuration. [bench/README.md] tells you how to run it.
219
+ - [CONTRIBUTING.md]: the one command that checks a change, and the rules
220
+ of the test suite.
221
+ - [CHANGELOG.md]: each change to a public surface.
222
+ - [docs/architecture.md]: how each operation runs on the Hotdata platform.
223
+
224
+ [CHANGELOG.md]: https://github.com/hotdata-dev/hotmemory/blob/main/CHANGELOG.md
225
+ [docs/contracts.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/contracts.md
226
+ [docs/benchmarks.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/benchmarks.md
227
+ [docs/guarantees.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/guarantees.md
228
+ [bench/README.md]: https://github.com/hotdata-dev/hotmemory/blob/main/bench/README.md
229
+ [CONTRIBUTING.md]: https://github.com/hotdata-dev/hotmemory/blob/main/CONTRIBUTING.md
230
+ [docs/architecture.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/architecture.md
231
+ [skills/hotmemory/SKILL.md]: https://github.com/hotdata-dev/hotmemory/blob/main/skills/hotmemory/SKILL.md