Metadata-Version: 2.4
Name: etchmem
Version: 0.1.1
Summary: A pluggable, embeddable reconsolidating skill memory engine for AI agents
Author: Andrey Olishchuk
License: MIT
Project-URL: Repository, https://github.com/andrey-olishchuk/etchmem
Keywords: llm,agent,memory,chromadb,rag,reconsolidation,skill,skills,etchmem
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: chromadb>=1.5.0
Requires-Dist: openai>=1.70.0
Requires-Dist: anthropic>=0.100.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Dynamic: license-file

# etchmem

A lightweight, embeddable memory engine for LLM agents. Agents remember things, recall them later, and the stored knowledge quietly matures through use — no extra plumbing required.

```bash
pip install etchmem
```

---

## What it is

`etchmem` gives your agent a persistent memory that improves over time. You deposit raw observations, retrieve them with natural-language queries, and periodically consolidate — at which point the engine synthesizes scattered signals into compact knowledge articles and rewrites anything that's been recalled against fresh context.

It is not a vector database and not an agent runtime. It sits above [ChromaDB](https://www.trychroma.com/), which handles all storage and embedding, and exposes exactly **three methods**.

---

## How it works

Memory is kept in three tiers:

- **Relational** — raw, append-only records with a TTL. Fresh signal lives here.
- **Buffer** — a working space for deposits and recall-events, waiting for the next consolidation run.
- **Injected** — synthesized knowledge articles, the primary search target. Only current knowledge is kept; superseded articles are hard-deleted.

The key idea is **reconsolidation**: every `recall()` call emits a recall-event into the buffer. When you later call `consolidate()`, the engine detects which injected articles were used, checks whether fresh signal changes anything, and rewrites them if needed. Knowledge that gets recalled stays current as a side-effect of being recalled. Knowledge nobody asks about simply rests.

Embeddings are computed locally by ChromaDB's built-in model — no external embedding API. Synthesis (the rewrite step inside `consolidate`) calls an LLM via `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.

---

## API

```python
from etchmem import Engine
engine = Engine()   # persists to .etchmem/ in the current directory
```

### `remember(data, hint=None, skill=None, metadata=None)`

Deposits a text record into the relational tier. No LLM call — cheap.

- `data` — the text to store.
- `hint` — optional float 0–1, your importance signal.
- `skill` — optional scope name; lets you filter recall by agent skill.
- `metadata` — arbitrary dict (source URL, tags, …).

### `recall(query, skill=None, top_k=None, hint=None) → list[SearchResult]`

Retrieves relevant knowledge and emits a recall-event for future reconsolidation. Results are blended from injected (primary) and relational (fresh signal), ranked by a composite score.

### `consolidate(num_records="all", method="LIFO") → dict`

Runs the consolidation worker: clusters the buffer, forms new injected articles, reconsolidates recalled articles against fresh context, hard-deletes superseded ones. Returns a summary dict with counts (`formed`, `reconsolidated`, `dropped`, `kept`, `flushed`, `superseded`). Requires an LLM API key.

---

## Hello world

```python
import os
from etchmem import Engine

os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..."   # or OPENAI_API_KEY

engine = Engine()

# Add some facts
engine.remember("The Eiffel Tower is 330 metres tall including its antenna.")
engine.remember("The tower was completed in 1889 and was originally intended to be temporary.")
engine.remember("It receives about 7 million visitors per year, making it the world's most visited paid monument.")

# Retrieve relevant knowledge
results = engine.recall("How tall is the Eiffel Tower?")
for r in results:
    print(r.score, r.content)

# Consolidate — synthesizes the raw deposits into a compact knowledge article
summary = engine.consolidate()
print(summary)
# {'formed': 1, 'reconsolidated': 0, 'dropped': 0, 'kept': 0, 'flushed': 3, 'superseded': 0}

# Subsequent recalls now hit the synthesized article,
# and any new signal deposited before the next consolidate()
# will be blended in during reconsolidation.
results = engine.recall("Eiffel Tower visitors")
print(results[0].content)
```

---

## License

MIT
