smruti 0.1.0__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.
- smruti-0.1.0/.gitignore +35 -0
- smruti-0.1.0/PKG-INFO +56 -0
- smruti-0.1.0/README.md +39 -0
- smruti-0.1.0/implementation.md +191 -0
- smruti-0.1.0/pyproject.toml +35 -0
- smruti-0.1.0/smruti/__init__.py +34 -0
- smruti-0.1.0/smruti/config.py +53 -0
- smruti-0.1.0/smruti/engine/__init__.py +12 -0
- smruti-0.1.0/smruti/engine/consolidator.py +199 -0
- smruti-0.1.0/smruti/engine/cortex.py +352 -0
- smruti-0.1.0/smruti/engine/inhibitory.py +297 -0
- smruti-0.1.0/smruti/engine/llm.py +127 -0
- smruti-0.1.0/smruti/engine/stream.py +213 -0
- smruti-0.1.0/smruti/framework.py +243 -0
- smruti-0.1.0/smruti/interfaces/__init__.py +1 -0
- smruti-0.1.0/smruti/interfaces/cli.py +170 -0
- smruti-0.1.0/smruti/interfaces/mcp_server.py +193 -0
- smruti-0.1.0/smruti/models.py +87 -0
- smruti-0.1.0/smruti/storage/__init__.py +4 -0
- smruti-0.1.0/smruti/storage/db.py +230 -0
- smruti-0.1.0/smruti/storage/embeddings.py +122 -0
- smruti-0.1.0/tasks.md +101 -0
smruti-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python bytecode & cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
|
|
9
|
+
# Virtual Environment
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
ENV/
|
|
14
|
+
|
|
15
|
+
# smruti local workspace database
|
|
16
|
+
.smruti/
|
|
17
|
+
.smriti/
|
|
18
|
+
*.db
|
|
19
|
+
*.db-wal
|
|
20
|
+
*.db-shm
|
|
21
|
+
|
|
22
|
+
# Tests folder
|
|
23
|
+
tests/
|
|
24
|
+
|
|
25
|
+
# Build artifacts
|
|
26
|
+
build/
|
|
27
|
+
dist/
|
|
28
|
+
*.egg-info/
|
|
29
|
+
*.whl
|
|
30
|
+
|
|
31
|
+
# IDE & OS
|
|
32
|
+
.vscode/
|
|
33
|
+
.idea/
|
|
34
|
+
.DS_Store
|
|
35
|
+
Thumbs.db
|
smruti-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: smruti
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Biologically Inspired, Dual-Valence Cognitive Memory Engine for Autonomous AI Agents
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: fastembed>=0.2.0
|
|
8
|
+
Requires-Dist: fastmcp>=0.1.0
|
|
9
|
+
Requires-Dist: numpy>=1.20.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Requires-Dist: typer>=0.9.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest-asyncio>=0.20.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# smruti (स्मृति)
|
|
19
|
+
|
|
20
|
+
> **Biologically Inspired, Dual-Valence Cognitive Memory for Autonomous AI Agents**
|
|
21
|
+
|
|
22
|
+
smruti equips autonomous coding and reasoning agents with an active, biological memory architecture:
|
|
23
|
+
|
|
24
|
+
1. **Tier 1 (Episodic Working Stream):** Sub-millisecond, append-only SQLite WAL stream. Zero LLM latency while acting.
|
|
25
|
+
2. **Tier 2 (The "Sleep" Consolidation Engine):** Periodic background distillation that strips 90% verbatim token noise into schemas and rules.
|
|
26
|
+
3. **Tier 3 (Dual-Valence Cortical Mesh):**
|
|
27
|
+
- **Positive Heuristics (Tier 3A):** Verified solution paths with biological Ebbinghaus decay and Hebbian reinforcement.
|
|
28
|
+
- **Inhibitory Anti-Memories (Tier 3B):** Active preflight interception gate that prevents agents from repeating known fatal mistakes and compiler errors.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quickstart
|
|
33
|
+
|
|
34
|
+
### Installation
|
|
35
|
+
```bash
|
|
36
|
+
pip install -e .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### CLI Inspection
|
|
40
|
+
```bash
|
|
41
|
+
smruti init # Initialize .smruti/ database in your project
|
|
42
|
+
smruti status # View positive rules, anti-memories, and decay health
|
|
43
|
+
smruti sleep # Run offline consolidation over recent episodes
|
|
44
|
+
smruti audit # Inspect chronological agent trajectories
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### MCP (Model Context Protocol) Server for Cursor / Claude Code / Antigravity
|
|
48
|
+
Add to your MCP configuration (`mcpServers`):
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"smruti": {
|
|
52
|
+
"command": "smruti",
|
|
53
|
+
"args": ["mcp"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
smruti-0.1.0/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# smruti (स्मृति)
|
|
2
|
+
|
|
3
|
+
> **Biologically Inspired, Dual-Valence Cognitive Memory for Autonomous AI Agents**
|
|
4
|
+
|
|
5
|
+
smruti equips autonomous coding and reasoning agents with an active, biological memory architecture:
|
|
6
|
+
|
|
7
|
+
1. **Tier 1 (Episodic Working Stream):** Sub-millisecond, append-only SQLite WAL stream. Zero LLM latency while acting.
|
|
8
|
+
2. **Tier 2 (The "Sleep" Consolidation Engine):** Periodic background distillation that strips 90% verbatim token noise into schemas and rules.
|
|
9
|
+
3. **Tier 3 (Dual-Valence Cortical Mesh):**
|
|
10
|
+
- **Positive Heuristics (Tier 3A):** Verified solution paths with biological Ebbinghaus decay and Hebbian reinforcement.
|
|
11
|
+
- **Inhibitory Anti-Memories (Tier 3B):** Active preflight interception gate that prevents agents from repeating known fatal mistakes and compiler errors.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
### Installation
|
|
18
|
+
```bash
|
|
19
|
+
pip install -e .
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### CLI Inspection
|
|
23
|
+
```bash
|
|
24
|
+
smruti init # Initialize .smruti/ database in your project
|
|
25
|
+
smruti status # View positive rules, anti-memories, and decay health
|
|
26
|
+
smruti sleep # Run offline consolidation over recent episodes
|
|
27
|
+
smruti audit # Inspect chronological agent trajectories
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### MCP (Model Context Protocol) Server for Cursor / Claude Code / Antigravity
|
|
31
|
+
Add to your MCP configuration (`mcpServers`):
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"smruti": {
|
|
35
|
+
"command": "smruti",
|
|
36
|
+
"args": ["mcp"]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# smruti (स्मृति) — Technical Implementation Specification
|
|
2
|
+
|
|
3
|
+
> **A Biologically Inspired, Dual-Valence Cognitive Memory Engine for Autonomous AI Agents**
|
|
4
|
+
> *Bridging fast episodic buffering, offline consolidation, inhibitory anti-memories, and synaptic decay.*
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Executive Summary & Problem Space
|
|
9
|
+
|
|
10
|
+
Current AI memory solutions (Cognee, Mem0, Zep, GraphRAG) approach agent memory as a relational or graph database with vector indexing bolted on. This architecture introduces severe cognitive limitations when paired with autonomous coding agents:
|
|
11
|
+
|
|
12
|
+
1. **Eager Ingestion Latency:** Every turn incurs heavy chunking, multiple LLM extraction calls, and graph compilation. Writes are slow, expensive, and block the agent's reasoning loop.
|
|
13
|
+
2. **Graph Bloat (Missing Consolidation):** Lacking a "sleep" or distillation phase, every casual interaction spawns noisy entity nodes (`User`, `Hello`, `Command`), rapidly cluttering graph traversal.
|
|
14
|
+
3. **Absence of Negative Knowledge (Anti-Memories):** Existing engines only store positive assertions (`X is Y`). They cannot represent failure trajectories, resulting in agents repeating identical mistakes, syntax errors, or compiler dead ends across sessions.
|
|
15
|
+
4. **Static Flat Weighting:** A file or note ingested months ago holds the exact same retrieval weight as a fact recalled two minutes ago. There is no biological forgetting curve or reinforcement mechanism.
|
|
16
|
+
|
|
17
|
+
**smruti** replaces this paradigm with a biological, dual-valence memory engine built on three decoupled tiers.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. The Tri-Tier Biological Architecture
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌────────────────────────────────────────────────────────────────────────┐
|
|
25
|
+
│ TIER 1: EPISODIC WORKING STREAM │
|
|
26
|
+
│ • Sub-millisecond append (Zero LLM overhead on write) │
|
|
27
|
+
│ • Causal trajectory: [Context] -> [Action] -> [Outcome] -> [Duration] │
|
|
28
|
+
└───────────────────────────────────┬────────────────────────────────────┘
|
|
29
|
+
│
|
|
30
|
+
▼ (Triggered "Sleep" / Consolidation)
|
|
31
|
+
┌────────────────────────────────────────────────────────────────────────┐
|
|
32
|
+
│ TIER 2: THE CONSOLIDATION ENGINE │
|
|
33
|
+
│ • Strips 90% verbatim token noise │
|
|
34
|
+
│ • Distills episodes into generalized rules and heuristics │
|
|
35
|
+
└───────────────────┬────────────────────────────────┬───────────────────┘
|
|
36
|
+
│ │
|
|
37
|
+
▼ ▼
|
|
38
|
+
┌──────────────────────────────────────┐ ┌───────────────────────────────┐
|
|
39
|
+
│ TIER 3A: POSITIVE KNOWLEDGE │ │ TIER 3B: INHIBITORY MEMORY │
|
|
40
|
+
│ • Verified solution paths │ │ • Anti-memories / Dead ends │
|
|
41
|
+
│ • Ebbinghaus reinforcement & decay │ │ • Exact failure signatures │
|
|
42
|
+
│ • Spreading associative activation │ │ • Proactive error prevention│
|
|
43
|
+
└──────────────────────────────────────┘ └───────────────────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Tier 1: Episodic Working Stream (Hippocampal Buffer)
|
|
47
|
+
- Append-only, transaction-safe event stream.
|
|
48
|
+
- Captures actions, tool invocations, shell commands, and execution results in real time.
|
|
49
|
+
- Operates at sub-millisecond latency using SQLite in Write-Ahead Logging (`WAL`) mode.
|
|
50
|
+
- **Zero LLM overhead during writes.** The agent is never stalled waiting for embeddings or entity extraction.
|
|
51
|
+
|
|
52
|
+
### Tier 2: Memory Consolidation Engine ("The Sleep Cycle")
|
|
53
|
+
- Periodic or threshold-triggered background worker (runs during idle agent periods or session boundaries).
|
|
54
|
+
- Replays recent episodic trajectories, separates verified successes from failed attempts, and prunes verbose raw text.
|
|
55
|
+
- Generalizes episodes into abstract schemas and heuristics.
|
|
56
|
+
- Applies biological synaptic decay sweeps across dormant memories.
|
|
57
|
+
|
|
58
|
+
### Tier 3: Dual-Valence Cortical Mesh
|
|
59
|
+
- **Tier 3A (Positive Excitatory Knowledge):**
|
|
60
|
+
- Stores verified solution strategies, project invariants, and architectural rules.
|
|
61
|
+
- Governed by Hebbian reinforcement: frequently recalled rules gain higher retrieval priority (`strength`).
|
|
62
|
+
- Governed by the Ebbinghaus forgetting curve: unused memories fade exponentially over time.
|
|
63
|
+
- **Tier 3B (Inhibitory Anti-Memories — Vimarsha):**
|
|
64
|
+
- Stores known dead ends, syntax traps, compiler failure signatures, and environmental collisions.
|
|
65
|
+
- Active preflight gate: intercepts agent actions *before* execution if a matching failure signature is detected.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 3. Technology Stack & Design Rationale
|
|
70
|
+
|
|
71
|
+
| Component | Technology | Rationale |
|
|
72
|
+
| :--- | :--- | :--- |
|
|
73
|
+
| **Language** | Python 3.10+ | Native to AI agent tooling, IDE plugins, and Python packaging. |
|
|
74
|
+
| **Episodic Stream (Buffer)** | **SQLite (WAL Mode)** | Zero-configuration, zero-daemon, embedded, sub-1ms ACID commits. |
|
|
75
|
+
| **Embedded Graph Storage** | **SQLite Adjacency Tables / Kùzu** | In-process relational adjacency mesh with optional Kùzu backend. Zero Docker requirements. |
|
|
76
|
+
| **Vector Engine (Local)** | **FastEmbed / sqlite-vec** | In-process CPU-friendly embeddings without requiring external vector microservices. |
|
|
77
|
+
| **IDE Protocol Interface** | **FastMCP (Model Context Protocol)** | First-class stdio integration for Cursor, Claude Code, Antigravity, and Windsurf. |
|
|
78
|
+
| **Developer CLI** | **Typer** | Intuitive terminal tool for memory inspection, manual consolidation, and system health audits. |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 4. Package & Directory Structure
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
smruti/
|
|
86
|
+
├── pyproject.toml # Project dependencies: fastmcp, typer, pydantic, fastembed
|
|
87
|
+
├── README.md # Quickstart, architecture overview, and MCP configuration
|
|
88
|
+
├── implementation.md # Technical specification and mathematical/logical models
|
|
89
|
+
├── tasks.md # Actionable task tracking and milestone progress
|
|
90
|
+
├── smruti/
|
|
91
|
+
│ ├── __init__.py # Public Python API: smrutiEngine, Guard
|
|
92
|
+
│ ├── config.py # Database paths, decay half-life, thresholds
|
|
93
|
+
│ ├── models.py # Pydantic schemas (Episode, AntiMemory, Rule, Valence)
|
|
94
|
+
│ ├── storage/
|
|
95
|
+
│ │ ├── __init__.py
|
|
96
|
+
│ │ └── db.py # SQLite WAL mode initialization, indexing, and migrations
|
|
97
|
+
│ ├── engine/
|
|
98
|
+
│ │ ├── __init__.py
|
|
99
|
+
│ │ ├── stream.py # Tier 1: Sub-millisecond episodic buffer (append-only)
|
|
100
|
+
│ │ ├── inhibitory.py # Tier 3B: Exact & fuzzy failure signature matcher
|
|
101
|
+
│ │ ├── cortex.py # Tier 3A: Positive rules, associative links, synaptic decay
|
|
102
|
+
│ │ └── consolidator.py # Tier 2: "Sleep" consolidation & token pruning pipeline
|
|
103
|
+
│ ├── interfaces/
|
|
104
|
+
│ │ ├── __init__.py
|
|
105
|
+
│ │ ├── mcp_server.py # FastMCP Server (stdio tools for AI coding assistants)
|
|
106
|
+
│ │ └── cli.py # Typer CLI (smruti init, inspect, sleep, audit)
|
|
107
|
+
└── tests/
|
|
108
|
+
├── test_stream.py # Sub-ms append benchmark & schema checks
|
|
109
|
+
├── test_inhibition.py # Preflight interception verification
|
|
110
|
+
├── test_decay.py # Synaptic decay & reinforcement math verification
|
|
111
|
+
├── test_consolidation.py # Token compression & schema distillation tests
|
|
112
|
+
└── test_mcp.py # MCP tool invocation tests
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 5. Mathematical & Algorithmic Formulation
|
|
118
|
+
|
|
119
|
+
### Biological Synaptic Decay
|
|
120
|
+
Memory strength degrades exponentially according to the Ebbinghaus retention model:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
effective_strength = base_strength * exp(-decay_rate * (current_time - last_accessed_at))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- When a rule is retrieved and successfully utilized by the agent:
|
|
127
|
+
- `access_count` increments by 1.
|
|
128
|
+
- `base_strength` increases (Hebbian reinforcement: `base_strength = min(1.0, base_strength + reinforcement_factor)`).
|
|
129
|
+
- `last_accessed_at` updates to `current_time`.
|
|
130
|
+
- When `effective_strength` falls below `prune_threshold` (e.g. 0.15) and has not been accessed across multiple consolidation cycles, the rule is compacted or archived.
|
|
131
|
+
|
|
132
|
+
### Inhibitory Matching Logic
|
|
133
|
+
An inhibitory anti-memory matches an action candidate if:
|
|
134
|
+
1. **Exact Signature Match:** Exact command or pattern match with a previously failed trajectory.
|
|
135
|
+
2. **Contextual Collision:** Action contains known destructive flags or conflicting dependencies in the current environment context.
|
|
136
|
+
3. **Regex / Error Trace Substring:** Action resembles an error-inducing command under identical project conditions.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 6. End-to-End System Flow
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
[ Agent Prompt / Plan ]
|
|
144
|
+
│
|
|
145
|
+
▼
|
|
146
|
+
┌───────────────────────┐
|
|
147
|
+
│ STEP 1: PREFLIGHT │
|
|
148
|
+
│ INHIBITION CHECK │
|
|
149
|
+
└───────────┬───────────┘
|
|
150
|
+
│
|
|
151
|
+
┌────────────────────────┴────────────────────────┐
|
|
152
|
+
▼ (Match Found) ▼ (Clear)
|
|
153
|
+
[ INTERCEPT AGENT ] [ AGENT EXECUTES ACTION ]
|
|
154
|
+
"Blocked: Known dead end: ..." │
|
|
155
|
+
▼
|
|
156
|
+
┌───────────────────────┐
|
|
157
|
+
│ STEP 2: FAST EPISODIC │
|
|
158
|
+
│ APPEND (Sub-1ms) │
|
|
159
|
+
└───────────┬───────────┘
|
|
160
|
+
│
|
|
161
|
+
▼ (Idle / Periodic)
|
|
162
|
+
┌───────────────────────┐
|
|
163
|
+
│ STEP 3: CONSOLIDATION │
|
|
164
|
+
│ ("Sleep" Distillation)│
|
|
165
|
+
│ Strips token noise │
|
|
166
|
+
│ Updates synaptic decay│
|
|
167
|
+
└───────────┬───────────┘
|
|
168
|
+
│
|
|
169
|
+
▼
|
|
170
|
+
[ PERMANENT CORTICAL MESH ]
|
|
171
|
+
• Positive Heuristics (Tier 3A)
|
|
172
|
+
• Inhibitory Anti-Memories (Tier 3B)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 7. Delivery Interfaces
|
|
178
|
+
|
|
179
|
+
### Interface A: FastMCP Server
|
|
180
|
+
Equips AI IDEs (Cursor, Claude Code, Antigravity) with four native tools over stdio:
|
|
181
|
+
1. `smruti_preflight_check(action, context)`: Intercepts actions before execution.
|
|
182
|
+
2. `smruti_record_episode(action, outcome, status, latency_ms)`: Logs execution results into Tier 1.
|
|
183
|
+
3. `smruti_recall_heuristics(query, limit)`: Injects top-weighted positive rules into working memory.
|
|
184
|
+
4. `smruti_trigger_sleep()`: Runs the consolidation cycle.
|
|
185
|
+
|
|
186
|
+
### Interface B: Typer CLI
|
|
187
|
+
Provides developers with terminal inspection and manual management:
|
|
188
|
+
- `smruti init`: Initializes `.smruti/` database in the current project root.
|
|
189
|
+
- `smruti status`: Displays active positive rules, anti-memories, and decay health.
|
|
190
|
+
- `smruti sleep`: Forces memory consolidation over uncompacted episodes.
|
|
191
|
+
- `smruti audit`: Shows chronological trajectory logs and blocked dead ends.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smruti"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Biologically Inspired, Dual-Valence Cognitive Memory Engine for Autonomous AI Agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"pydantic>=2.0.0",
|
|
14
|
+
"typer>=0.9.0",
|
|
15
|
+
"fastmcp>=0.1.0",
|
|
16
|
+
"fastembed>=0.2.0",
|
|
17
|
+
"numpy>=1.20.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=7.0.0",
|
|
23
|
+
"pytest-asyncio>=0.20.0",
|
|
24
|
+
"ruff>=0.1.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
smruti = "smruti.interfaces.cli:app"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel]
|
|
31
|
+
packages = ["smruti"]
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
line-length = 100
|
|
35
|
+
target-version = "py310"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
smruti (स्मृति) - Biologically Inspired, Dual-Valence Cognitive Memory for Autonomous AI Agents.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from smruti.config import SmrutiConfig, get_config, smrutiConfig
|
|
6
|
+
from smruti.engine.consolidator import Consolidator
|
|
7
|
+
from smruti.engine.cortex import Cortex
|
|
8
|
+
from smruti.engine.inhibitory import InhibitoryGate
|
|
9
|
+
from smruti.engine.stream import StreamBuffer
|
|
10
|
+
from smruti.framework import Smruti, SmrutiInhibitionError, smruti, smrutiInhibitionError
|
|
11
|
+
from smruti.models import ActionStatus, AntiMemory, CorticalRule, Episode, InhibitionResult
|
|
12
|
+
from smruti.storage.db import DatabaseManager, get_db
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ActionStatus",
|
|
17
|
+
"AntiMemory",
|
|
18
|
+
"Consolidator",
|
|
19
|
+
"Cortex",
|
|
20
|
+
"CorticalRule",
|
|
21
|
+
"DatabaseManager",
|
|
22
|
+
"Episode",
|
|
23
|
+
"InhibitionResult",
|
|
24
|
+
"InhibitoryGate",
|
|
25
|
+
"Smruti",
|
|
26
|
+
"SmrutiConfig",
|
|
27
|
+
"SmrutiInhibitionError",
|
|
28
|
+
"smruti",
|
|
29
|
+
"smrutiConfig",
|
|
30
|
+
"smrutiInhibitionError",
|
|
31
|
+
"StreamBuffer",
|
|
32
|
+
"get_config",
|
|
33
|
+
"get_db",
|
|
34
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
config.py - Dynamic environment and runtime configuration for smruti.
|
|
3
|
+
Discovers local .smruti/ directory or falls back to user home directory.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class smrutiConfig:
|
|
12
|
+
# Storage settings
|
|
13
|
+
db_filename: str = "smruti.db"
|
|
14
|
+
project_dir: Path = Path.cwd()
|
|
15
|
+
smruti_dir_name: str = ".smruti"
|
|
16
|
+
|
|
17
|
+
# Biological decay parameters
|
|
18
|
+
# decay_rate: controls how fast unused rules decay over time (in hours^-1)
|
|
19
|
+
default_decay_rate: float = 0.05
|
|
20
|
+
# prune_threshold: rules with effective strength below this are archived
|
|
21
|
+
prune_threshold: float = 0.15
|
|
22
|
+
# reinforcement_boost: strength added per successful recall
|
|
23
|
+
reinforcement_boost: float = 0.15
|
|
24
|
+
|
|
25
|
+
# Semantic & Associative Graph parameters
|
|
26
|
+
dedup_similarity_threshold: float = 0.85
|
|
27
|
+
associative_edge_threshold: float = 0.60
|
|
28
|
+
spreading_activation_factor: float = 0.25
|
|
29
|
+
embedding_model: str = "BAAI/bge-small-en-v1.5"
|
|
30
|
+
|
|
31
|
+
# Consolidation thresholds
|
|
32
|
+
consolidation_turn_interval: int = 5
|
|
33
|
+
auto_consolidate: bool = True
|
|
34
|
+
raw_retention_days: int = 7
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def smruti_dir(self) -> Path:
|
|
38
|
+
"""Finds .smruti in current working directory or ancestors, else creates in cwd."""
|
|
39
|
+
current = self.project_dir.resolve()
|
|
40
|
+
for parent in [current, *current.parents]:
|
|
41
|
+
candidate = parent / self.smruti_dir_name
|
|
42
|
+
if candidate.is_dir():
|
|
43
|
+
return candidate
|
|
44
|
+
return current / self.smruti_dir_name
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def db_path(self) -> Path:
|
|
48
|
+
return self.smruti_dir / self.db_filename
|
|
49
|
+
|
|
50
|
+
def get_config() -> smrutiConfig:
|
|
51
|
+
return smrutiConfig()
|
|
52
|
+
|
|
53
|
+
SmrutiConfig = smrutiConfig
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Engine modules for smruti."""
|
|
2
|
+
from smruti.engine.consolidator import Consolidator
|
|
3
|
+
from smruti.engine.cortex import Cortex
|
|
4
|
+
from smruti.engine.inhibitory import InhibitoryGate
|
|
5
|
+
from smruti.engine.stream import StreamBuffer
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Consolidator",
|
|
9
|
+
"Cortex",
|
|
10
|
+
"InhibitoryGate",
|
|
11
|
+
"StreamBuffer",
|
|
12
|
+
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
consolidator.py - Tier 2: The "Sleep" Consolidation & Distillation Engine.
|
|
3
|
+
Replays raw episodic trajectories, extracts schemas and heuristics, promotes recurring
|
|
4
|
+
errors into permanent inhibitory anti-memories, links causal resolution trajectories,
|
|
5
|
+
and sweeps historical raw buffers and decaying cortical rules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from smruti.config import smrutiConfig, get_config
|
|
16
|
+
from smruti.engine.cortex import Cortex
|
|
17
|
+
from smruti.engine.inhibitory import InhibitoryGate
|
|
18
|
+
from smruti.engine.llm import LLMClient
|
|
19
|
+
from smruti.engine.stream import StreamBuffer
|
|
20
|
+
from smruti.models import ActionStatus, Episode
|
|
21
|
+
from smruti.storage.db import DatabaseManager, get_db
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
class Consolidator:
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
db: DatabaseManager | None = None,
|
|
29
|
+
config: smrutiConfig | None = None,
|
|
30
|
+
stream: StreamBuffer | None = None,
|
|
31
|
+
inhibitory: InhibitoryGate | None = None,
|
|
32
|
+
cortex: Cortex | None = None,
|
|
33
|
+
llm_summarizer: Callable[[str, list[dict[str, Any]]], str] | None = None,
|
|
34
|
+
llm_client: LLMClient | None = None
|
|
35
|
+
):
|
|
36
|
+
self.config = config or get_config()
|
|
37
|
+
self.db = db or get_db(self.config)
|
|
38
|
+
self.stream = stream or StreamBuffer(self.db)
|
|
39
|
+
self.inhibitory = inhibitory or InhibitoryGate(self.db)
|
|
40
|
+
self.cortex = cortex or Cortex(self.db, self.config)
|
|
41
|
+
self.llm_client = llm_client or LLMClient()
|
|
42
|
+
self.llm_summarizer = llm_summarizer
|
|
43
|
+
|
|
44
|
+
def sleep(
|
|
45
|
+
self,
|
|
46
|
+
session_id: str | None = None,
|
|
47
|
+
current_time: float | None = None,
|
|
48
|
+
project_root: str | None = None
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
"""
|
|
51
|
+
Executes memory consolidation ("Sleep Cycle"):
|
|
52
|
+
1. Analyzes unconsolidated episodic trajectories.
|
|
53
|
+
2. Detects recurring failures and promotes them to Inhibitory Anti-Memories.
|
|
54
|
+
3. Discovers multi-step causal resolutions and promotes them to Cortical Rules.
|
|
55
|
+
4. Marks analyzed episodes as consolidated (idempotent execution).
|
|
56
|
+
5. Enforces raw_retention_days cleanup on old consolidated episodes.
|
|
57
|
+
6. Sweeps decaying dormant rules.
|
|
58
|
+
"""
|
|
59
|
+
now = current_time or datetime.now(timezone.utc).timestamp()
|
|
60
|
+
|
|
61
|
+
# 1. Fetch only unconsolidated episodes
|
|
62
|
+
unconsolidated = self.stream.get_unconsolidated(limit=200, session_id=session_id)
|
|
63
|
+
if not unconsolidated:
|
|
64
|
+
# Still perform maintenance sweeps even if no new episodes
|
|
65
|
+
pruned_rules = self.cortex.prune_decayed_rules(current_time=now)
|
|
66
|
+
pruned_raw = self.stream.prune_older_than(
|
|
67
|
+
self.config.raw_retention_days * 86400.0,
|
|
68
|
+
only_consolidated=True
|
|
69
|
+
)
|
|
70
|
+
return {
|
|
71
|
+
"processed_episodes": 0,
|
|
72
|
+
"promoted_anti_memories": 0,
|
|
73
|
+
"promoted_positive_rules": 0,
|
|
74
|
+
"pruned_decayed_rules": pruned_rules,
|
|
75
|
+
"pruned_raw_episodes": pruned_raw,
|
|
76
|
+
"timestamp": now
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
episodes = sorted(unconsolidated, key=lambda e: e.timestamp)
|
|
80
|
+
promoted_anti_memories = 0
|
|
81
|
+
promoted_rules = 0
|
|
82
|
+
|
|
83
|
+
# 2. Analyze failure repetitions with normalized command & error clustering
|
|
84
|
+
failure_clusters: dict[str, list[Episode]] = defaultdict(list)
|
|
85
|
+
for ep in episodes:
|
|
86
|
+
if ep.status in [ActionStatus.FAILURE, ActionStatus.ERROR]:
|
|
87
|
+
cluster_key = self._extract_cluster_key(ep.action)
|
|
88
|
+
failure_clusters[cluster_key].append(ep)
|
|
89
|
+
|
|
90
|
+
for cluster_key, cluster in failure_clusters.items():
|
|
91
|
+
if len(cluster) >= 2:
|
|
92
|
+
# Recurring failure pattern detected -> promote to anti-memory
|
|
93
|
+
first_ep = cluster[0]
|
|
94
|
+
sig = f"recurring_failure_{self._slugify(cluster_key)}"
|
|
95
|
+
|
|
96
|
+
# Check for project scoping in episode metadata or parameter
|
|
97
|
+
ep_proj = project_root or first_ep.metadata.get("project_root")
|
|
98
|
+
|
|
99
|
+
cluster_data = [
|
|
100
|
+
{"action": e.action, "result": e.result[:200], "context": e.context}
|
|
101
|
+
for e in cluster
|
|
102
|
+
]
|
|
103
|
+
if self.llm_summarizer:
|
|
104
|
+
try:
|
|
105
|
+
reason = self.llm_summarizer("failure_summary", cluster_data)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.warning("LLM summarizer failed: %s; falling back to heuristic", e)
|
|
108
|
+
reason = f"Command '{cluster_key}' failed {len(cluster)} times. Sample error: {first_ep.result[:120]}"
|
|
109
|
+
elif self.llm_client.is_configured():
|
|
110
|
+
reason = self.llm_client.summarize_failure_cluster(cluster_data)
|
|
111
|
+
else:
|
|
112
|
+
sample_err = first_ep.result.strip().split("\n")[-1][:120] if first_ep.result else "Non-zero exit code"
|
|
113
|
+
reason = f"Command pattern '{cluster_key}' failed {len(cluster)} times. Error: {sample_err}"
|
|
114
|
+
|
|
115
|
+
self.inhibitory.record_anti_memory(
|
|
116
|
+
signature=sig,
|
|
117
|
+
pattern=cluster_key,
|
|
118
|
+
reason=reason,
|
|
119
|
+
suggested_fix="Check prerequisites and inspect command arguments.",
|
|
120
|
+
severity="high",
|
|
121
|
+
project_root=ep_proj
|
|
122
|
+
)
|
|
123
|
+
promoted_anti_memories += 1
|
|
124
|
+
|
|
125
|
+
# 3. Multi-Step Causal Discovery (Window of up to 4 episodes)
|
|
126
|
+
# Finds a failure followed by a subsequent resolution in the same session
|
|
127
|
+
processed_pairs = set()
|
|
128
|
+
for i in range(len(episodes)):
|
|
129
|
+
curr_ep = episodes[i]
|
|
130
|
+
if curr_ep.status not in [ActionStatus.FAILURE, ActionStatus.ERROR]:
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
# Look ahead up to 4 steps for a successful resolution
|
|
134
|
+
lookahead_limit = min(len(episodes), i + 5)
|
|
135
|
+
for j in range(i + 1, lookahead_limit):
|
|
136
|
+
succ_ep = episodes[j]
|
|
137
|
+
if succ_ep.status == ActionStatus.SUCCESS and succ_ep.session_id == curr_ep.session_id:
|
|
138
|
+
pair_key = (curr_ep.action.strip(), succ_ep.action.strip())
|
|
139
|
+
if pair_key in processed_pairs:
|
|
140
|
+
break
|
|
141
|
+
processed_pairs.add(pair_key)
|
|
142
|
+
|
|
143
|
+
# Distill causal heuristic
|
|
144
|
+
transitions = [{"failed_action": curr_ep.action, "error": curr_ep.result[:150], "fix_action": succ_ep.action}]
|
|
145
|
+
if self.llm_summarizer:
|
|
146
|
+
try:
|
|
147
|
+
rule_text = self.llm_summarizer("causal_resolution", transitions)
|
|
148
|
+
except Exception:
|
|
149
|
+
rule_text = f"When '{curr_ep.action}' fails, use '{succ_ep.action}' instead."
|
|
150
|
+
elif self.llm_client.is_configured():
|
|
151
|
+
rule_text = self.llm_client.distill_resolution_heuristic(transitions)
|
|
152
|
+
else:
|
|
153
|
+
rule_text = f"When '{curr_ep.action}' fails, use '{succ_ep.action}' instead."
|
|
154
|
+
|
|
155
|
+
self.cortex.add_rule(
|
|
156
|
+
rule_text=rule_text,
|
|
157
|
+
category="problem_resolution",
|
|
158
|
+
confidence=0.9,
|
|
159
|
+
base_strength=0.9,
|
|
160
|
+
source_episode_ids=[curr_ep.id, succ_ep.id]
|
|
161
|
+
)
|
|
162
|
+
promoted_rules += 1
|
|
163
|
+
break # Found the resolution for curr_ep
|
|
164
|
+
|
|
165
|
+
# 4. Mark all processed episodes as consolidated
|
|
166
|
+
ep_ids = [e.id for e in episodes]
|
|
167
|
+
self.stream.mark_consolidated(ep_ids, now)
|
|
168
|
+
|
|
169
|
+
# 5. Raw retention cleanup: prune historical episodes older than retention limit
|
|
170
|
+
retention_seconds = self.config.raw_retention_days * 86400.0
|
|
171
|
+
pruned_raw = self.stream.prune_older_than(retention_seconds, only_consolidated=True)
|
|
172
|
+
|
|
173
|
+
# 6. Sweep biologically decayed rules
|
|
174
|
+
pruned_rules = self.cortex.prune_decayed_rules(current_time=now)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
"processed_episodes": len(episodes),
|
|
178
|
+
"promoted_anti_memories": promoted_anti_memories,
|
|
179
|
+
"promoted_positive_rules": promoted_rules,
|
|
180
|
+
"pruned_decayed_rules": pruned_rules,
|
|
181
|
+
"pruned_raw_episodes": pruned_raw,
|
|
182
|
+
"timestamp": now
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def _extract_cluster_key(action: str) -> str:
|
|
187
|
+
"""Extracts normalized command or pattern key from action string."""
|
|
188
|
+
tokens = action.strip().split()
|
|
189
|
+
if not tokens:
|
|
190
|
+
return "unknown"
|
|
191
|
+
# If multi-word command like "pip install" or "npm run" or "git push", group top 2 words
|
|
192
|
+
if len(tokens) >= 2 and tokens[0].lower() in {"pip", "npm", "git", "docker", "yarn", "cargo", "pnpm", "poetry", "uv"}:
|
|
193
|
+
return f"{tokens[0]} {tokens[1]}"
|
|
194
|
+
return tokens[0]
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _slugify(text: str) -> str:
|
|
198
|
+
"""Creates safe alphanumeric identifier."""
|
|
199
|
+
return re.sub(r"[^a-zA-Z0-9_]+", "_", text).strip("_").lower()
|