kryth 0.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of kryth might be problematic. Click here for more details.

kryth/__init__.py ADDED
@@ -0,0 +1,31 @@
1
+ """Kryth — AI Agent Memory Framework.
2
+
3
+ Kryth provides structured memory layers for LLM-based agents, including
4
+ write, retrieval, duplicate detection, and compression controllers.
5
+
6
+ Typical usage::
7
+
8
+ from kryth.agent.memory.controllers import (
9
+ WriteController,
10
+ RetrievalController,
11
+ DuplicateDetector,
12
+ CompressionController,
13
+ compute_state_hash,
14
+ )
15
+ """
16
+
17
+ from kryth.agent.memory.controllers import (
18
+ CompressionController,
19
+ DuplicateDetector,
20
+ RetrievalController,
21
+ WriteController,
22
+ compute_state_hash,
23
+ )
24
+
25
+ __all__ = [
26
+ "WriteController",
27
+ "RetrievalController",
28
+ "DuplicateDetector",
29
+ "CompressionController",
30
+ "compute_state_hash",
31
+ ]
@@ -0,0 +1 @@
1
+ """Agent memory subpackage for Kryth."""
@@ -0,0 +1,21 @@
1
+ """Memory controllers subpackage for Kryth.
2
+
3
+ Exports: WriteController, RetrievalController, DuplicateDetector,
4
+ CompressionController, compute_state_hash.
5
+ """
6
+
7
+ from kryth.agent.memory.controllers import (
8
+ CompressionController,
9
+ DuplicateDetector,
10
+ RetrievalController,
11
+ WriteController,
12
+ compute_state_hash,
13
+ )
14
+
15
+ __all__ = [
16
+ "WriteController",
17
+ "RetrievalController",
18
+ "DuplicateDetector",
19
+ "CompressionController",
20
+ "compute_state_hash",
21
+ ]
@@ -0,0 +1,415 @@
1
+ """Memory controllers — Write, Retrieval, DuplicateDetector, Compression.
2
+
3
+ Every tool call that produces knowledge must go through WriteController.
4
+ Every prompt injection must go through RetrievalController.
5
+ Every duplicate check must go through DuplicateDetector.
6
+ Memory growth is managed by CompressionController.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import os
13
+ import re
14
+ import subprocess
15
+ import time
16
+ from pathlib import Path
17
+ from typing import Any, Optional
18
+
19
+
20
+ # ── State hash ──────────────────────────────────────────────────────────
21
+
22
+ def compute_state_hash(cwd: str = "") -> str:
23
+ """Compute a robust state hash for duplicate command detection.
24
+
25
+ Includes:
26
+ 1. git diff (staged + unstaged)
27
+ 2. modified file hashes (up to 20)
28
+ 3. dependency lock file hashes
29
+ 4. environment hash (selected vars)
30
+ 5. working directory
31
+ """
32
+ hasher = hashlib.sha256()
33
+ cwd = cwd or os.getcwd()
34
+ hasher.update(cwd.encode())
35
+
36
+ # 1. Git diff
37
+ try:
38
+ diff = subprocess.run(
39
+ ["git", "diff", "--no-color"],
40
+ capture_output=True, text=True, timeout=5, cwd=cwd,
41
+ )
42
+ hasher.update(diff.stdout.encode()[:50000])
43
+ diff_staged = subprocess.run(
44
+ ["git", "diff", "--no-color", "--cached"],
45
+ capture_output=True, text=True, timeout=5, cwd=cwd,
46
+ )
47
+ hasher.update(diff_staged.stdout.encode()[:50000])
48
+ except Exception:
49
+ pass
50
+
51
+ # 2. Modified file hashes (up to 20 files by mtime)
52
+ try:
53
+ lock_files = [
54
+ "requirements.txt", "Pipfile", "Pipfile.lock",
55
+ "pyproject.toml", "poetry.lock",
56
+ "package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
57
+ "Cargo.toml", "Cargo.lock",
58
+ "go.mod", "go.sum",
59
+ "Gemfile", "Gemfile.lock",
60
+ "composer.json", "composer.lock",
61
+ ]
62
+ counted = 0
63
+ for fname in lock_files:
64
+ fpath = Path(cwd) / fname
65
+ if fpath.is_file():
66
+ try:
67
+ h = hashlib.sha256(fpath.read_bytes()).hexdigest()[:16]
68
+ hasher.update(f"{fname}:{h}".encode())
69
+ counted += 1
70
+ except Exception:
71
+ pass
72
+ # Recent modified files
73
+ for f in sorted(Path(cwd).iterdir(), key=lambda p: p.stat().st_mtime if p.is_file() else 0, reverse=True)[:20]:
74
+ if f.is_file() and f.suffix in (".py", ".js", ".ts", ".jsx", ".tsx", ".go", ".rs", ".java"):
75
+ try:
76
+ h = hashlib.sha256(f.read_bytes()).hexdigest()[:16]
77
+ hasher.update(f"{f.name}:{h}".encode())
78
+ counted += 1
79
+ except Exception:
80
+ pass
81
+ except Exception:
82
+ pass
83
+
84
+ # 3. Environment hash (selected vars — not secrets)
85
+ for var in ("VIRTUAL_ENV", "CONDA_PREFIX", "NODE_ENV", "PYTHON_VERSION", "NODE_VERSION"):
86
+ val = os.environ.get(var, "")
87
+ if val:
88
+ hasher.update(f"{var}={val}".encode())
89
+
90
+ return hasher.hexdigest()[:16]
91
+
92
+
93
+ # ── Duplicate Detector ──────────────────────────────────────────────────
94
+
95
+ class DuplicateDetector:
96
+ """Soft duplicate detection — returns memory summaries, never hard-blocks.
97
+
98
+ Rules:
99
+ - Same file + same hash → return memory summary, allow reread
100
+ - Same command + same cwd + same state_hash → return previous result, allow rerun
101
+ """
102
+
103
+ def check_read(self, repo_memory, session_id: int, path: str) -> Optional[dict]:
104
+ """Check if file was already read. Returns a memory hit dict or None."""
105
+ file_rec = repo_memory.get_file(session_id, path)
106
+ if not file_rec:
107
+ return None
108
+ try:
109
+ with open(path, "rb") as f:
110
+ current_hash = hashlib.sha256(f.read()).hexdigest()[:16]
111
+ if current_hash != file_rec.hash:
112
+ return None # file changed, not a duplicate
113
+ except Exception:
114
+ return None
115
+
116
+ summary = self._build_read_summary(file_rec)
117
+ return {
118
+ "duplicate": True,
119
+ "type": "read",
120
+ "path": path,
121
+ "turn": file_rec.last_read_turn,
122
+ "read_count": file_rec.read_count,
123
+ "summary": summary,
124
+ }
125
+
126
+ def _build_read_summary(self, rec) -> str:
127
+ """Build a concise semantic summary of what's already known about a file."""
128
+ parts = [f"📄 {rec.path} ({rec.language}, {rec.lines} lines, read #{rec.read_count})"]
129
+ if rec.purpose:
130
+ parts.append(f" Purpose: {rec.purpose[:80]}")
131
+ if rec.functions:
132
+ parts.append(f" Functions: {', '.join(rec.functions[:8])}")
133
+ if rec.classes:
134
+ parts.append(f" Classes: {', '.join(rec.classes[:5])}")
135
+ if rec.routes:
136
+ parts.append(f" Routes: {', '.join(rec.routes[:8])}")
137
+ if rec.imports:
138
+ parts.append(f" Imports: {', '.join(rec.imports[:6])}")
139
+ return "\n".join(parts)
140
+
141
+ def check_command(
142
+ self, execution_memory, session_id: int, command: str, cwd: str, state_hash: str = ""
143
+ ) -> Optional[dict]:
144
+ """Check if identical command was already run. Returns a memory hit dict or None."""
145
+ dup = execution_memory.find_duplicate(session_id, command, cwd, state_hash)
146
+ if dup is None:
147
+ return None
148
+ return {
149
+ "duplicate": True,
150
+ "type": "command",
151
+ "command": command[:80],
152
+ "exit_code": dup.exit_code,
153
+ "status": "success" if dup.exit_code == 0 else "failed",
154
+ "previous_output": dup.output_summary[:200],
155
+ "run_count": dup.run_count,
156
+ }
157
+
158
+
159
+ # ── Write Controller ────────────────────────────────────────────────────
160
+
161
+ class WriteController:
162
+ """Single writer for all memory layers.
163
+
164
+ Tool → Memory mapping:
165
+ read_file → RepoMemory
166
+ run_command → ExecutionMemory (with state_hash)
167
+ edit_file/multi_edit → RepoMemory (invalidate) + EpisodicMemory
168
+ run_tests → EpisodicMemory
169
+ """
170
+
171
+ def on_tool_result(
172
+ self,
173
+ tool_name: str,
174
+ args: dict,
175
+ result: Any,
176
+ error: bool,
177
+ session_id: int,
178
+ memory_manager,
179
+ turn: int,
180
+ ) -> None:
181
+ if error:
182
+ return
183
+
184
+ if tool_name == "read_file":
185
+ path = args.get("path", "")
186
+ if path and result:
187
+ content = str(result)
188
+ memory_manager.repo.add_file(session_id, path, content, turn)
189
+ memory_manager.working.add_finding(f"Read: {path}")
190
+
191
+ elif tool_name in ("run_command", "shell_exec"):
192
+ command = args.get("command", "") or args.get("cmd", "")
193
+ cwd = args.get("cwd") or os.getcwd()
194
+ exit_code = 0
195
+ output_summary = str(result)[:500] if result else ""
196
+ if "ERROR" in output_summary.upper() or "FAILED" in output_summary.upper():
197
+ exit_code = 1
198
+ env_hash = hashlib.sha256(str(sorted(os.environ.items())).encode()).hexdigest()[:16]
199
+ state_hash = compute_state_hash(cwd)
200
+ memory_manager.execution.record(
201
+ session_id, command, cwd, exit_code, output_summary,
202
+ env_hash=env_hash, state_hash=state_hash,
203
+ )
204
+
205
+ elif tool_name in ("edit_file", "multi_edit"):
206
+ path = args.get("path", "")
207
+ if path:
208
+ memory_manager.repo.invalidate_file(session_id, path)
209
+ desc = args.get("oldString", "")[:60] or args.get("newString", "")[:60]
210
+ desc = desc or f"edit {os.path.basename(path)}"
211
+ memory_manager.episodic.add_edit(session_id, path, desc)
212
+
213
+ elif tool_name == "write_file":
214
+ path = args.get("path", "")
215
+ if path:
216
+ memory_manager.repo.invalidate_file(session_id, path)
217
+ memory_manager.episodic.add_edit(session_id, path, "wrote file")
218
+
219
+ elif tool_name in ("run_tests",):
220
+ memory_manager.episodic.add_result(session_id, str(result)[:200] if result else "tests completed")
221
+
222
+ def set_objective(self, memory_manager, session_id: int, objective: str) -> None:
223
+ memory_manager.working.set_objective(objective)
224
+ if session_id:
225
+ try:
226
+ memory_manager.episodic.set_objective(session_id, objective)
227
+ except Exception:
228
+ pass
229
+
230
+
231
+ # ── Retrieval Controller ────────────────────────────────────────────────
232
+
233
+ class RetrievalController:
234
+ """Retrieve only relevant memory for LLM context injection.
235
+
236
+ Strictly capped at 800 tokens (~3200 chars).
237
+ Priority order:
238
+ 1. Current objective
239
+ 2. WorkingMemory findings + blockers
240
+ 3. Top repo memory hits (ranked by importance)
241
+ 4. Recent command results
242
+ 5. Critical episodic findings
243
+ """
244
+
245
+ MAX_CHARS = 3200 # ~800 tokens
246
+
247
+ # Keyword → relevance mapping for scoring repo files
248
+ _RELEVANCE_KEYWORDS = {
249
+ "auth": ["login", "auth", "oauth", "session", "token", "password", "user", "permission", "jwt"],
250
+ "api": ["route", "api", "endpoint", "rest", "controller", "handler", "middleware"],
251
+ "db": ["db", "database", "sql", "model", "schema", "migration", "query", "orm"],
252
+ "config": ["config", "setting", "env", "yaml", "json", "toml", "ini"],
253
+ "test": ["test", "spec", "mock", "assert", "pytest", "jest"],
254
+ "frontend": ["component", "react", "vue", "svelte", "template", "css", "html", "ui"],
255
+ "build": ["build", "webpack", "vite", "rollup", "babel", "tsconfig", "make"],
256
+ "deploy": ["deploy", "docker", "ci", "cd", "pipeline", "action", "workflow"],
257
+ }
258
+
259
+ def build_prompt_block(
260
+ self,
261
+ memory_manager,
262
+ session_id: int,
263
+ include_long_term: bool = False,
264
+ user_input: str = "",
265
+ ) -> str:
266
+ parts = []
267
+
268
+ # 1. Current objective (always included, highest priority)
269
+ wm = memory_manager.working.to_prompt_block(max_chars=600)
270
+ if wm:
271
+ parts.append(wm)
272
+
273
+ remaining = self.MAX_CHARS - sum(len(p) + 2 for p in parts)
274
+
275
+ # 2. Repo memory — ranked by importance + relevance
276
+ repo = self._get_repo_block(memory_manager, session_id, user_input, max_chars=min(remaining, 1200))
277
+ if repo:
278
+ parts.append(repo)
279
+ remaining = self.MAX_CHARS - sum(len(p) + 2 for p in parts)
280
+
281
+ # 3. Recent command results (compact)
282
+ exec_block = memory_manager.execution.get_prompt_block(session_id)
283
+ if exec_block:
284
+ if len(exec_block) + 2 > remaining:
285
+ exec_lines = exec_block.splitlines()
286
+ exec_block = "\n".join(exec_lines[:4]) # keep only header + 3 results
287
+ if len(exec_block) + 2 > remaining:
288
+ exec_block = exec_block[:remaining - 2]
289
+ if exec_block:
290
+ parts.append(exec_block)
291
+ remaining = self.MAX_CHARS - sum(len(p) + 2 for p in parts)
292
+
293
+ # 4. Episodic findings (compact)
294
+ ep = memory_manager.episodic.get_prompt_block(max_chars=min(remaining, 800))
295
+ if ep:
296
+ parts.append(ep)
297
+ remaining = self.MAX_CHARS - sum(len(p) + 2 for p in parts)
298
+
299
+ # 5. Long-term memory (if requested, lowest priority)
300
+ if include_long_term and remaining > 200:
301
+ ltm = memory_manager.long_term.to_prompt_block(max_chars=min(remaining, 400))
302
+ if ltm:
303
+ parts.append(ltm)
304
+
305
+ block = "\n\n".join(parts)
306
+ if len(block) > self.MAX_CHARS:
307
+ block = block[:self.MAX_CHARS] + "\n... (truncated)"
308
+ return block
309
+
310
+ def _get_repo_block(
311
+ self, memory_manager, session_id: int, user_input: str, max_chars: int
312
+ ) -> str:
313
+ """Retrieve repo memory ranked by importance + relevance."""
314
+ mem = memory_manager.repo._get(session_id)
315
+ if not mem.files:
316
+ return ""
317
+
318
+ # Score files by relevance
319
+ query_tokens = set(re.findall(r'\w+', user_input.lower())) if user_input else set()
320
+ scored = []
321
+ for path, rec in mem.files.items():
322
+ relevance = self._compute_relevance(rec, query_tokens) if query_tokens else 0.5
323
+ score = 0.7 * rec.importance_score + 0.3 * relevance
324
+ scored.append((score, path, rec))
325
+
326
+ # Sort by score descending
327
+ scored.sort(key=lambda x: -x[0])
328
+
329
+ parts = ["KNOWN REPO STATE"]
330
+ if mem.framework:
331
+ parts.append(f"Framework: {mem.framework}")
332
+
333
+ # Top files by score
334
+ file_lines = []
335
+ for score, path, rec in scored[:8]:
336
+ desc = f" {path} ({rec.language}, {rec.lines} lines, imp={score:.2f})"
337
+ if rec.purpose:
338
+ desc += f" — {rec.purpose[:50]}"
339
+ file_lines.append(desc)
340
+ if file_lines:
341
+ parts.append(f"Files ({len(mem.files)}):\n" + "\n".join(file_lines))
342
+
343
+ # Key symbols from top files
344
+ sym_lines = []
345
+ for score, path, rec in scored[:5]:
346
+ syms = (rec.functions + rec.classes)[:6]
347
+ if syms:
348
+ sym_lines.append(f" {path}: {', '.join(syms)}")
349
+ if sym_lines:
350
+ parts.append("Key symbols:\n" + "\n".join(sym_lines))
351
+
352
+ if mem.entrypoints:
353
+ parts.append("Entry points:\n " + "\n ".join(mem.entrypoints[:3]))
354
+
355
+ if mem.routes:
356
+ parts.append("Routes:\n " + ", ".join(mem.routes[:10]))
357
+
358
+ block = "\n\n".join(parts)
359
+ if len(block) > max_chars:
360
+ return block[:max_chars] + "\n... (truncated)"
361
+ return block
362
+
363
+ @staticmethod
364
+ def _compute_relevance(rec, query_tokens: set) -> float:
365
+ """Score how relevant a file record is to the user's query tokens."""
366
+ if not query_tokens:
367
+ return 0.0
368
+ text = f"{rec.path} {rec.purpose} {' '.join(rec.functions)} {' '.join(rec.classes)} {' '.join(rec.imports)}".lower()
369
+ matches = sum(1 for t in query_tokens if t in text or any(t in w for w in text.split()))
370
+ return min(1.0, matches / max(1, len(query_tokens)))
371
+
372
+ def get_priority_ranking(
373
+ self,
374
+ memory_manager,
375
+ session_id: int,
376
+ user_input: str = "",
377
+ ) -> dict[str, str]:
378
+ return {
379
+ "objective": memory_manager.working.objective,
380
+ "repo_files": str(len(memory_manager.repo._get(session_id).files)),
381
+ "recent_commands": str(len(memory_manager.execution._get(session_id).commands)),
382
+ "findings": str(len(memory_manager.episodic._get(session_id).root_causes)),
383
+ }
384
+
385
+
386
+ # ── Compression Controller ──────────────────────────────────────────────
387
+
388
+ class CompressionController:
389
+ """Prevent uncontrolled memory growth."""
390
+
391
+ def compress_episodic(self, episodic_memory, session_id: int) -> None:
392
+ episode = episodic_memory._get(session_id)
393
+ if len(episode.root_causes) > 10:
394
+ episode.root_causes = episode.root_causes[-10:]
395
+ if len(episode.hypotheses) > 10:
396
+ episode.hypotheses = episode.hypotheses[-10:]
397
+ if len(episode.edits) > 20:
398
+ episode.edits = episode.edits[-20:]
399
+ if len(episode.decisions) > 20:
400
+ episode.decisions = episode.decisions[-20:]
401
+ if len(episode.successful_strategies) > 10:
402
+ episode.successful_strategies = episode.successful_strategies[-10:]
403
+ if len(episode.failed_strategies) > 10:
404
+ episode.failed_strategies = episode.failed_strategies[-10:]
405
+
406
+ def compress_long_term(self, long_term_memory) -> int:
407
+ return long_term_memory.compress(keep_top=500)
408
+
409
+ def compress_execution(self, execution_memory, session_id: int) -> None:
410
+ mem = execution_memory._get(session_id)
411
+ if len(mem.commands) > mem.max_commands:
412
+ mem.commands = mem.commands[-mem.max_commands:]
413
+ keys = list(mem._index.keys())
414
+ for k in keys[:max(0, len(keys) - mem.max_commands)]:
415
+ mem._index.pop(k, None)
@@ -0,0 +1,303 @@
1
+ Metadata-Version: 2.4
2
+ Name: kryth
3
+ Version: 0.1.0
4
+ Summary: AI Agent Memory Framework — structured memory layers for LLM-based agents
5
+ Author: Kryth Contributors
6
+ License: MIT
7
+ Keywords: ai,agent,memory,llm,framework
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8.0; extra == "dev"
22
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
23
+ Requires-Dist: mypy>=1.10; extra == "dev"
24
+ Requires-Dist: ruff>=0.7; extra == "dev"
25
+ Requires-Dist: pre-commit>=4.0; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ <div align="center">
29
+ <br/>
30
+ <h1>🧠 Kryth</h1>
31
+ <p><strong>AI Agent Memory Framework — Structured memory layers for LLM-based agents</strong></p>
32
+
33
+ <br/>
34
+
35
+ <!-- Badges -->
36
+ <p>
37
+ <a href="https://pypi.org/project/kryth/">
38
+ <img src="https://img.shields.io/pypi/v/kryth?style=for-the-badge&logo=pypi&logoColor=white&color=blueviolet" alt="PyPI Version"/>
39
+ </a>
40
+ <a href="https://pypi.org/project/kryth/">
41
+ <img src="https://img.shields.io/pypi/dm/kryth?style=for-the-badge&logo=python&logoColor=white&color=blue" alt="PyPI Downloads"/>
42
+ </a>
43
+ <a href="LICENSE">
44
+ <img src="https://img.shields.io/badge/license-MIT-green.svg?style=for-the-badge" alt="MIT License"/>
45
+ </a>
46
+ <a href="https://github.com/navadeep0508/KRYTH-OS">
47
+ <img src="https://img.shields.io/badge/python-3.10%2B-blue?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.10+"/>
48
+ </a>
49
+ <a href="https://github.com/navadeep0508/KRYTH-OS/actions">
50
+ <img src="https://img.shields.io/badge/code%20style-ruff-261230?style=for-the-badge&logo=ruff&logoColor=white" alt="Ruff"/>
51
+ </a>
52
+ <a href="CONTRIBUTING.md">
53
+ <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=github" alt="PRs Welcome"/>
54
+ </a>
55
+ </p>
56
+
57
+ <br/>
58
+
59
+ <!-- Quick links -->
60
+ <p>
61
+ <a href="#-quick-start">🚀 Quick Start</a> •
62
+ <a href="#-features">✨ Features</a> •
63
+ <a href="#-architecture">🏗️ Architecture</a> •
64
+ <a href="#-installation">📦 Installation</a> •
65
+ <a href="#-contributing">🤝 Contributing</a>
66
+ </p>
67
+
68
+ <br/>
69
+ </div>
70
+
71
+ ---
72
+
73
+ ## 💡 Why Kryth?
74
+
75
+ LLM-based agents need **memory** — but not just any memory. They need structured, token-efficient, deduplicated memory that fits inside a context window. Kryth gives you:
76
+
77
+ | Without Kryth | With Kryth |
78
+ |---|---|
79
+ | ❌ Bloated, unstructured context | ✅ Token-capped retrieval (~800 tokens) |
80
+ | ❌ Duplicate reads and redundant commands | ✅ Smart duplicate detection & summarization |
81
+ | ❌ Runaway memory growth | ✅ Automatic compression with configurable limits |
82
+ | ❌ Ad-hoc memory management | ✅ Clean, modular controller architecture |
83
+
84
+ ---
85
+
86
+ ## 🚀 Quick Start
87
+
88
+ ```python
89
+ from kryth import WriteController, RetrievalController, compute_state_hash
90
+
91
+ # Compute a unique state hash for your project
92
+ state_hash = compute_state_hash(cwd="/path/to/project")
93
+
94
+ # Route knowledge from tool calls into memory
95
+ write_ctrl = WriteController()
96
+ write_ctrl.on_tool_result(
97
+ tool_name="read_file",
98
+ args={"path": "main.py"},
99
+ result="file contents...",
100
+ error=False,
101
+ session_id=1,
102
+ memory_manager=memory_manager, # Your MemoryManager instance
103
+ turn=3,
104
+ )
105
+
106
+ # Retrieve only the most relevant memories for LLM context
107
+ retrieval_ctrl = RetrievalController()
108
+ context_block = retrieval_ctrl.build_prompt_block(
109
+ memory_manager=memory_manager,
110
+ session_id=1,
111
+ user_input="What does the auth module do?",
112
+ )
113
+ ```
114
+
115
+ > 💡 **Tip:** Kryth is designed to integrate with your existing `MemoryManager`. See the [full documentation](#) for setup details.
116
+
117
+ ---
118
+
119
+ ## ✨ Features
120
+
121
+ <table>
122
+ <tr>
123
+ <td width="50%">
124
+ <h3>🧩 <strong>Modular Architecture</strong></h3>
125
+ <p>Each memory concern — writing, retrieval, deduplication, compression — is isolated in its own controller. Mix and match what you need.</p>
126
+ </td>
127
+ <td width="50%">
128
+ <h3>📏 <strong>Token-Aware Retrieval</strong></h3>
129
+ <p>Context injection is <strong>always capped at ~800 tokens</strong>, ensuring your LLM stays focused and never exceeds context limits.</p>
130
+ </td>
131
+ </tr>
132
+ <tr>
133
+ <td width="50%">
134
+ <h3>🎯 <strong>Relevance Ranking</strong></h3>
135
+ <p>Memories are scored and ranked by importance × query relevance, so only the most pertinent information makes it into the prompt.</p>
136
+ </td>
137
+ <td width="50%">
138
+ <h3>🔍 <strong>Smart Duplicate Detection</strong></h3>
139
+ <p>Detects duplicate file reads and command executions — then returns a <em>summary</em> instead of hard-blocking. Never repeat work.</p>
140
+ </td>
141
+ </tr>
142
+ <tr>
143
+ <td width="50%">
144
+ <h3>🧹 <strong>Automatic Compression</strong></h3>
145
+ <p>Prevents unbounded memory growth with configurable limits per memory layer. Set it and forget it.</p>
146
+ </td>
147
+ <td width="50%">
148
+ <h3>🔐 <strong>State Hashing</strong></h3>
149
+ <p>Robust duplicate command detection using git diff, file hashes, and environment variables. Know when state has genuinely changed.</p>
150
+ </td>
151
+ </tr>
152
+ </table>
153
+
154
+ ---
155
+
156
+ ## 🏗️ Architecture
157
+
158
+ Kryth organizes memory into a clean, layered architecture:
159
+
160
+ ```mermaid
161
+ flowchart TB
162
+ subgraph Input["📥 Inputs"]
163
+ TC[Tool Calls]
164
+ UI[User Input]
165
+ end
166
+
167
+ subgraph Controllers["🎮 Controllers"]
168
+ WC[WriteController]
169
+ RD[DuplicateDetector]
170
+ RC[RetrievalController]
171
+ CC[CompressionController]
172
+ end
173
+
174
+ subgraph Memory["💾 Memory Layers"]
175
+ RM[RepoMemory<br/>File contents, symbols]
176
+ EM[ExecutionMemory<br/>Command history]
177
+ EPM[EpisodicMemory<br/>Edits, decisions]
178
+ WM[WorkingMemory<br/>Objective, blockers]
179
+ LTM[LongTermMemory<br/>Summarized insights]
180
+ end
181
+
182
+ TC --> WC
183
+ WC --> RM
184
+ WC --> EM
185
+ WC --> EPM
186
+ WC --> WM
187
+ WC --> RD
188
+ RD -.->|Soft dedup| TC
189
+
190
+ UI --> RC
191
+ RC --> RM
192
+ RC --> EM
193
+ RC --> EPM
194
+ RC --> WM
195
+ RC -.->|Optional| LTM
196
+
197
+ CC --> EPM
198
+ CC --> LTM
199
+ CC --> EM
200
+ ```
201
+
202
+ ### Memory Layers
203
+
204
+ | Layer | Purpose | Written By |
205
+ |---|---|---|
206
+ | **RepoMemory** | File contents, structure, symbols | `read_file` |
207
+ | **ExecutionMemory** | Command history and results | `run_command` |
208
+ | **EpisodicMemory** | Edits, decisions, findings | `edit_file`, `write_file` |
209
+ | **WorkingMemory** | Current objective and blockers | `set_objective` |
210
+ | **LongTermMemory** | Summarized insights | Compression |
211
+
212
+ ### Retrieval Priority
213
+
214
+ The `RetrievalController` builds context blocks in **strict priority order**, ensuring the most critical information is always included:
215
+
216
+ ```
217
+ 🥇 Current objective (always included)
218
+ 🥈 Working memory findings + blockers
219
+ 🥉 Top repo memory hits (ranked by importance × relevance)
220
+ Recent command execution results
221
+ Critical episodic findings
222
+ Long-term memory summaries (optional)
223
+ ```
224
+
225
+ ---
226
+
227
+ ## 📦 Installation
228
+
229
+ **Stable release** (recommended):
230
+
231
+ ```bash
232
+ pip install kryth
233
+ ```
234
+
235
+ **Development version**:
236
+
237
+ ```bash
238
+ git clone https://github.com/navadeep0508/KRYTH-OS.git
239
+ cd kryth
240
+ pip install -e ".[dev]"
241
+ ```
242
+
243
+ ---
244
+
245
+ ## 🧪 Development
246
+
247
+ ```bash
248
+ # Set up environment
249
+ python -m venv venv
250
+ source venv/bin/activate # On Windows: venv\Scripts\activate
251
+ pip install -e ".[dev]"
252
+
253
+ # Install pre-commit hooks
254
+ pre-commit install
255
+
256
+ # Run tests
257
+ pytest
258
+
259
+ # Code quality
260
+ ruff check src/
261
+ mypy src/
262
+ ```
263
+
264
+ ---
265
+
266
+ ## 🗺️ Roadmap
267
+
268
+ - [x] Core memory controllers (Write, Retrieve, Deduplicate, Compress)
269
+ - [x] Token-capped context retrieval
270
+ - [ ] Integration with popular agent frameworks (LangChain, CrewAI)
271
+ - [ ] Persistent storage backends (SQLite, PostgreSQL, Redis)
272
+ - [ ] Async-first API
273
+ - [ ] Interactive memory dashboard
274
+
275
+ ---
276
+
277
+ ## 🤝 Contributing
278
+
279
+ Contributions are what make the open source community such an amazing place! Any contributions you make are **greatly appreciated**.
280
+
281
+ - 🐛 Found a bug? [Open an issue](https://github.com/navadeep0508/KRYTH-OS/issues)
282
+ - 💡 Have an idea? [Start a discussion](https://github.com/navadeep0508/KRYTH-OS/discussions)
283
+ - 🔧 Want to contribute? See [CONTRIBUTING.md](CONTRIBUTING.md)
284
+
285
+ ---
286
+
287
+ ## 📄 License
288
+
289
+ Distributed under the **MIT License**. See [`LICENSE`](LICENSE) for more information.
290
+
291
+ ---
292
+
293
+ <div align="center">
294
+ <p>
295
+ Made with ❤️ by <a href="https://github.com/navadeep0508">navadeep0508</a>
296
+ </p>
297
+ <p>
298
+ <a href="https://github.com/navadeep0508/KRYTH-OS">GitHub</a> •
299
+ <a href="https://pypi.org/project/kryth/">PyPI</a> •
300
+ <a href="CHANGELOG.md">Changelog</a> •
301
+ <a href="CODE_OF_CONDUCT.md">Code of Conduct</a>
302
+ </p>
303
+ </div>
@@ -0,0 +1,9 @@
1
+ kryth/__init__.py,sha256=2_ni1dmz-5K6y0f6gKEkJHySLn50iXNv1o8RzkFetZc,711
2
+ kryth/agent/__init__.py,sha256=xbT9pJFwCWRps32v6y1s0VAUAEzpEqKKdz2wlc2008Y,41
3
+ kryth/agent/memory/__init__.py,sha256=moq3lqQheZvNTkzByYrWuUyORnl22VY9yPewCr39VkA,471
4
+ kryth/agent/memory/controllers.py,sha256=w9JQEsghyGK53LmS9NuFctrSnhs198piXhvyhqEwHwE,16685
5
+ kryth-0.1.0.dist-info/licenses/LICENSE,sha256=NAbUpPHgo4xtuaN2PlRZWE6-cSxhMaTybd4hc--PjGs,1062
6
+ kryth-0.1.0.dist-info/METADATA,sha256=_Fde4EWZ7L7PEBN1A7jBijC2KRaA0ShUrHgm2ht28G0,9323
7
+ kryth-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
8
+ kryth-0.1.0.dist-info/top_level.txt,sha256=8gUYy5gbkp2v_rDrvdeipvrt5u7eJwjstnnRPXVRrgw,6
9
+ kryth-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kryth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ kryth