tribalmemory 0.1.0__tar.gz → 0.2.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.
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/PKG-INFO +61 -8
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/README.md +60 -7
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/pyproject.toml +1 -1
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/cli.py +199 -35
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/interfaces.py +44 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/mcp/server.py +160 -14
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/app.py +53 -2
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/config.py +41 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/models.py +65 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/routes.py +68 -0
- tribalmemory-0.2.0/src/tribalmemory/services/fts_store.py +255 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/memory.py +193 -33
- tribalmemory-0.2.0/src/tribalmemory/services/reranker.py +267 -0
- tribalmemory-0.2.0/src/tribalmemory/services/session_store.py +412 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/vector_store.py +86 -1
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/PKG-INFO +61 -8
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/SOURCES.txt +6 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_a21_config.py +2 -2
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_a21_container.py +8 -8
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_a21_providers.py +10 -11
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_a21_system.py +4 -4
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_benchmarks.py +1 -1
- tribalmemory-0.2.0/tests/test_cli.py +428 -0
- tribalmemory-0.2.0/tests/test_hybrid_search.py +323 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_mcp_server.py +9 -5
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_negative_security.py +3 -3
- tribalmemory-0.2.0/tests/test_reranking.py +392 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_services.py +7 -7
- tribalmemory-0.2.0/tests/test_session_store.py +429 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_tier1_functional.py +3 -3
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_tier2_capability.py +1 -1
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_tier3_emergence.py +1 -1
- tribalmemory-0.1.0/tests/test_cli.py +0 -161
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/LICENSE +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/setup.cfg +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/config/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/config/providers.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/config/system.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/container/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/container/container.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/base.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/deduplication.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/lancedb.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/memory.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/mock.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/openai.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/timestamp.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/system.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/mcp/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/mcp/__main__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/performance/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/performance/benchmarks.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/performance/corpus_generator.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/portability/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/portability/embedding_metadata.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/__main__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/deduplication.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/embeddings.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/import_export.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/__init__.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/embedding_utils.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/fixtures.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/metrics.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/mocks.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/semantic_expansions.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/utils.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/dependency_links.txt +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/entry_points.txt +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/requires.txt +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/top_level.txt +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_embedding_portability.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_import_export.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_local_embeddings.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_mcp_integration.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_memory_harness.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_performance.py +0 -0
- {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tribalmemory
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Shared memory infrastructure for multi-instance AI agents
|
|
5
5
|
Author-email: Joe <joe@example.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -48,12 +48,48 @@ Dynamic: license-file
|
|
|
48
48
|
|
|
49
49
|
One memory store, many agents. Teach Claude Code something — Codex already knows it. That's not just persistence — it's **cross-agent intelligence**.
|
|
50
50
|
|
|
51
|
+
<p align="center">
|
|
52
|
+
<img src="docs/assets/one-brain-two-agents.gif" alt="One Brain, Two Agents — Claude Code stores memories, Codex recalls them" width="700">
|
|
53
|
+
<br>
|
|
54
|
+
<em>Claude Code stores architecture decisions → Codex recalls them instantly</em>
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
[](https://asciinema.org/a/ZM74iIXzM07SV21P)
|
|
58
|
+
[](https://pypi.org/project/tribalmemory/)
|
|
59
|
+
[](LICENSE)
|
|
60
|
+
|
|
51
61
|
## Why
|
|
52
62
|
|
|
53
63
|
Every AI coding assistant starts fresh. Claude Code doesn't know what you told Codex. Codex doesn't know what you told Claude. You repeat yourself constantly.
|
|
54
64
|
|
|
55
65
|
Tribal Memory is a shared memory server that any AI agent can connect to. Store a memory from one agent, recall it from another. It just works.
|
|
56
66
|
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
**macOS:**
|
|
70
|
+
```bash
|
|
71
|
+
# Install uv (https://docs.astral.sh/uv/)
|
|
72
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
73
|
+
|
|
74
|
+
# Restart your terminal, or run:
|
|
75
|
+
source ~/.zshrc
|
|
76
|
+
|
|
77
|
+
# Install tribalmemory
|
|
78
|
+
uv tool install tribalmemory
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **Why uv?** macOS blocks `pip install` into the system Python with "externally-managed-environment" errors. `uv tool install` handles isolated environments automatically.
|
|
82
|
+
|
|
83
|
+
**Linux:**
|
|
84
|
+
```bash
|
|
85
|
+
pip install tribalmemory
|
|
86
|
+
|
|
87
|
+
# Or with uv:
|
|
88
|
+
# curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
89
|
+
# source ~/.bashrc
|
|
90
|
+
# uv tool install tribalmemory
|
|
91
|
+
```
|
|
92
|
+
|
|
57
93
|
## Quick Start
|
|
58
94
|
|
|
59
95
|
### Option A: Local Mode (Zero Cloud, Zero Cost)
|
|
@@ -61,8 +97,6 @@ Tribal Memory is a shared memory server that any AI agent can connect to. Store
|
|
|
61
97
|
No API keys. No cloud. Everything runs on your machine.
|
|
62
98
|
|
|
63
99
|
```bash
|
|
64
|
-
pip install tribalmemory
|
|
65
|
-
|
|
66
100
|
# Set up with local Ollama embeddings
|
|
67
101
|
tribalmemory init --local
|
|
68
102
|
|
|
@@ -76,8 +110,6 @@ tribalmemory serve
|
|
|
76
110
|
### Option B: OpenAI Embeddings
|
|
77
111
|
|
|
78
112
|
```bash
|
|
79
|
-
pip install tribalmemory
|
|
80
|
-
|
|
81
113
|
# Set up with OpenAI
|
|
82
114
|
export OPENAI_API_KEY=sk-...
|
|
83
115
|
tribalmemory init
|
|
@@ -224,19 +256,40 @@ await service.correct(
|
|
|
224
256
|
)
|
|
225
257
|
```
|
|
226
258
|
|
|
259
|
+
## Demo
|
|
260
|
+
|
|
261
|
+
See cross-agent memory sharing in action:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# Start the server
|
|
265
|
+
tribalmemory serve
|
|
266
|
+
|
|
267
|
+
# Run the interactive demo
|
|
268
|
+
./demo.sh
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
See [docs/demo-output.md](docs/demo-output.md) for example output.
|
|
272
|
+
|
|
227
273
|
## HTTP API
|
|
228
274
|
|
|
275
|
+
All endpoints are under the `/v1` prefix.
|
|
276
|
+
|
|
229
277
|
```bash
|
|
230
278
|
# Store a memory
|
|
231
|
-
curl -X POST http://localhost:18790/
|
|
279
|
+
curl -X POST http://localhost:18790/v1/remember \
|
|
232
280
|
-H "Content-Type: application/json" \
|
|
233
281
|
-d '{"content": "The database uses Postgres 16", "tags": ["infra"]}'
|
|
234
282
|
|
|
235
283
|
# Search memories
|
|
236
|
-
curl
|
|
284
|
+
curl -X POST http://localhost:18790/v1/recall \
|
|
285
|
+
-H "Content-Type: application/json" \
|
|
286
|
+
-d '{"query": "what database", "limit": 5}'
|
|
237
287
|
|
|
238
288
|
# Get stats
|
|
239
|
-
curl http://localhost:18790/stats
|
|
289
|
+
curl http://localhost:18790/v1/stats
|
|
290
|
+
|
|
291
|
+
# Health check
|
|
292
|
+
curl http://localhost:18790/v1/health
|
|
240
293
|
```
|
|
241
294
|
|
|
242
295
|
## OpenClaw Integration
|
|
@@ -4,12 +4,48 @@
|
|
|
4
4
|
|
|
5
5
|
One memory store, many agents. Teach Claude Code something — Codex already knows it. That's not just persistence — it's **cross-agent intelligence**.
|
|
6
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="docs/assets/one-brain-two-agents.gif" alt="One Brain, Two Agents — Claude Code stores memories, Codex recalls them" width="700">
|
|
9
|
+
<br>
|
|
10
|
+
<em>Claude Code stores architecture decisions → Codex recalls them instantly</em>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
[](https://asciinema.org/a/ZM74iIXzM07SV21P)
|
|
14
|
+
[](https://pypi.org/project/tribalmemory/)
|
|
15
|
+
[](LICENSE)
|
|
16
|
+
|
|
7
17
|
## Why
|
|
8
18
|
|
|
9
19
|
Every AI coding assistant starts fresh. Claude Code doesn't know what you told Codex. Codex doesn't know what you told Claude. You repeat yourself constantly.
|
|
10
20
|
|
|
11
21
|
Tribal Memory is a shared memory server that any AI agent can connect to. Store a memory from one agent, recall it from another. It just works.
|
|
12
22
|
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
**macOS:**
|
|
26
|
+
```bash
|
|
27
|
+
# Install uv (https://docs.astral.sh/uv/)
|
|
28
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
29
|
+
|
|
30
|
+
# Restart your terminal, or run:
|
|
31
|
+
source ~/.zshrc
|
|
32
|
+
|
|
33
|
+
# Install tribalmemory
|
|
34
|
+
uv tool install tribalmemory
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> **Why uv?** macOS blocks `pip install` into the system Python with "externally-managed-environment" errors. `uv tool install` handles isolated environments automatically.
|
|
38
|
+
|
|
39
|
+
**Linux:**
|
|
40
|
+
```bash
|
|
41
|
+
pip install tribalmemory
|
|
42
|
+
|
|
43
|
+
# Or with uv:
|
|
44
|
+
# curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
45
|
+
# source ~/.bashrc
|
|
46
|
+
# uv tool install tribalmemory
|
|
47
|
+
```
|
|
48
|
+
|
|
13
49
|
## Quick Start
|
|
14
50
|
|
|
15
51
|
### Option A: Local Mode (Zero Cloud, Zero Cost)
|
|
@@ -17,8 +53,6 @@ Tribal Memory is a shared memory server that any AI agent can connect to. Store
|
|
|
17
53
|
No API keys. No cloud. Everything runs on your machine.
|
|
18
54
|
|
|
19
55
|
```bash
|
|
20
|
-
pip install tribalmemory
|
|
21
|
-
|
|
22
56
|
# Set up with local Ollama embeddings
|
|
23
57
|
tribalmemory init --local
|
|
24
58
|
|
|
@@ -32,8 +66,6 @@ tribalmemory serve
|
|
|
32
66
|
### Option B: OpenAI Embeddings
|
|
33
67
|
|
|
34
68
|
```bash
|
|
35
|
-
pip install tribalmemory
|
|
36
|
-
|
|
37
69
|
# Set up with OpenAI
|
|
38
70
|
export OPENAI_API_KEY=sk-...
|
|
39
71
|
tribalmemory init
|
|
@@ -180,19 +212,40 @@ await service.correct(
|
|
|
180
212
|
)
|
|
181
213
|
```
|
|
182
214
|
|
|
215
|
+
## Demo
|
|
216
|
+
|
|
217
|
+
See cross-agent memory sharing in action:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# Start the server
|
|
221
|
+
tribalmemory serve
|
|
222
|
+
|
|
223
|
+
# Run the interactive demo
|
|
224
|
+
./demo.sh
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
See [docs/demo-output.md](docs/demo-output.md) for example output.
|
|
228
|
+
|
|
183
229
|
## HTTP API
|
|
184
230
|
|
|
231
|
+
All endpoints are under the `/v1` prefix.
|
|
232
|
+
|
|
185
233
|
```bash
|
|
186
234
|
# Store a memory
|
|
187
|
-
curl -X POST http://localhost:18790/
|
|
235
|
+
curl -X POST http://localhost:18790/v1/remember \
|
|
188
236
|
-H "Content-Type: application/json" \
|
|
189
237
|
-d '{"content": "The database uses Postgres 16", "tags": ["infra"]}'
|
|
190
238
|
|
|
191
239
|
# Search memories
|
|
192
|
-
curl
|
|
240
|
+
curl -X POST http://localhost:18790/v1/recall \
|
|
241
|
+
-H "Content-Type: application/json" \
|
|
242
|
+
-d '{"query": "what database", "limit": 5}'
|
|
193
243
|
|
|
194
244
|
# Get stats
|
|
195
|
-
curl http://localhost:18790/stats
|
|
245
|
+
curl http://localhost:18790/v1/stats
|
|
246
|
+
|
|
247
|
+
# Health check
|
|
248
|
+
curl http://localhost:18790/v1/health
|
|
196
249
|
```
|
|
197
250
|
|
|
198
251
|
## OpenClaw Integration
|
|
@@ -9,6 +9,7 @@ Usage:
|
|
|
9
9
|
import argparse
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
|
+
import shutil
|
|
12
13
|
import sys
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
|
|
@@ -26,7 +27,37 @@ TRIBAL_DIR = Path.home() / ".tribal-memory"
|
|
|
26
27
|
CONFIG_FILE = TRIBAL_DIR / "config.yaml"
|
|
27
28
|
DEFAULT_INSTANCE_ID = "default"
|
|
28
29
|
|
|
29
|
-
#
|
|
30
|
+
# Paths to global instructions files (relative to home)
|
|
31
|
+
CLAUDE_INSTRUCTIONS_FILE = Path(".claude") / "CLAUDE.md"
|
|
32
|
+
CODEX_INSTRUCTIONS_FILE = Path(".codex") / "AGENTS.md"
|
|
33
|
+
|
|
34
|
+
# Section marker used to detect if auto-capture instructions already exist
|
|
35
|
+
_AUTO_CAPTURE_MARKER = "## Tribal Memory — Auto-Capture"
|
|
36
|
+
|
|
37
|
+
AUTO_CAPTURE_INSTRUCTIONS = f"""\
|
|
38
|
+
|
|
39
|
+
{_AUTO_CAPTURE_MARKER}
|
|
40
|
+
|
|
41
|
+
You have access to Tribal Memory — a shared memory system for AI agents.
|
|
42
|
+
Use it proactively:
|
|
43
|
+
|
|
44
|
+
**After completing tasks, making decisions, or learning something:**
|
|
45
|
+
- Call `tribal_remember` to store the insight with relevant tags
|
|
46
|
+
- Good memories: architectural decisions, bug fixes, "gotchas", user preferences,
|
|
47
|
+
project conventions, API quirks, things that took a while to figure out
|
|
48
|
+
- Bad memories: trivial actions ("I ran the tests"), obvious facts, temporary state
|
|
49
|
+
|
|
50
|
+
**Before answering questions about prior work:**
|
|
51
|
+
- Call `tribal_recall` to search for relevant context first
|
|
52
|
+
- Search by topic, not exact phrases — it's semantic search
|
|
53
|
+
|
|
54
|
+
**Tips:**
|
|
55
|
+
- Use tags to organize: `["python", "debugging"]`, `["api", "auth"]`
|
|
56
|
+
- One clear insight per memory is better than a wall of text
|
|
57
|
+
- If you're unsure whether to remember something, remember it — recall is cheap
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# MCP config for Claude Code CLI and Claude Desktop
|
|
30
61
|
CLAUDE_CODE_MCP_CONFIG = {
|
|
31
62
|
"mcpServers": {
|
|
32
63
|
"tribal-memory": {
|
|
@@ -55,7 +86,7 @@ db:
|
|
|
55
86
|
server:
|
|
56
87
|
host: 127.0.0.1
|
|
57
88
|
port: 18790
|
|
58
|
-
"""
|
|
89
|
+
{auto_capture_line}"""
|
|
59
90
|
|
|
60
91
|
LOCAL_CONFIG_TEMPLATE = """\
|
|
61
92
|
# Tribal Memory Configuration — Local Mode (Zero Cloud)
|
|
@@ -78,7 +109,7 @@ db:
|
|
|
78
109
|
server:
|
|
79
110
|
host: 127.0.0.1
|
|
80
111
|
port: 18790
|
|
81
|
-
"""
|
|
112
|
+
{auto_capture_line}"""
|
|
82
113
|
|
|
83
114
|
|
|
84
115
|
def cmd_init(args: argparse.Namespace) -> int:
|
|
@@ -89,16 +120,23 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
89
120
|
# Create config directory
|
|
90
121
|
TRIBAL_DIR.mkdir(parents=True, exist_ok=True)
|
|
91
122
|
|
|
123
|
+
# Auto-capture config line (only included when flag is set)
|
|
124
|
+
auto_capture_line = ""
|
|
125
|
+
if args.auto_capture:
|
|
126
|
+
auto_capture_line = "\nauto_capture: true\n"
|
|
127
|
+
|
|
92
128
|
# Choose template
|
|
93
129
|
if args.local:
|
|
94
130
|
config_content = LOCAL_CONFIG_TEMPLATE.format(
|
|
95
131
|
instance_id=instance_id,
|
|
96
132
|
db_path=db_path,
|
|
133
|
+
auto_capture_line=auto_capture_line,
|
|
97
134
|
)
|
|
98
135
|
else:
|
|
99
136
|
config_content = OPENAI_CONFIG_TEMPLATE.format(
|
|
100
137
|
instance_id=instance_id,
|
|
101
138
|
db_path=db_path,
|
|
139
|
+
auto_capture_line=auto_capture_line,
|
|
102
140
|
)
|
|
103
141
|
|
|
104
142
|
# Write config
|
|
@@ -124,65 +162,185 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
124
162
|
if args.codex:
|
|
125
163
|
_setup_codex_mcp(args.local)
|
|
126
164
|
|
|
165
|
+
# Set up auto-capture instructions
|
|
166
|
+
if args.auto_capture:
|
|
167
|
+
_setup_auto_capture(
|
|
168
|
+
claude_code=args.claude_code,
|
|
169
|
+
codex=args.codex,
|
|
170
|
+
)
|
|
171
|
+
|
|
127
172
|
print()
|
|
128
173
|
print("🚀 Start the server:")
|
|
129
174
|
print(" tribalmemory serve")
|
|
130
175
|
print()
|
|
131
176
|
print("🧠 Or use with Claude Code (MCP):")
|
|
132
177
|
print(" tribalmemory-mcp")
|
|
178
|
+
|
|
179
|
+
if not args.auto_capture:
|
|
180
|
+
print()
|
|
181
|
+
print("💡 Want your agents to remember things automatically?")
|
|
182
|
+
print(" tribalmemory init --auto-capture --force")
|
|
133
183
|
|
|
134
184
|
return 0
|
|
135
185
|
|
|
136
186
|
|
|
187
|
+
def _setup_auto_capture(claude_code: bool = False, codex: bool = False) -> None:
|
|
188
|
+
"""Write auto-capture instructions to agent instruction files.
|
|
189
|
+
|
|
190
|
+
Appends memory usage instructions so agents proactively use
|
|
191
|
+
tribal_remember and tribal_recall without being explicitly asked.
|
|
192
|
+
|
|
193
|
+
Writes to:
|
|
194
|
+
- ~/.claude/CLAUDE.md (Claude Code) — when --claude-code is set
|
|
195
|
+
- ~/.codex/AGENTS.md (Codex CLI) — when --codex is set
|
|
196
|
+
- Both files if neither flag is set (covers the common case)
|
|
197
|
+
|
|
198
|
+
Skips if instructions are already present (idempotent).
|
|
199
|
+
"""
|
|
200
|
+
# If no specific flag, write to both (default behavior)
|
|
201
|
+
if not claude_code and not codex:
|
|
202
|
+
claude_code = codex = True
|
|
203
|
+
|
|
204
|
+
targets = []
|
|
205
|
+
if claude_code:
|
|
206
|
+
targets.append(("Claude Code", Path.home() / CLAUDE_INSTRUCTIONS_FILE))
|
|
207
|
+
if codex:
|
|
208
|
+
targets.append(("Codex CLI", Path.home() / CODEX_INSTRUCTIONS_FILE))
|
|
209
|
+
|
|
210
|
+
for label, instructions_path in targets:
|
|
211
|
+
_write_instructions_file(instructions_path, label)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _write_instructions_file(instructions_path: Path, label: str) -> None:
|
|
215
|
+
"""Write auto-capture instructions to a single instructions file."""
|
|
216
|
+
instructions_path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
|
|
218
|
+
if instructions_path.exists():
|
|
219
|
+
existing = instructions_path.read_text()
|
|
220
|
+
if _AUTO_CAPTURE_MARKER in existing:
|
|
221
|
+
print(f"✅ Auto-capture already present in {label}: {instructions_path}")
|
|
222
|
+
return
|
|
223
|
+
# Append to existing file
|
|
224
|
+
if not existing.endswith("\n"):
|
|
225
|
+
existing += "\n"
|
|
226
|
+
instructions_path.write_text(existing + AUTO_CAPTURE_INSTRUCTIONS)
|
|
227
|
+
else:
|
|
228
|
+
instructions_path.write_text(AUTO_CAPTURE_INSTRUCTIONS.lstrip("\n"))
|
|
229
|
+
|
|
230
|
+
print(f"✅ Auto-capture instructions written for {label}: {instructions_path}")
|
|
231
|
+
|
|
232
|
+
|
|
137
233
|
def _setup_claude_code_mcp(is_local: bool) -> None:
|
|
138
|
-
"""Add Tribal Memory to Claude Code's MCP configuration.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
234
|
+
"""Add Tribal Memory to Claude Code's MCP configuration.
|
|
235
|
+
|
|
236
|
+
Claude Code CLI reads MCP servers from ~/.claude.json (user scope).
|
|
237
|
+
Claude Desktop reads from platform-specific claude_desktop_config.json.
|
|
238
|
+
We update both if they exist, and always ensure ~/.claude.json is set.
|
|
239
|
+
"""
|
|
240
|
+
# Claude Code CLI config (primary — this is what `claude` CLI reads)
|
|
241
|
+
claude_cli_config = Path.home() / ".claude.json"
|
|
242
|
+
|
|
243
|
+
# Claude Desktop config paths (secondary — update if they exist)
|
|
244
|
+
claude_desktop_paths = [
|
|
245
|
+
Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json", # macOS
|
|
246
|
+
Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json", # Windows
|
|
247
|
+
Path.home() / ".claude" / "claude_desktop_config.json", # Legacy / Linux
|
|
144
248
|
]
|
|
145
249
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
250
|
+
# Resolve full path to tribalmemory-mcp binary.
|
|
251
|
+
# Claude Desktop doesn't inherit the user's shell PATH (e.g. ~/.local/bin),
|
|
252
|
+
# so we need the absolute path for it to find the command.
|
|
253
|
+
mcp_command = _resolve_mcp_command()
|
|
254
|
+
|
|
255
|
+
mcp_entry = {
|
|
256
|
+
"command": mcp_command,
|
|
257
|
+
"env": {},
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if is_local:
|
|
261
|
+
mcp_entry["env"]["TRIBAL_MEMORY_EMBEDDING_API_BASE"] = "http://localhost:11434/v1"
|
|
262
|
+
|
|
263
|
+
# Always update Claude Code CLI config (~/.claude.json)
|
|
264
|
+
_update_mcp_config(claude_cli_config, mcp_entry, create_if_missing=True)
|
|
265
|
+
print(f"✅ Claude Code CLI config updated: {claude_cli_config}")
|
|
266
|
+
|
|
267
|
+
# Also update Claude Desktop config (create platform-appropriate path)
|
|
268
|
+
desktop_path = _get_claude_desktop_config_path()
|
|
269
|
+
_update_mcp_config(desktop_path, mcp_entry, create_if_missing=True)
|
|
270
|
+
print(f"✅ Claude Desktop config updated: {desktop_path}")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _resolve_mcp_command() -> str:
|
|
274
|
+
"""Resolve the full path to the tribalmemory-mcp binary.
|
|
275
|
+
|
|
276
|
+
Claude Desktop doesn't inherit the user's shell PATH (e.g. ~/.local/bin
|
|
277
|
+
from uv/pipx installs), so bare command names like "tribalmemory-mcp"
|
|
278
|
+
fail with "No such file or directory". We resolve the absolute path at
|
|
279
|
+
init time so the config works regardless of the app's PATH.
|
|
280
|
+
|
|
281
|
+
Falls back to the bare command name if not found on PATH (e.g. user
|
|
282
|
+
hasn't installed yet and will do so later).
|
|
283
|
+
"""
|
|
284
|
+
resolved = shutil.which("tribalmemory-mcp")
|
|
285
|
+
if resolved:
|
|
286
|
+
return resolved
|
|
287
|
+
|
|
288
|
+
# Check common tool install locations that might not be on PATH
|
|
289
|
+
base_name = "tribalmemory-mcp"
|
|
290
|
+
search_dirs = [
|
|
291
|
+
Path.home() / ".local" / "bin", # uv/pipx (Linux/macOS)
|
|
292
|
+
Path.home() / ".cargo" / "bin", # unlikely but possible
|
|
293
|
+
]
|
|
294
|
+
# On Windows, executables may have .exe/.cmd extensions
|
|
295
|
+
suffixes = [""]
|
|
296
|
+
if sys.platform == "win32":
|
|
297
|
+
suffixes = [".exe", ".cmd", ""]
|
|
298
|
+
|
|
299
|
+
for search_dir in search_dirs:
|
|
300
|
+
for suffix in suffixes:
|
|
301
|
+
candidate = search_dir / (base_name + suffix)
|
|
302
|
+
if candidate.exists() and os.access(candidate, os.X_OK):
|
|
303
|
+
return str(candidate)
|
|
304
|
+
|
|
305
|
+
# Fall back to bare command — will work if PATH is set correctly
|
|
306
|
+
return "tribalmemory-mcp"
|
|
151
307
|
|
|
152
|
-
if config_path is None:
|
|
153
|
-
# Create default location
|
|
154
|
-
config_path = claude_config_paths[0]
|
|
155
|
-
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
156
308
|
|
|
157
|
-
|
|
309
|
+
def _get_claude_desktop_config_path() -> Path:
|
|
310
|
+
"""Get the platform-appropriate Claude Desktop config path."""
|
|
311
|
+
if sys.platform == "darwin":
|
|
312
|
+
return Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
|
|
313
|
+
elif sys.platform == "win32":
|
|
314
|
+
return Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json"
|
|
315
|
+
else:
|
|
316
|
+
return Path.home() / ".claude" / "claude_desktop_config.json"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _update_mcp_config(
|
|
320
|
+
config_path: Path, mcp_entry: dict, create_if_missing: bool = False
|
|
321
|
+
) -> None:
|
|
322
|
+
"""Update an MCP config file with the tribal-memory server entry."""
|
|
158
323
|
if config_path.exists():
|
|
159
324
|
try:
|
|
160
325
|
existing = json.loads(config_path.read_text())
|
|
161
326
|
except json.JSONDecodeError as e:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
327
|
+
backup_path = config_path.with_suffix(".json.bak")
|
|
328
|
+
config_path.rename(backup_path)
|
|
329
|
+
print(f"⚠️ Existing config has invalid JSON: {e}")
|
|
330
|
+
print(f" Backed up to {backup_path}")
|
|
165
331
|
print(f" Creating fresh config at {config_path}")
|
|
166
332
|
existing = {}
|
|
167
|
-
|
|
333
|
+
elif create_if_missing:
|
|
334
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
168
335
|
existing = {}
|
|
336
|
+
else:
|
|
337
|
+
return
|
|
169
338
|
|
|
170
|
-
# Merge MCP server config
|
|
171
339
|
if "mcpServers" not in existing:
|
|
172
340
|
existing["mcpServers"] = {}
|
|
173
341
|
|
|
174
|
-
mcp_entry = {
|
|
175
|
-
"command": "tribalmemory-mcp",
|
|
176
|
-
"env": {},
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if is_local:
|
|
180
|
-
mcp_entry["env"]["TRIBAL_MEMORY_EMBEDDING_API_BASE"] = "http://localhost:11434/v1"
|
|
181
|
-
|
|
182
342
|
existing["mcpServers"]["tribal-memory"] = mcp_entry
|
|
183
|
-
|
|
184
343
|
config_path.write_text(json.dumps(existing, indent=2) + "\n")
|
|
185
|
-
print(f"✅ Claude Code MCP config updated: {config_path}")
|
|
186
344
|
|
|
187
345
|
|
|
188
346
|
def _setup_codex_mcp(is_local: bool) -> None:
|
|
@@ -190,6 +348,10 @@ def _setup_codex_mcp(is_local: bool) -> None:
|
|
|
190
348
|
codex_config_path = Path.home() / ".codex" / "config.toml"
|
|
191
349
|
codex_config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
192
350
|
|
|
351
|
+
# Resolve full path (same reason as Claude Desktop — Codex may not
|
|
352
|
+
# inherit the user's full shell PATH)
|
|
353
|
+
mcp_command = _resolve_mcp_command()
|
|
354
|
+
|
|
193
355
|
# Build the TOML section manually (avoid tomli_w dependency)
|
|
194
356
|
# Codex uses [mcp_servers.name] sections in config.toml
|
|
195
357
|
section_marker = "[mcp_servers.tribal-memory]"
|
|
@@ -198,7 +360,7 @@ def _setup_codex_mcp(is_local: bool) -> None:
|
|
|
198
360
|
"",
|
|
199
361
|
"# Tribal Memory — shared memory for AI agents",
|
|
200
362
|
section_marker,
|
|
201
|
-
'command = "
|
|
363
|
+
f'command = "{mcp_command}"',
|
|
202
364
|
]
|
|
203
365
|
|
|
204
366
|
if is_local:
|
|
@@ -265,6 +427,8 @@ def main() -> None:
|
|
|
265
427
|
help="Configure Claude Code MCP integration")
|
|
266
428
|
init_parser.add_argument("--codex", action="store_true",
|
|
267
429
|
help="Configure Codex CLI MCP integration")
|
|
430
|
+
init_parser.add_argument("--auto-capture", action="store_true",
|
|
431
|
+
help="Enable auto-capture (writes instructions to agent config files)")
|
|
268
432
|
init_parser.add_argument("--instance-id", type=str, default=None,
|
|
269
433
|
help="Instance identifier (default: 'default')")
|
|
270
434
|
init_parser.add_argument("--force", action="store_true",
|
|
@@ -174,6 +174,50 @@ class IVectorStore(ABC):
|
|
|
174
174
|
"""Count memories matching filters."""
|
|
175
175
|
pass
|
|
176
176
|
|
|
177
|
+
async def get_stats(self) -> dict:
|
|
178
|
+
"""Compute aggregate statistics over all memories.
|
|
179
|
+
|
|
180
|
+
Returns dict with keys:
|
|
181
|
+
total_memories, by_source_type, by_tag, by_instance, corrections
|
|
182
|
+
|
|
183
|
+
Default implementation iterates in pages of 500. Subclasses
|
|
184
|
+
should override with native queries (SQL GROUP BY, etc.) for
|
|
185
|
+
stores with >10k entries.
|
|
186
|
+
"""
|
|
187
|
+
page_size = 500
|
|
188
|
+
total = 0
|
|
189
|
+
corrections = 0
|
|
190
|
+
by_source: dict[str, int] = {}
|
|
191
|
+
by_instance: dict[str, int] = {}
|
|
192
|
+
by_tag: dict[str, int] = {}
|
|
193
|
+
|
|
194
|
+
offset = 0
|
|
195
|
+
while True:
|
|
196
|
+
page = await self.list(limit=page_size, offset=offset)
|
|
197
|
+
if not page:
|
|
198
|
+
break
|
|
199
|
+
total += len(page)
|
|
200
|
+
for m in page:
|
|
201
|
+
src = m.source_type.value
|
|
202
|
+
by_source[src] = by_source.get(src, 0) + 1
|
|
203
|
+
inst = m.source_instance
|
|
204
|
+
by_instance[inst] = by_instance.get(inst, 0) + 1
|
|
205
|
+
for tag in m.tags:
|
|
206
|
+
by_tag[tag] = by_tag.get(tag, 0) + 1
|
|
207
|
+
if m.supersedes:
|
|
208
|
+
corrections += 1
|
|
209
|
+
if len(page) < page_size:
|
|
210
|
+
break
|
|
211
|
+
offset += page_size
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
"total_memories": total,
|
|
215
|
+
"by_source_type": by_source,
|
|
216
|
+
"by_tag": by_tag,
|
|
217
|
+
"by_instance": by_instance,
|
|
218
|
+
"corrections": corrections,
|
|
219
|
+
}
|
|
220
|
+
|
|
177
221
|
|
|
178
222
|
class IDeduplicationService(ABC):
|
|
179
223
|
"""Interface for detecting duplicate memories."""
|