Metadata-Version: 2.5
Name: hotmemory
Version: 0.1.0
Summary: Agent memory as tables on Hotdata
Project-URL: Homepage, https://github.com/hotdata-dev/hotmemory
Project-URL: Documentation, https://github.com/hotdata-dev/hotmemory/tree/main/docs
Project-URL: Changelog, https://github.com/hotdata-dev/hotmemory/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/hotdata-dev/hotmemory/issues
Author: Hotdata
License-Expression: MIT
License-File: LICENSE
Keywords: agent,hotdata,llm,memory,vector-search
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: hotdata
Requires-Dist: hotdata-framework>=0.14.1; extra == 'hotdata'
Requires-Dist: pyarrow>=16.0; extra == 'hotdata'
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == 'openai'
Description-Content-Type: text/markdown

# hotmemory

Agent memory as tables on [Hotdata](https://hotdata.dev).

A memory record is a row in a managed table. The row carries the text, a scope, tags,
source references, and the time span in which the fact was true. The table is an ordinary
Hotdata table, so an agent can join its memory to its own data in one SQL query. None of
the memory systems that we surveyed stores memory as typed columns in the same engine as
the data of the consumer.

Status: alpha. A minor version before 1.0 can change a public surface, and [CHANGELOG.md]
lists each change. The storage contract exists in Python with two drivers. `MemoryStore`
runs in process memory. `HotdataStore` keeps records in one Hotdata managed database. The
memory contract, `Memory`, runs over either driver.

The library has two layers:

- A storage contract. Put, get, list, search, and delete records in a namespace. Records
  are immutable. A new put under the same key creates a new revision.
- A memory contract. Remember facts, recall them inside a context budget, supersede a
  fact, forget by id or by horizon, and load the profile of a subject. Extraction from raw
  text is optional, and it takes an extractor callable that the caller supplies. `load`
  cuts a Markdown document into chunks, stores each chunk as an episode, and extracts
  facts that name their episode as a source. `capture` with an `episode` key stores its
  text as an episode in the same way, or as chunks with `chunk_chars`. `profile` returns facts, profiles, and procedures,
  never episodes. `recall` returns them too, and adds episodes after the facts only when
  the caller asks for them.

The library runs in the process of the consumer and calls the Hotdata API with the API key
of the consumer. There is no hotmemory server. The first consumer is an incident
investigator built on Hotdata. The library is not specific to it. A plain Python agent, a
LangGraph agent, or any process with a Hotdata API key can use it.

## Install

hotmemory needs Python 3.11 or later. The core package has no dependencies:

```sh
pip install hotmemory
```

Each driver and embedder that calls a service is in an extra. The `hotdata` extra adds
`HotdataStore`. The `openai` extra adds `OpenAIEmbedder`.

```sh
pip install "hotmemory[hotdata,openai]"
```

## Try the storage contract

`MemoryStore` needs an embedder for a search with query text. An embedder is a callable
that turns a list of texts into a list of vectors. This example uses a toy embedder that
counts two words:

```python
from hotmemory import Filter, MemoryStore


def embed(texts):
    return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]


store = MemoryStore(embedder=embed)
store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at night.")
store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at noon.")
store.put(("team", "alerts"), "cpu", kind="fact", content="The cpu spikes after a deploy.")

print(store.get(("team", "alerts"), "disk").id)  # team/alerts/disk@2
print([r.revision for r in store.history(("team", "alerts"), "disk")])  # [1, 2]
hits = store.search("disk", [("team",)], Filter(kind="fact"), k=1)
print(hits[0].record.content)  # The disk fills at noon.
```

## Try the memory contract

`Memory` gives an agent the memory operations over any store. `remember` derives each key
from the subject and the content, so a retried call writes nothing new. `recall` returns
the records and one block of text, with one line for each record:

```python
from hotmemory import Fact, Memory, MemoryStore


def embed(texts):
    return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]


memory = Memory(MemoryStore(embedder=embed))
scope = ("team", "alerts")
memory.remember(
    [Fact(kind="fact", subject="disk", content="The disk fills at night.", sources=("chat-1",))],
    scope,
)
records, block = memory.recall("disk", [scope])
print(block)  # - The disk fills at night. [sources: chat-1] [valid: unknown to now]
```

`load` takes a document name, the Markdown text, a scope, and an extractor. The extractor
gets each chunk, with the first heading and the heading path of the chunk at its top:

```python
from hotmemory import Fact, Memory, MemoryStore


def embed(texts):
    return [[text.count("disk") + 0.1, text.count("cpu") + 0.1] for text in texts]


def last_line(text, observed_at, current):
    return [Fact(kind="fact", subject="disk", content=text.splitlines()[-1])]


memory = Memory(MemoryStore(embedder=embed))
text = "# Disk incident\n\n## Root cause\n\nThe disk fills at night.\n"
episodes, facts = memory.load("disk-incident", text, ("team", "alerts"), last_line)
print(episodes)  # ['team/alerts/disk-incident-0001@1']
episode = memory.store.get(("team", "alerts"), "disk-incident-0001")
print(episode.content.splitlines()[0])  # # Disk incident
```

## Use the skill

[skills/hotmemory/SKILL.md] gives an agent the same operations as commands. The skill is
a folder with the skill file and one script for each operation. The package does not
include it, so you copy it from the repository.

1. Install hotmemory in the Python that the agent runs. The extras are necessary only for
   a Hotdata database:

   ```sh
   pip install "hotmemory[hotdata,openai]"
   ```

2. Copy the skill folder into the folder where your coding agent reads skills. Many agents
   use `~/.<agent>/skills/` for every project, or `.<agent>/skills/` in one project. The
   documentation of your agent gives the correct folder.

   ```sh
   git clone --depth 1 https://github.com/hotdata-dev/hotmemory
   cp -r hotmemory/skills/hotmemory <skills-folder-of-your-agent>/
   ```

The agent runs the scripts from the skill folder. With `--memory-file`, they need no
network and no key. With `--database`, they read the Hotdata and OpenAI keys from the
environment.

## Store memory in Hotdata

`HotdataStore` needs the `hotdata` extra, and an embedder for every write and search. The
`openai` extra ships `OpenAIEmbedder`, which reads `OPENAI_API_KEY`. Any callable that turns
a list of texts into a list of vectors also works. `provision` opens the database with the
name, or creates it. It reads the connection from `HOTDATA_API_KEY`, `HOTDATA_WORKSPACE`,
and `HOTDATA_API_URL`:

```python
from hotmemory.hotdata import HotdataStore
from hotmemory.openai import OpenAIEmbedder

embedder = OpenAIEmbedder()
store = HotdataStore.provision(
    "agent-memory", embedder=embedder, model=embedder.model, dimensions=1536
)
store.put(("team", "alerts"), "disk", kind="fact", content="The disk fills at night.")
print([hit.record.id for hit in store.search("disk", [("team",)])])
```

One process writes to a database. [docs/contracts.md] gives the tables,
the retrieval query, and the rules of the driver.

## Benchmark

hotmemory ran LongMemEval-S, a public benchmark of 500 questions over long chat histories,
on 2026-10-10. [docs/benchmarks.md] gives every number with its
configuration.

On all 500 questions, on 2026-10-22, the memory arm scored 439 of 500 (87.8%). It ran
`capture` with chunks of 1,000 characters and a `gpt-4o-mini` extractor, and `recall` with
a budget of 16,000 characters, about 4,350 tokens of reader input. The reader and the judge
were `gpt-4o-2024-08-06`. This is one run, and its 95% confidence interval is about 2.9
points on each side. The 392 questions that no tuning used scored 88.0%.

Retrieval needs no model except the embedder. The runner stored each session of a question
as an episode in `HotdataStore` and searched with the question. The 419 scored questions
had these results with the vector ranking. For 98.3% of them, an evidence session was in
the top 5. For 99.3% of them, every evidence session was in the top 10.

Before that run, the end-to-end score ran on two samples of 54 questions, sample A on
2026-10-10 and sample B on 2026-10-14. Later rows give their own date. The reader and the judge were
`gpt-4o-2024-08-06`.

| What the reader got | Sample A | Sample B |
|---|---|---|
| Every session of the question, about 114,000 tokens | 39 of 54 (72.2%) | 43 of 54 (79.6%) |
| The 10 sessions that the search ranked first | 45 of 54 (83.3%) | 48 of 54 (88.9%) |
| 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%) |
| 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%) |
| 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%) |
| 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%) |

