etchmem 0.1.2__tar.gz → 0.1.4__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.
- {etchmem-0.1.2 → etchmem-0.1.4}/PKG-INFO +10 -4
- {etchmem-0.1.2 → etchmem-0.1.4}/README.md +9 -3
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/consolidate/worker.py +22 -4
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/recall/recaller.py +12 -1
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem.egg-info/PKG-INFO +10 -4
- {etchmem-0.1.2 → etchmem-0.1.4}/pyproject.toml +1 -1
- {etchmem-0.1.2 → etchmem-0.1.4}/LICENSE +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/config.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/connector/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/connector/base.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/connector/remote.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/consolidate/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/engine.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/models.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/recall/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/stores/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/stores/base.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/stores/chroma_store.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/util/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/util/affectability.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem/util/hashing.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem.egg-info/SOURCES.txt +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem.egg-info/dependency_links.txt +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem.egg-info/requires.txt +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/etchmem.egg-info/top_level.txt +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/setup.cfg +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/tests/test_consolidate_idempotent.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/tests/test_formation_buckets.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/tests/test_recall.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/tests/test_reconsolidation.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.4}/tests/test_remember_recall.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: etchmem
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A pluggable, embeddable reconsolidating skill memory engine for AI agents
|
|
5
5
|
Author: Andrey Olishchuk
|
|
6
6
|
License: MIT
|
|
@@ -28,7 +28,13 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# etchmem
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
**Where agents grow up — not where they take notes.**
|
|
32
|
+
|
|
33
|
+
Modern AI agents are built from skills. Every time a skill runs, it leaves a trail: results, feedback, corrections, surprises. That trail is experience — and experience is how an agent learns how the world actually works.
|
|
34
|
+
|
|
35
|
+
`etchmem` is memory for that maturation. Not a scratchpad for the current session. Not a user profile to personalize replies. Not a long chat log you grep when context runs out. It is the layer where raw observations turn into durable understanding — how things work, what users react to, what failed last time — and keep sharpening every time the agent draws on it again.
|
|
36
|
+
|
|
37
|
+
Over time, knowledge compounds. The agent stops merely following scenarios and starts carrying a worldview shaped by use.
|
|
32
38
|
|
|
33
39
|
```bash
|
|
34
40
|
pip install etchmem
|
|
@@ -38,7 +44,7 @@ pip install etchmem
|
|
|
38
44
|
|
|
39
45
|
## What it is
|
|
40
46
|
|
|
41
|
-
`etchmem` gives your agent a persistent memory that
|
|
47
|
+
`etchmem` gives your agent a persistent memory that matures through living. You deposit raw observations — facts, outcomes, feedback — scoped to a skill or left general. You retrieve with natural-language queries. Periodically you consolidate, and the engine synthesizes scattered signal into compact knowledge articles, rewriting anything that has been recalled against fresh context.
|
|
42
48
|
|
|
43
49
|
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**.
|
|
44
50
|
|
|
@@ -52,7 +58,7 @@ Memory is kept in three tiers:
|
|
|
52
58
|
- **Buffer** — a working space for deposits and recall-events, waiting for the next consolidation run.
|
|
53
59
|
- **Injected** — synthesized knowledge articles, the primary search target. Only current knowledge is kept; superseded articles are hard-deleted.
|
|
54
60
|
|
|
55
|
-
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.
|
|
61
|
+
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. This is how a skill — or the agent as a whole — accumulates experience instead of starting from zero each run.
|
|
56
62
|
|
|
57
63
|
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`.
|
|
58
64
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# etchmem
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Where agents grow up — not where they take notes.**
|
|
4
|
+
|
|
5
|
+
Modern AI agents are built from skills. Every time a skill runs, it leaves a trail: results, feedback, corrections, surprises. That trail is experience — and experience is how an agent learns how the world actually works.
|
|
6
|
+
|
|
7
|
+
`etchmem` is memory for that maturation. Not a scratchpad for the current session. Not a user profile to personalize replies. Not a long chat log you grep when context runs out. It is the layer where raw observations turn into durable understanding — how things work, what users react to, what failed last time — and keep sharpening every time the agent draws on it again.
|
|
8
|
+
|
|
9
|
+
Over time, knowledge compounds. The agent stops merely following scenarios and starts carrying a worldview shaped by use.
|
|
4
10
|
|
|
5
11
|
```bash
|
|
6
12
|
pip install etchmem
|
|
@@ -10,7 +16,7 @@ pip install etchmem
|
|
|
10
16
|
|
|
11
17
|
## What it is
|
|
12
18
|
|
|
13
|
-
`etchmem` gives your agent a persistent memory that
|
|
19
|
+
`etchmem` gives your agent a persistent memory that matures through living. You deposit raw observations — facts, outcomes, feedback — scoped to a skill or left general. You retrieve with natural-language queries. Periodically you consolidate, and the engine synthesizes scattered signal into compact knowledge articles, rewriting anything that has been recalled against fresh context.
|
|
14
20
|
|
|
15
21
|
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**.
|
|
16
22
|
|
|
@@ -24,7 +30,7 @@ Memory is kept in three tiers:
|
|
|
24
30
|
- **Buffer** — a working space for deposits and recall-events, waiting for the next consolidation run.
|
|
25
31
|
- **Injected** — synthesized knowledge articles, the primary search target. Only current knowledge is kept; superseded articles are hard-deleted.
|
|
26
32
|
|
|
27
|
-
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.
|
|
33
|
+
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. This is how a skill — or the agent as a whole — accumulates experience instead of starting from zero each run.
|
|
28
34
|
|
|
29
35
|
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`.
|
|
30
36
|
|
|
@@ -25,6 +25,7 @@ from __future__ import annotations
|
|
|
25
25
|
import json
|
|
26
26
|
import time
|
|
27
27
|
from dataclasses import dataclass, field
|
|
28
|
+
from datetime import datetime, timezone
|
|
28
29
|
from typing import Any
|
|
29
30
|
|
|
30
31
|
from etchmem.config import Config
|
|
@@ -53,6 +54,9 @@ accurate knowledge article from a set of raw signal fragments.
|
|
|
53
54
|
## Instructions
|
|
54
55
|
- Write a single, cohesive knowledge article that captures all key facts.
|
|
55
56
|
- Do NOT invent facts not present in the fragments.
|
|
57
|
+
- Each fragment may be prefixed with a timestamp like `[21 Jun 2025, 15:25 UTC]`.
|
|
58
|
+
Preserve these timestamps verbatim inline, anchoring the facts they precede,
|
|
59
|
+
e.g. "As of 21 Jun 2025, 15:25 UTC, the deployment was rolled back."
|
|
56
60
|
- At the end of the article, output a JSON block on its own line:
|
|
57
61
|
{{"tags": {{"access_level": "...", "topic": "...", "product": "..."}}}}
|
|
58
62
|
|
|
@@ -74,6 +78,9 @@ accurate and incorporates the new information.
|
|
|
74
78
|
- Preserve ALL load-bearing facts from the existing article.
|
|
75
79
|
- Incorporate relevant new information from the fresh context.
|
|
76
80
|
- Remove outdated claims only when the fresh context explicitly supersedes them.
|
|
81
|
+
- Fresh context fragments may be prefixed with a timestamp like `[21 Jun 2025, 15:25 UTC]`.
|
|
82
|
+
Preserve these timestamps verbatim inline, anchoring the facts they precede.
|
|
83
|
+
Also carry forward any timestamps already present in the existing article.
|
|
77
84
|
- At the end, output a JSON block on its own line:
|
|
78
85
|
{{"tags": {{"access_level": "...", "topic": "...", "product": "..."}}}}
|
|
79
86
|
|
|
@@ -93,6 +100,13 @@ Reply with exactly one word: YES or NO.
|
|
|
93
100
|
"""
|
|
94
101
|
|
|
95
102
|
|
|
103
|
+
def _stamp(ts: float) -> str:
|
|
104
|
+
"""Format a Unix timestamp as a human-readable UTC string, e.g. '21 Jun 2025, 15:25 UTC'."""
|
|
105
|
+
if not ts:
|
|
106
|
+
return "unknown time"
|
|
107
|
+
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%-d %b %Y, %H:%M UTC")
|
|
108
|
+
|
|
109
|
+
|
|
96
110
|
def _parse_synthesis_output(raw: str) -> tuple[str, dict]:
|
|
97
111
|
"""Split LLM output into (article_text, tags_dict)."""
|
|
98
112
|
lines = raw.strip().split("\n")
|
|
@@ -351,8 +365,10 @@ class Worker:
|
|
|
351
365
|
return [item]
|
|
352
366
|
return []
|
|
353
367
|
|
|
354
|
-
# Synthesize
|
|
355
|
-
fragments = "\n\n---\n\n".join(
|
|
368
|
+
# Synthesize — prefix each fragment with its absolute UTC timestamp
|
|
369
|
+
fragments = "\n\n---\n\n".join(
|
|
370
|
+
f"[{_stamp(d.created_at)}]\n{d.content}" for d in cluster.deposits
|
|
371
|
+
)
|
|
356
372
|
prompt = _FORMATION_PROMPT.format(fragments=fragments)
|
|
357
373
|
raw = self._connector.complete(prompt)
|
|
358
374
|
article_text, tags = _parse_synthesis_output(raw)
|
|
@@ -396,8 +412,10 @@ class Worker:
|
|
|
396
412
|
current_articles[r.id] = item
|
|
397
413
|
break
|
|
398
414
|
|
|
399
|
-
# Fresh deposit content for the cluster
|
|
400
|
-
fresh_texts = [
|
|
415
|
+
# Fresh deposit content for the cluster — prefix with absolute UTC timestamps
|
|
416
|
+
fresh_texts = [
|
|
417
|
+
f"[{_stamp(d.created_at)}]\n{d.content}" for d in cluster.deposits
|
|
418
|
+
]
|
|
401
419
|
snapshots = [re.relational_snapshot for re in cluster.recall_events]
|
|
402
420
|
all_fresh = fresh_texts + snapshots
|
|
403
421
|
fresh_combined = "\n\n---\n\n".join(t for t in all_fresh if t.strip())
|
|
@@ -18,6 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
import time
|
|
20
20
|
import uuid
|
|
21
|
+
from datetime import datetime, timezone
|
|
21
22
|
from typing import Any
|
|
22
23
|
|
|
23
24
|
from etchmem.config import Config
|
|
@@ -87,13 +88,23 @@ class Recaller:
|
|
|
87
88
|
merged = self._merge(injected_results, relational_results, top_k)
|
|
88
89
|
|
|
89
90
|
# ── 4. Emit recall-event into buffer ──────────────────────────────
|
|
90
|
-
|
|
91
|
+
# Stamp each relational hit so time context survives into the snapshot
|
|
92
|
+
relational_snapshot = "\n\n".join(
|
|
93
|
+
f"[{self._stamp(r.created_at)}]\n{r.content}" for r in relational_results
|
|
94
|
+
)
|
|
91
95
|
self._emit_recall_event(query, relational_snapshot, skill, hint)
|
|
92
96
|
|
|
93
97
|
return merged
|
|
94
98
|
|
|
95
99
|
# ── Helpers ───────────────────────────────────────────────────────────
|
|
96
100
|
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _stamp(ts: float) -> str:
|
|
103
|
+
"""Format a Unix timestamp as a human-readable UTC string."""
|
|
104
|
+
if not ts:
|
|
105
|
+
return "unknown time"
|
|
106
|
+
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%-d %b %Y, %H:%M UTC")
|
|
107
|
+
|
|
97
108
|
def _merge(
|
|
98
109
|
self,
|
|
99
110
|
injected: list[SearchResult],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: etchmem
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A pluggable, embeddable reconsolidating skill memory engine for AI agents
|
|
5
5
|
Author: Andrey Olishchuk
|
|
6
6
|
License: MIT
|
|
@@ -28,7 +28,13 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# etchmem
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
**Where agents grow up — not where they take notes.**
|
|
32
|
+
|
|
33
|
+
Modern AI agents are built from skills. Every time a skill runs, it leaves a trail: results, feedback, corrections, surprises. That trail is experience — and experience is how an agent learns how the world actually works.
|
|
34
|
+
|
|
35
|
+
`etchmem` is memory for that maturation. Not a scratchpad for the current session. Not a user profile to personalize replies. Not a long chat log you grep when context runs out. It is the layer where raw observations turn into durable understanding — how things work, what users react to, what failed last time — and keep sharpening every time the agent draws on it again.
|
|
36
|
+
|
|
37
|
+
Over time, knowledge compounds. The agent stops merely following scenarios and starts carrying a worldview shaped by use.
|
|
32
38
|
|
|
33
39
|
```bash
|
|
34
40
|
pip install etchmem
|
|
@@ -38,7 +44,7 @@ pip install etchmem
|
|
|
38
44
|
|
|
39
45
|
## What it is
|
|
40
46
|
|
|
41
|
-
`etchmem` gives your agent a persistent memory that
|
|
47
|
+
`etchmem` gives your agent a persistent memory that matures through living. You deposit raw observations — facts, outcomes, feedback — scoped to a skill or left general. You retrieve with natural-language queries. Periodically you consolidate, and the engine synthesizes scattered signal into compact knowledge articles, rewriting anything that has been recalled against fresh context.
|
|
42
48
|
|
|
43
49
|
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**.
|
|
44
50
|
|
|
@@ -52,7 +58,7 @@ Memory is kept in three tiers:
|
|
|
52
58
|
- **Buffer** — a working space for deposits and recall-events, waiting for the next consolidation run.
|
|
53
59
|
- **Injected** — synthesized knowledge articles, the primary search target. Only current knowledge is kept; superseded articles are hard-deleted.
|
|
54
60
|
|
|
55
|
-
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.
|
|
61
|
+
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. This is how a skill — or the agent as a whole — accumulates experience instead of starting from zero each run.
|
|
56
62
|
|
|
57
63
|
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`.
|
|
58
64
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|