zer0lint 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,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: zer0lint
3
+ Version: 0.1.0
4
+ Summary: mem0 extraction optimizer: diagnose failures and generate custom prompts for your system
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: httpx>=0.24.0
7
+ Requires-Dist: mem0ai>=0.1.0
8
+ Requires-Dist: pydantic>=2.0.0
9
+ Requires-Dist: rich>=13.0.0
10
+ Requires-Dist: typer[all]>=0.9.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
13
+ Requires-Dist: pytest>=7.0; extra == 'dev'
14
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # zer0lint
18
+
19
+ **AI memory extraction diagnostics.** Find out why your AI agent forgets what matters — and fix it.
20
+
21
+ [![PyPI](https://img.shields.io/badge/coming%20soon-PyPI-blue)](https://pypi.org/project/zer0lint/)
22
+ [![License](https://img.shields.io/badge/license-Apache%202.0-green)](LICENSE)
23
+ [![Made by Hermes Labs](https://img.shields.io/badge/made%20by-Hermes%20Labs-purple)](https://hermes-labs.ai)
24
+
25
+ ---
26
+
27
+ ## The Problem
28
+
29
+ You set up mem0 (or Zep, or LangMem) and your AI agent still seems forgetful. Technical decisions, version numbers, experiment results — it stores them, but can't recall them.
30
+
31
+ **Why?** mem0's default extraction prompt is designed for personal assistants: favorite coffee shops, dietary preferences, weekend plans. When your agent stores "Redis upgraded to v7.2.4" or "security audit scored 7.2/10", it silently drops the specifics.
32
+
33
+ **Proven gap (our testing):**
34
+ - Default mem0 prompt → **70% recall** on technical facts
35
+ - Wrong domain prompt (personal) → **40% recall**
36
+ - zer0lint technical prompt → **90% recall**
37
+
38
+ That's a **+20pp improvement** from getting the extraction prompt right.
39
+
40
+ ---
41
+
42
+ ## Quick Start
43
+
44
+ ```bash
45
+ pip install zer0lint # coming soon
46
+
47
+ # Check your current extraction health
48
+ zer0lint check --config ~/.mem0/config.json
49
+
50
+ # Diagnose and fix
51
+ zer0lint generate --config ~/.mem0/config.json
52
+
53
+ # Dry run (see what would change without applying)
54
+ zer0lint generate --config ~/.mem0/config.json --dry-run
55
+ ```
56
+
57
+ ---
58
+
59
+ ## What It Does
60
+
61
+ ### `zer0lint check`
62
+ Tests your current mem0 config against domain-relevant synthetic facts. Returns a score and status.
63
+
64
+ ```
65
+ zer0lint v0.2.0 — extraction health check
66
+ Config : ~/.mem0/config.json
67
+ Model : qwen3.5:4b
68
+ Prompt : default (mem0 built-in)
69
+
70
+ Score : 4/5 (80%) — HEALTHY
71
+
72
+ ✅ Version update
73
+ ✅ CI status
74
+ ✅ Model upgrade
75
+ ✅ Configuration
76
+ ⚠ API endpoint
77
+ ```
78
+
79
+ Statuses: **HEALTHY** (≥80%) · **ACCEPTABLE** (60–79%) · **DEGRADED** (40–59%) · **CRITICAL** (<40%)
80
+
81
+ ### `zer0lint generate`
82
+ 3-phase diagnostic + fix:
83
+
84
+ 1. **Baseline** — test your config as-is
85
+ 2. **Re-test** — inject zer0lint's domain-aware extraction prompt at config level
86
+ 3. **Apply** — if improved, write the validated prompt to your config
87
+
88
+ ```
89
+ zer0lint v0.2.0 — extraction optimizer
90
+
91
+ [1/3] Baseline — testing current config as-is...
92
+ Baseline score: 4/5 (80%)
93
+
94
+ [2/3] Re-testing with zer0lint technical extraction prompt...
95
+ Improved score: 5/5 (100%)
96
+ Improvement: +20pp
97
+
98
+ Results:
99
+ Before : 4/5 (80%)
100
+ After : 5/5 (100%)
101
+ Δ : +20pp
102
+
103
+ ✅ Fix applied to config.
104
+ Backup: ~/.mem0/config.backup.2026-03-23T14:20:00
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Why Config-Level Injection Matters
110
+
111
+ **Important finding:** In mem0 v1.x, passing a prompt via `memory.add(..., prompt=X)` has **no measurable effect** on retrieval quality. The extraction prompt must live in the config (`custom_fact_extraction_prompt` field) to actually work.
112
+
113
+ zer0lint writes the validated prompt directly to your config — this is the correct fix.
114
+
115
+ ---
116
+
117
+ ## Supported Systems
118
+
119
+ | System | Status | Notes |
120
+ |---|---|---|
121
+ | mem0 (v1.x) | ✅ Supported | Full check + generate |
122
+ | Zep / Graphiti | 🔜 Planned | v0.3 |
123
+ | LangMem | 🔜 Planned | v0.4 |
124
+ | Generic adapter | 🔜 Planned | BYOC callables |
125
+
126
+ ---
127
+
128
+ ## Test Results (2026-03-23)
129
+
130
+ Horse race across models (5 technical + research facts):
131
+
132
+ | Model | Default prompt | zer0lint prompt | Δ |
133
+ |---|---|---|---|
134
+ | qwen3.5:4b | 80% | **100%** | +20pp |
135
+ | mistral:7b | 40% | 40% | 0pp |
136
+
137
+ **mistral:7b** produces malformed JSON regardless of prompt — model quality is the bottleneck. zer0lint detects this and recommends switching models.
138
+
139
+ Scale test (10 facts, 5 domains):
140
+
141
+ | | Score | % |
142
+ |---|---|---|
143
+ | Default | 7/10 | 70% |
144
+ | zer0lint | 9/10 | **90%** |
145
+
146
+ ---
147
+
148
+ ## Installation
149
+
150
+ ```bash
151
+ # From source (current)
152
+ git clone https://github.com/roli-lpci/zer0lint
153
+ cd zer0lint
154
+ pip install -e .
155
+
156
+ # PyPI (coming soon)
157
+ pip install zer0lint
158
+ ```
159
+
160
+ **Requirements:** Python 3.9+, mem0 v1.x, an Ollama or cloud LLM configured in your mem0 config.
161
+
162
+ ---
163
+
164
+ ## How It Works
165
+
166
+ zer0lint reads your existing mem0 config, borrows whatever LLM you already have configured, and runs a controlled recall test. No new API keys, no new models, no cloud calls beyond what you already have.
167
+
168
+ The extraction prompt it generates is **domain-aware** — tuned for technical/research work rather than personal assistant use cases. It's validated against synthetic facts before being applied, and your original config is always backed up.
169
+
170
+ ---
171
+
172
+ ## Built by Hermes Labs
173
+
174
+ zer0lint is part of the [Hermes Labs](https://hermes-labs.ai) AI agent tooling suite:
175
+
176
+ - **[lintlang](https://github.com/roli-lpci/lintlang)** — Static linter for AI agent tool descriptions and prompts
177
+ - **[Little Canary](https://github.com/roli-lpci/little-canary)** — Prompt injection detection
178
+ - **[Suy Sideguy](https://github.com/roli-lpci/suy-sideguy)** — Runtime policy enforcement for agents
179
+ - **zer0lint** — Memory extraction diagnostics ← you are here
180
+
181
+ ---
182
+
183
+ ## License
184
+
185
+ Apache 2.0
@@ -0,0 +1,169 @@
1
+ # zer0lint
2
+
3
+ **AI memory extraction diagnostics.** Find out why your AI agent forgets what matters — and fix it.
4
+
5
+ [![PyPI](https://img.shields.io/badge/coming%20soon-PyPI-blue)](https://pypi.org/project/zer0lint/)
6
+ [![License](https://img.shields.io/badge/license-Apache%202.0-green)](LICENSE)
7
+ [![Made by Hermes Labs](https://img.shields.io/badge/made%20by-Hermes%20Labs-purple)](https://hermes-labs.ai)
8
+
9
+ ---
10
+
11
+ ## The Problem
12
+
13
+ You set up mem0 (or Zep, or LangMem) and your AI agent still seems forgetful. Technical decisions, version numbers, experiment results — it stores them, but can't recall them.
14
+
15
+ **Why?** mem0's default extraction prompt is designed for personal assistants: favorite coffee shops, dietary preferences, weekend plans. When your agent stores "Redis upgraded to v7.2.4" or "security audit scored 7.2/10", it silently drops the specifics.
16
+
17
+ **Proven gap (our testing):**
18
+ - Default mem0 prompt → **70% recall** on technical facts
19
+ - Wrong domain prompt (personal) → **40% recall**
20
+ - zer0lint technical prompt → **90% recall**
21
+
22
+ That's a **+20pp improvement** from getting the extraction prompt right.
23
+
24
+ ---
25
+
26
+ ## Quick Start
27
+
28
+ ```bash
29
+ pip install zer0lint # coming soon
30
+
31
+ # Check your current extraction health
32
+ zer0lint check --config ~/.mem0/config.json
33
+
34
+ # Diagnose and fix
35
+ zer0lint generate --config ~/.mem0/config.json
36
+
37
+ # Dry run (see what would change without applying)
38
+ zer0lint generate --config ~/.mem0/config.json --dry-run
39
+ ```
40
+
41
+ ---
42
+
43
+ ## What It Does
44
+
45
+ ### `zer0lint check`
46
+ Tests your current mem0 config against domain-relevant synthetic facts. Returns a score and status.
47
+
48
+ ```
49
+ zer0lint v0.2.0 — extraction health check
50
+ Config : ~/.mem0/config.json
51
+ Model : qwen3.5:4b
52
+ Prompt : default (mem0 built-in)
53
+
54
+ Score : 4/5 (80%) — HEALTHY
55
+
56
+ ✅ Version update
57
+ ✅ CI status
58
+ ✅ Model upgrade
59
+ ✅ Configuration
60
+ ⚠ API endpoint
61
+ ```
62
+
63
+ Statuses: **HEALTHY** (≥80%) · **ACCEPTABLE** (60–79%) · **DEGRADED** (40–59%) · **CRITICAL** (<40%)
64
+
65
+ ### `zer0lint generate`
66
+ 3-phase diagnostic + fix:
67
+
68
+ 1. **Baseline** — test your config as-is
69
+ 2. **Re-test** — inject zer0lint's domain-aware extraction prompt at config level
70
+ 3. **Apply** — if improved, write the validated prompt to your config
71
+
72
+ ```
73
+ zer0lint v0.2.0 — extraction optimizer
74
+
75
+ [1/3] Baseline — testing current config as-is...
76
+ Baseline score: 4/5 (80%)
77
+
78
+ [2/3] Re-testing with zer0lint technical extraction prompt...
79
+ Improved score: 5/5 (100%)
80
+ Improvement: +20pp
81
+
82
+ Results:
83
+ Before : 4/5 (80%)
84
+ After : 5/5 (100%)
85
+ Δ : +20pp
86
+
87
+ ✅ Fix applied to config.
88
+ Backup: ~/.mem0/config.backup.2026-03-23T14:20:00
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Why Config-Level Injection Matters
94
+
95
+ **Important finding:** In mem0 v1.x, passing a prompt via `memory.add(..., prompt=X)` has **no measurable effect** on retrieval quality. The extraction prompt must live in the config (`custom_fact_extraction_prompt` field) to actually work.
96
+
97
+ zer0lint writes the validated prompt directly to your config — this is the correct fix.
98
+
99
+ ---
100
+
101
+ ## Supported Systems
102
+
103
+ | System | Status | Notes |
104
+ |---|---|---|
105
+ | mem0 (v1.x) | ✅ Supported | Full check + generate |
106
+ | Zep / Graphiti | 🔜 Planned | v0.3 |
107
+ | LangMem | 🔜 Planned | v0.4 |
108
+ | Generic adapter | 🔜 Planned | BYOC callables |
109
+
110
+ ---
111
+
112
+ ## Test Results (2026-03-23)
113
+
114
+ Horse race across models (5 technical + research facts):
115
+
116
+ | Model | Default prompt | zer0lint prompt | Δ |
117
+ |---|---|---|---|
118
+ | qwen3.5:4b | 80% | **100%** | +20pp |
119
+ | mistral:7b | 40% | 40% | 0pp |
120
+
121
+ **mistral:7b** produces malformed JSON regardless of prompt — model quality is the bottleneck. zer0lint detects this and recommends switching models.
122
+
123
+ Scale test (10 facts, 5 domains):
124
+
125
+ | | Score | % |
126
+ |---|---|---|
127
+ | Default | 7/10 | 70% |
128
+ | zer0lint | 9/10 | **90%** |
129
+
130
+ ---
131
+
132
+ ## Installation
133
+
134
+ ```bash
135
+ # From source (current)
136
+ git clone https://github.com/roli-lpci/zer0lint
137
+ cd zer0lint
138
+ pip install -e .
139
+
140
+ # PyPI (coming soon)
141
+ pip install zer0lint
142
+ ```
143
+
144
+ **Requirements:** Python 3.9+, mem0 v1.x, an Ollama or cloud LLM configured in your mem0 config.
145
+
146
+ ---
147
+
148
+ ## How It Works
149
+
150
+ zer0lint reads your existing mem0 config, borrows whatever LLM you already have configured, and runs a controlled recall test. No new API keys, no new models, no cloud calls beyond what you already have.
151
+
152
+ The extraction prompt it generates is **domain-aware** — tuned for technical/research work rather than personal assistant use cases. It's validated against synthetic facts before being applied, and your original config is always backed up.
153
+
154
+ ---
155
+
156
+ ## Built by Hermes Labs
157
+
158
+ zer0lint is part of the [Hermes Labs](https://hermes-labs.ai) AI agent tooling suite:
159
+
160
+ - **[lintlang](https://github.com/roli-lpci/lintlang)** — Static linter for AI agent tool descriptions and prompts
161
+ - **[Little Canary](https://github.com/roli-lpci/little-canary)** — Prompt injection detection
162
+ - **[Suy Sideguy](https://github.com/roli-lpci/suy-sideguy)** — Runtime policy enforcement for agents
163
+ - **zer0lint** — Memory extraction diagnostics ← you are here
164
+
165
+ ---
166
+
167
+ ## License
168
+
169
+ Apache 2.0
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zer0lint"
7
+ version = "0.1.0"
8
+ description = "mem0 extraction optimizer: diagnose failures and generate custom prompts for your system"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ dependencies = [
12
+ "mem0ai>=0.1.0",
13
+ "typer[all]>=0.9.0",
14
+ "rich>=13.0.0",
15
+ "httpx>=0.24.0",
16
+ "pydantic>=2.0.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=7.0",
22
+ "pytest-cov>=4.0",
23
+ "ruff>=0.1.0",
24
+ ]
25
+
26
+ [project.scripts]
27
+ zer0lint = "zer0lint.cli:app"
28
+
29
+ [tool.ruff]
30
+ line-length = 100
31
+ target-version = "py39"
32
+
33
+ [tool.ruff.lint]
34
+ select = ["E", "F", "W", "I"]
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
@@ -0,0 +1 @@
1
+ """Tests for zer0lint."""
@@ -0,0 +1,138 @@
1
+ """Tests for config application and backup."""
2
+
3
+ import json
4
+ import tempfile
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from zer0lint.fixer import (
10
+ apply_prompt,
11
+ backup_config,
12
+ detect_extraction_model,
13
+ detect_vector_store,
14
+ )
15
+
16
+
17
+ def test_backup_config():
18
+ """Test config backup creation."""
19
+ with tempfile.TemporaryDirectory() as tmpdir:
20
+ config_path = Path(tmpdir) / "config.json"
21
+ config_path.write_text('{"test": "data"}')
22
+
23
+ backup_path = backup_config(config_path)
24
+
25
+ assert Path(backup_path).exists()
26
+ assert "backup" in backup_path
27
+
28
+
29
+ def test_apply_prompt_new():
30
+ """Test applying a new prompt to config."""
31
+ with tempfile.TemporaryDirectory() as tmpdir:
32
+ config_path = Path(tmpdir) / "config.json"
33
+ config = {
34
+ "llm": {"provider": "ollama"},
35
+ "custom_fact_extraction_prompt": "old prompt",
36
+ }
37
+ config_path.write_text(json.dumps(config))
38
+
39
+ result = apply_prompt(config_path, "new prompt", backup=True)
40
+
41
+ assert result["success"] is True
42
+ assert result["backup_path"] is not None
43
+
44
+ # Verify new prompt was written
45
+ updated = json.loads(config_path.read_text())
46
+ assert updated["custom_fact_extraction_prompt"] == "new prompt"
47
+
48
+
49
+ def test_apply_prompt_no_backup():
50
+ """Test applying prompt without backup."""
51
+ with tempfile.TemporaryDirectory() as tmpdir:
52
+ config_path = Path(tmpdir) / "config.json"
53
+ config_path.write_text('{"custom_fact_extraction_prompt": "old"}')
54
+
55
+ result = apply_prompt(config_path, "new", backup=False)
56
+
57
+ assert result["success"] is True
58
+ assert result["backup_path"] is None
59
+
60
+
61
+ def test_apply_prompt_file_not_found():
62
+ """Test error handling for missing config."""
63
+ with pytest.raises(FileNotFoundError):
64
+ apply_prompt("/nonexistent/path/config.json", "new prompt")
65
+
66
+
67
+ def test_detect_extraction_model_ollama():
68
+ """Test model detection for Ollama config."""
69
+ config = {
70
+ "llm": {
71
+ "provider": "ollama",
72
+ "config": {"model": "mistral:7b", "ollama_base_url": "http://localhost:11434"},
73
+ }
74
+ }
75
+
76
+ model = detect_extraction_model(config)
77
+
78
+ assert model == "mistral:7b"
79
+
80
+
81
+ def test_detect_extraction_model_openai():
82
+ """Test model detection for OpenAI config."""
83
+ config = {
84
+ "llm": {
85
+ "provider": "openai",
86
+ "model": "gpt-4o",
87
+ }
88
+ }
89
+
90
+ model = detect_extraction_model(config)
91
+
92
+ assert model == "gpt-4o"
93
+
94
+
95
+ def test_detect_extraction_model_unknown():
96
+ """Test fallback for unknown config structure."""
97
+ config = {"llm": {}}
98
+
99
+ model = detect_extraction_model(config)
100
+
101
+ assert model == "unknown"
102
+
103
+
104
+ def test_detect_vector_store_chroma():
105
+ """Test vector store detection for Chroma."""
106
+ config = {
107
+ "vector_store": {
108
+ "provider": "chroma",
109
+ "config": {"collection_name": "mem0", "path": ".mem0_chroma"},
110
+ }
111
+ }
112
+
113
+ store = detect_vector_store(config)
114
+
115
+ assert store == "chroma"
116
+
117
+
118
+ def test_detect_vector_store_qdrant():
119
+ """Test vector store detection for Qdrant."""
120
+ config = {
121
+ "vector_store": {
122
+ "provider": "qdrant",
123
+ "config": {"collection_name": "mem0", "url": "http://localhost:6333"},
124
+ }
125
+ }
126
+
127
+ store = detect_vector_store(config)
128
+
129
+ assert store == "qdrant"
130
+
131
+
132
+ def test_detect_vector_store_unknown():
133
+ """Test fallback for unknown vector store."""
134
+ config = {"vector_store": {}}
135
+
136
+ store = detect_vector_store(config)
137
+
138
+ assert store == "unknown"
@@ -0,0 +1,92 @@
1
+ """Tests for memory sampling."""
2
+
3
+ from unittest.mock import MagicMock
4
+
5
+ import pytest
6
+
7
+ from zer0lint.sampler import MemorySample, analyze_sample_content, sample_memories
8
+
9
+
10
+ def test_memory_sample_model():
11
+ """Test MemorySample dataclass."""
12
+ sample = MemorySample(id="1", content="API runs on port 8421", metadata={"tag": "tech"})
13
+ assert sample.id == "1"
14
+ assert sample.content == "API runs on port 8421"
15
+ assert sample.metadata["tag"] == "tech"
16
+
17
+
18
+ def test_sample_memories_with_search():
19
+ """Test sampling memories via search method."""
20
+ mock_memory = MagicMock()
21
+ mock_memory.search.return_value = [
22
+ {"id": "1", "memory": "API runs on port 8421", "metadata": None},
23
+ {"id": "2", "memory": "Using gpt-4o-mini", "metadata": None},
24
+ ]
25
+
26
+ samples = sample_memories(mock_memory, limit=30)
27
+
28
+ assert len(samples) == 2
29
+ assert samples[0].id == "1"
30
+ assert "port 8421" in samples[0].content
31
+ mock_memory.search.assert_called_once()
32
+
33
+
34
+ def test_sample_memories_fallback_to_direct():
35
+ """Test fallback to direct store access when search fails."""
36
+ mock_memory = MagicMock()
37
+ mock_memory.search.side_effect = Exception("Search failed")
38
+ mock_memory.vector_store.get_all.return_value = [
39
+ {"id": "1", "content": "Memory 1", "text": None},
40
+ {"id": "2", "content": "Memory 2", "text": None},
41
+ ]
42
+
43
+ samples = sample_memories(mock_memory, limit=30)
44
+
45
+ assert len(samples) >= 1
46
+ assert samples[0].id == "1"
47
+
48
+
49
+ def test_sample_memories_empty():
50
+ """Test handling empty memories."""
51
+ mock_memory = MagicMock()
52
+ mock_memory.search.return_value = []
53
+ mock_memory.vector_store.get_all.side_effect = Exception("No store")
54
+
55
+ samples = sample_memories(mock_memory)
56
+
57
+ assert samples == []
58
+
59
+
60
+ def test_analyze_sample_content_technical():
61
+ """Test pattern analysis for technical content."""
62
+ samples = [
63
+ MemorySample(id="1", content="API server runs on port 8421"),
64
+ MemorySample(id="2", content="Updated to gpt-4o-mini model"),
65
+ MemorySample(id="3", content="Redis version 7.2.4 deployed"),
66
+ ]
67
+
68
+ patterns = analyze_sample_content(samples)
69
+
70
+ assert "technical" in patterns
71
+ assert any(kw in patterns["technical"] for kw in ["port", "version", "model"])
72
+
73
+
74
+ def test_analyze_sample_content_mixed():
75
+ """Test pattern analysis with mixed content."""
76
+ samples = [
77
+ MemorySample(id="1", content="API on port 8421"),
78
+ MemorySample(id="2", content="Diagnosis: acute bronchitis"),
79
+ MemorySample(id="3", content="Contract clause specifies indemnification"),
80
+ ]
81
+
82
+ patterns = analyze_sample_content(samples)
83
+
84
+ # Should detect multiple domains
85
+ detected_domains = list(patterns.keys())
86
+ assert len(detected_domains) > 0
87
+
88
+
89
+ def test_analyze_sample_content_empty():
90
+ """Test pattern analysis with no samples."""
91
+ patterns = analyze_sample_content([])
92
+ assert patterns == {}