Facts alone score below the full history. With the `baseline` prompt, the extractor did
not store the answer in 19 of the 35 misses on sample A, and in 16 of the 27 misses on
sample B. With the `detail` prompt, these counts are 16 of 23 and 14 of 22. With the
whole sessions that a search adds after the facts, the extractor did not store the answer
in 5 misses on each sample. With chunks of 1,000 characters, this count is 2 on each
sample. The chunk row scores above the full history on both samples. It scores 3 more
than the 10 sessions on sample A and 2 less on sample B, with about one seventh of their
tokens. With 54 questions, each score can be about 10 points off. With the `baseline` prompt, the memory
arm differed by 14.8 points between the two samples. In the cloud, a search took 361 ms at
p50 with the vector ranking, and 998 ms with the fused ranking.

## Documents

- [docs/contracts.md]: the record, the storage operations, the memory
  operations, and the platform facts behind them.
- [docs/guarantees.md]: each behavior that a consumer can rely on, its
  state, and its proof.
- [docs/benchmarks.md]: the numbers of hotmemory on LongMemEval-S,
  each with its configuration. [bench/README.md] tells you how to run it.
- [CONTRIBUTING.md]: the one command that checks a change, and the rules
  of the test suite.
- [CHANGELOG.md]: each change to a public surface.
- [docs/architecture.md]: how each operation runs on the Hotdata platform.

[CHANGELOG.md]: https://github.com/hotdata-dev/hotmemory/blob/main/CHANGELOG.md
[docs/contracts.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/contracts.md
[docs/benchmarks.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/benchmarks.md
[docs/guarantees.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/guarantees.md
[bench/README.md]: https://github.com/hotdata-dev/hotmemory/blob/main/bench/README.md
[CONTRIBUTING.md]: https://github.com/hotdata-dev/hotmemory/blob/main/CONTRIBUTING.md
[docs/architecture.md]: https://github.com/hotdata-dev/hotmemory/blob/main/docs/architecture.md
[skills/hotmemory/SKILL.md]: https://github.com/hotdata-dev/hotmemory/blob/main/skills/hotmemory/SKILL.md
