foldkeep 0.9.2__tar.gz → 0.9.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.
- {foldkeep-0.9.2 → foldkeep-0.9.3}/PKG-INFO +10 -1
- {foldkeep-0.9.2 → foldkeep-0.9.3}/README.md +10 -1
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/session.py +80 -8
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep.egg-info/PKG-INFO +10 -1
- {foldkeep-0.9.2 → foldkeep-0.9.3}/pyproject.toml +1 -1
- {foldkeep-0.9.2 → foldkeep-0.9.3}/LICENSE +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/__init__.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/__main__.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/cli.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/core/__init__.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/core/compressor.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/diff.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/estimate.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/mcp_server.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/memory/__init__.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/memory/store.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/packing.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep/selftest.py +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep.egg-info/SOURCES.txt +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep.egg-info/dependency_links.txt +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/foldkeep.egg-info/top_level.txt +0 -0
- {foldkeep-0.9.2 → foldkeep-0.9.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foldkeep
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: Zero-dependency lossless context folding for agent sessions: pinned facts, tolerance routing, byte-for-byte expand.
|
|
5
5
|
Author: foldkeep contributors
|
|
6
6
|
License: MIT License
|
|
@@ -37,6 +37,9 @@ Dynamic: license-file
|
|
|
37
37
|
|
|
38
38
|
# foldkeep
|
|
39
39
|
|
|
40
|
+
[](https://pypi.org/project/foldkeep/)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
|
|
40
43
|
**Fold the context. Keep the facts.**
|
|
41
44
|
|
|
42
45
|
foldkeep is a zero-dependency, deterministic context-compression engine for
|
|
@@ -46,6 +49,12 @@ you expand ANY turn back byte-for-byte, even after rolling and merging.
|
|
|
46
49
|
|
|
47
50
|
Pure Python stdlib. No LLM calls. Millisecond latency. SQLite-backed.
|
|
48
51
|
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install foldkeep
|
|
56
|
+
```
|
|
57
|
+
|
|
49
58
|
## Why
|
|
50
59
|
|
|
51
60
|
Agent transcripts grow until the model drowns. Naive fixes lose the past:
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# foldkeep
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/foldkeep/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
3
6
|
**Fold the context. Keep the facts.**
|
|
4
7
|
|
|
5
8
|
foldkeep is a zero-dependency, deterministic context-compression engine for
|
|
@@ -9,6 +12,12 @@ you expand ANY turn back byte-for-byte, even after rolling and merging.
|
|
|
9
12
|
|
|
10
13
|
Pure Python stdlib. No LLM calls. Millisecond latency. SQLite-backed.
|
|
11
14
|
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install foldkeep
|
|
19
|
+
```
|
|
20
|
+
|
|
12
21
|
## Why
|
|
13
22
|
|
|
14
23
|
Agent transcripts grow until the model drowns. Naive fixes lose the past:
|
|
@@ -77,4 +86,4 @@ v0.9.2. Research notes (`RESEARCH.md`) document the competitive landscape
|
|
|
77
86
|
(LLMLingua, leanctx, mnesis, FoldAgent, pi-fold, ECC, hermes-agent) and what
|
|
78
87
|
we learned from each.
|
|
79
88
|
|
|
80
|
-
MIT licensed. Windows/macOS/Linux, Python 3.10+.
|
|
89
|
+
MIT licensed. Windows/macOS/Linux, Python 3.10+.
|
|
@@ -91,6 +91,19 @@ def _conn():
|
|
|
91
91
|
c.execute("""CREATE TABLE IF NOT EXISTS originals (
|
|
92
92
|
name TEXT NOT NULL, seq INTEGER NOT NULL,
|
|
93
93
|
text TEXT NOT NULL, PRIMARY KEY (name, seq))""")
|
|
94
|
+
# v0.9.3: entity frequency/recency stats (ECC instinct-style ranking)
|
|
95
|
+
c.execute("""CREATE TABLE IF NOT EXISTS entity_stats (
|
|
96
|
+
name TEXT NOT NULL, ent TEXT NOT NULL,
|
|
97
|
+
freq INTEGER NOT NULL DEFAULT 0,
|
|
98
|
+
last_seq INTEGER NOT NULL DEFAULT 0,
|
|
99
|
+
PRIMARY KEY (name, ent))""")
|
|
100
|
+
# v0.9.3: FTS5 mirror of originals (hermes-style session search);
|
|
101
|
+
# originals are immutable so the index never needs updating
|
|
102
|
+
try:
|
|
103
|
+
c.execute("CREATE VIRTUAL TABLE IF NOT EXISTS search_fts "
|
|
104
|
+
"USING fts5(name, seq, text)")
|
|
105
|
+
except sqlite3.OperationalError:
|
|
106
|
+
pass # FTS5 unavailable -> search() falls back to LIKE
|
|
94
107
|
return c
|
|
95
108
|
|
|
96
109
|
_FENCE_RE = re.compile(r"```|~~~")
|
|
@@ -120,6 +133,12 @@ def new(name, project="default"):
|
|
|
120
133
|
c.execute("DELETE FROM turns WHERE name=?", (name,))
|
|
121
134
|
c.execute("DELETE FROM entities WHERE name=?", (name,))
|
|
122
135
|
c.execute("DELETE FROM facts WHERE name=?", (name,))
|
|
136
|
+
c.execute("DELETE FROM entity_stats WHERE name=?", (name,))
|
|
137
|
+
c.execute("DELETE FROM originals WHERE name=?", (name,))
|
|
138
|
+
try:
|
|
139
|
+
c.execute("DELETE FROM search_fts WHERE name=?", (name,))
|
|
140
|
+
except sqlite3.OperationalError:
|
|
141
|
+
pass
|
|
123
142
|
return name
|
|
124
143
|
|
|
125
144
|
def _rows(name):
|
|
@@ -127,7 +146,9 @@ def _rows(name):
|
|
|
127
146
|
return c.execute("SELECT sid,seq,role,kind,text,orig_chars FROM turns "
|
|
128
147
|
"WHERE name=? ORDER BY seq", (name,)).fetchall()
|
|
129
148
|
|
|
130
|
-
def push(name, role, text, roll_threshold=
|
|
149
|
+
def push(name, role, text, roll_threshold=None):
|
|
150
|
+
if roll_threshold is None:
|
|
151
|
+
roll_threshold = _env_int("OC_ROLL_THRESHOLD", 2600)
|
|
131
152
|
with _conn() as c:
|
|
132
153
|
mx = c.execute("SELECT COALESCE(MAX(seq),0) FROM turns WHERE name=?",
|
|
133
154
|
(name,)).fetchone()[0]
|
|
@@ -135,6 +156,11 @@ def push(name, role, text, roll_threshold=2600):
|
|
|
135
156
|
"created,orig_text) VALUES (?,?,?,?,?,?,?,?)",
|
|
136
157
|
(name, mx + 1, role, "raw", text, len(text), time.time(),
|
|
137
158
|
text))
|
|
159
|
+
try:
|
|
160
|
+
c.execute("INSERT INTO search_fts (name, seq, text) "
|
|
161
|
+
"VALUES (?,?,?)", (name, mx + 1, text))
|
|
162
|
+
except sqlite3.OperationalError:
|
|
163
|
+
pass
|
|
138
164
|
# pin a LEAN fact line: context windows around entities/numbers
|
|
139
165
|
# of the salient sentence -- exact substrings survive, filler
|
|
140
166
|
# does not; render shows it only for out-of-body turns
|
|
@@ -189,6 +215,21 @@ def _rollup(name, threshold):
|
|
|
189
215
|
# entity registry: rolled-away turns keep entity facts reachable
|
|
190
216
|
for e in ents:
|
|
191
217
|
c.execute("INSERT OR IGNORE INTO entities VALUES (?,?)", (name, e))
|
|
218
|
+
# v0.9.3 frequency/recency stats for relevance-ranked injection
|
|
219
|
+
from collections import Counter
|
|
220
|
+
cnt, last = Counter(), {}
|
|
221
|
+
for r in rows[:cut]:
|
|
222
|
+
if _intolerant(r[4]):
|
|
223
|
+
continue
|
|
224
|
+
for e in set(ENT_RE.findall(r[4])):
|
|
225
|
+
cnt[e] += 1
|
|
226
|
+
last[e] = max(last.get(e, 0), r[1])
|
|
227
|
+
for e, f in cnt.items():
|
|
228
|
+
c.execute("INSERT INTO entity_stats VALUES (?,?,?,?) "
|
|
229
|
+
"ON CONFLICT(name, ent) DO UPDATE SET "
|
|
230
|
+
"freq=freq+excluded.freq, "
|
|
231
|
+
"last_seq=MAX(last_seq, excluded.last_seq)",
|
|
232
|
+
(name, e, f, last[e]))
|
|
192
233
|
_consolidate(name)
|
|
193
234
|
return n
|
|
194
235
|
|
|
@@ -235,6 +276,20 @@ def _registry(name, cap=None):
|
|
|
235
276
|
if cap is None:
|
|
236
277
|
cap = _env_int("OC_REGISTRY_MAX", 48)
|
|
237
278
|
with _conn() as c:
|
|
279
|
+
# v0.9.3 relevance ranking (ECC instinct-style), TIERED so the
|
|
280
|
+
# old SCREAMING-first guarantee still holds: SCREAMING_CASE
|
|
281
|
+
# entities always outrank lowercase ones; within a tier,
|
|
282
|
+
# frequency x recency decides, alphabetical tiebreak.
|
|
283
|
+
# (Pure freq ranking regressed the marathon: high-freq lowercase
|
|
284
|
+
# filler evicted low-freq SCREAMING checkpoints from the cap.)
|
|
285
|
+
rows = c.execute(
|
|
286
|
+
"SELECT ent, freq, last_seq FROM entity_stats WHERE name=?"
|
|
287
|
+
, (name,)).fetchall()
|
|
288
|
+
if rows:
|
|
289
|
+
rows.sort(key=lambda r: (
|
|
290
|
+
0 if re.match(r"^[A-Z][A-Z0-9_]{2,}$", r[0]) else 1,
|
|
291
|
+
-r[1], -r[2], r[0]))
|
|
292
|
+
return [r[0] for r in rows][:cap]
|
|
238
293
|
rows = c.execute("SELECT DISTINCT ent FROM entities WHERE name=?",
|
|
239
294
|
(name,)).fetchall()
|
|
240
295
|
ents = [r[0] for r in rows]
|
|
@@ -371,15 +426,32 @@ def search(name, query, limit=10):
|
|
|
371
426
|
search): substring match over verbatim originals + pinned facts,
|
|
372
427
|
newest first. Works even for turns that were rolled away or merged,
|
|
373
428
|
because originals survive (v0.9 lossless base)."""
|
|
374
|
-
pat = "%" + query.replace("%", "").replace("_", " ") + "%"
|
|
375
429
|
like = "%" + query.replace("%", "") + "%"
|
|
376
430
|
with _conn() as c:
|
|
377
|
-
rows =
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
431
|
+
rows = None
|
|
432
|
+
try:
|
|
433
|
+
# v0.9.3 FTS5 over the originals mirror (hermes-style);
|
|
434
|
+
# every term ANDed, quoted to avoid syntax issues
|
|
435
|
+
match = " ".join('"%s"' % t.replace('"', "")
|
|
436
|
+
for t in query.split() if t)
|
|
437
|
+
if match:
|
|
438
|
+
rows = c.execute(
|
|
439
|
+
"SELECT f.seq, COALESCE(t.role,'system'), f.text "
|
|
440
|
+
"FROM search_fts f LEFT JOIN turns t "
|
|
441
|
+
"ON t.name=f.name AND t.seq=f.seq "
|
|
442
|
+
"WHERE search_fts MATCH ? AND f.name=? "
|
|
443
|
+
"ORDER BY f.seq DESC LIMIT ?",
|
|
444
|
+
(match, name, limit)).fetchall()
|
|
445
|
+
except sqlite3.OperationalError:
|
|
446
|
+
rows = None
|
|
447
|
+
if rows is None:
|
|
448
|
+
pat = "%" + query.replace("%", "").replace("_", " ") + "%"
|
|
449
|
+
rows = c.execute(
|
|
450
|
+
"SELECT seq, role, COALESCE(orig_text, text) FROM turns "
|
|
451
|
+
"WHERE name=? AND (COALESCE(orig_text, text) LIKE ? "
|
|
452
|
+
"OR COALESCE(orig_text, text) LIKE ?) "
|
|
453
|
+
"ORDER BY seq DESC LIMIT ?",
|
|
454
|
+
(name, like, pat, limit)).fetchall()
|
|
383
455
|
facts = c.execute(
|
|
384
456
|
"SELECT seq, text FROM facts WHERE name=? AND text LIKE ? "
|
|
385
457
|
"ORDER BY seq DESC LIMIT ?", (name, like, limit)).fetchall()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foldkeep
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: Zero-dependency lossless context folding for agent sessions: pinned facts, tolerance routing, byte-for-byte expand.
|
|
5
5
|
Author: foldkeep contributors
|
|
6
6
|
License: MIT License
|
|
@@ -37,6 +37,9 @@ Dynamic: license-file
|
|
|
37
37
|
|
|
38
38
|
# foldkeep
|
|
39
39
|
|
|
40
|
+
[](https://pypi.org/project/foldkeep/)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
|
|
40
43
|
**Fold the context. Keep the facts.**
|
|
41
44
|
|
|
42
45
|
foldkeep is a zero-dependency, deterministic context-compression engine for
|
|
@@ -46,6 +49,12 @@ you expand ANY turn back byte-for-byte, even after rolling and merging.
|
|
|
46
49
|
|
|
47
50
|
Pure Python stdlib. No LLM calls. Millisecond latency. SQLite-backed.
|
|
48
51
|
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install foldkeep
|
|
56
|
+
```
|
|
57
|
+
|
|
49
58
|
## Why
|
|
50
59
|
|
|
51
60
|
Agent transcripts grow until the model drowns. Naive fixes lose the past:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "foldkeep"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.3"
|
|
8
8
|
description = "Zero-dependency lossless context folding for agent sessions: pinned facts, tolerance routing, byte-for-byte expand."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
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
|