etchmem 0.1.2__tar.gz → 0.1.3__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.3}/PKG-INFO +1 -1
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/consolidate/worker.py +22 -4
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/recall/recaller.py +12 -1
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem.egg-info/PKG-INFO +1 -1
- {etchmem-0.1.2 → etchmem-0.1.3}/pyproject.toml +1 -1
- {etchmem-0.1.2 → etchmem-0.1.3}/LICENSE +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/README.md +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/config.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/connector/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/connector/base.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/connector/remote.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/consolidate/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/engine.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/models.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/recall/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/stores/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/stores/base.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/stores/chroma_store.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/util/__init__.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/util/affectability.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem/util/hashing.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem.egg-info/SOURCES.txt +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem.egg-info/dependency_links.txt +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem.egg-info/requires.txt +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/etchmem.egg-info/top_level.txt +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/setup.cfg +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/tests/test_consolidate_idempotent.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/tests/test_formation_buckets.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/tests/test_recall.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/tests/test_reconsolidation.py +0 -0
- {etchmem-0.1.2 → etchmem-0.1.3}/tests/test_remember_recall.py +0 -0
|
@@ -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],
|
|
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
|
|
File without changes
|