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.
@@ -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()