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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foldkeep
3
- Version: 0.9.2
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
+ [![PyPI](https://img.shields.io/pypi/v/foldkeep)](https://pypi.org/project/foldkeep/)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
+ [![PyPI](https://img.shields.io/pypi/v/foldkeep)](https://pypi.org/project/foldkeep/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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=2600):
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 = c.execute(
378
- "SELECT seq, role, COALESCE(orig_text, text) FROM turns "
379
- "WHERE name=? AND (COALESCE(orig_text, text) LIKE ? "
380
- "OR COALESCE(orig_text, text) LIKE ?) "
381
- "ORDER BY seq DESC LIMIT ?",
382
- (name, like, pat, limit)).fetchall()
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.2
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
+ [![PyPI](https://img.shields.io/pypi/v/foldkeep)](https://pypi.org/project/foldkeep/)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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.2"
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