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.
Files changed (82) hide show
  1. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/PKG-INFO +61 -8
  2. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/README.md +60 -7
  3. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/pyproject.toml +1 -1
  4. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/cli.py +199 -35
  5. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/interfaces.py +44 -0
  6. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/mcp/server.py +160 -14
  7. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/app.py +53 -2
  8. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/config.py +41 -0
  9. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/models.py +65 -0
  10. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/routes.py +68 -0
  11. tribalmemory-0.2.0/src/tribalmemory/services/fts_store.py +255 -0
  12. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/memory.py +193 -33
  13. tribalmemory-0.2.0/src/tribalmemory/services/reranker.py +267 -0
  14. tribalmemory-0.2.0/src/tribalmemory/services/session_store.py +412 -0
  15. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/vector_store.py +86 -1
  16. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/PKG-INFO +61 -8
  17. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/SOURCES.txt +6 -0
  18. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_a21_config.py +2 -2
  19. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_a21_container.py +8 -8
  20. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_a21_providers.py +10 -11
  21. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_a21_system.py +4 -4
  22. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_benchmarks.py +1 -1
  23. tribalmemory-0.2.0/tests/test_cli.py +428 -0
  24. tribalmemory-0.2.0/tests/test_hybrid_search.py +323 -0
  25. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_mcp_server.py +9 -5
  26. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_negative_security.py +3 -3
  27. tribalmemory-0.2.0/tests/test_reranking.py +392 -0
  28. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_services.py +7 -7
  29. tribalmemory-0.2.0/tests/test_session_store.py +429 -0
  30. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_tier1_functional.py +3 -3
  31. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_tier2_capability.py +1 -1
  32. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_tier3_emergence.py +1 -1
  33. tribalmemory-0.1.0/tests/test_cli.py +0 -161
  34. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/LICENSE +0 -0
  35. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/setup.cfg +0 -0
  36. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/__init__.py +0 -0
  37. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/__init__.py +0 -0
  38. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/config/__init__.py +0 -0
  39. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/config/providers.py +0 -0
  40. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/config/system.py +0 -0
  41. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/container/__init__.py +0 -0
  42. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/container/container.py +0 -0
  43. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/__init__.py +0 -0
  44. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/base.py +0 -0
  45. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/deduplication.py +0 -0
  46. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/lancedb.py +0 -0
  47. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/memory.py +0 -0
  48. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/mock.py +0 -0
  49. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/openai.py +0 -0
  50. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/providers/timestamp.py +0 -0
  51. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/a21/system.py +0 -0
  52. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/mcp/__init__.py +0 -0
  53. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/mcp/__main__.py +0 -0
  54. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/performance/__init__.py +0 -0
  55. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/performance/benchmarks.py +0 -0
  56. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/performance/corpus_generator.py +0 -0
  57. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/portability/__init__.py +0 -0
  58. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/portability/embedding_metadata.py +0 -0
  59. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/__init__.py +0 -0
  60. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/server/__main__.py +0 -0
  61. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/__init__.py +0 -0
  62. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/deduplication.py +0 -0
  63. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/embeddings.py +0 -0
  64. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/services/import_export.py +0 -0
  65. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/__init__.py +0 -0
  66. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/embedding_utils.py +0 -0
  67. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/fixtures.py +0 -0
  68. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/metrics.py +0 -0
  69. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/mocks.py +0 -0
  70. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/testing/semantic_expansions.py +0 -0
  71. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory/utils.py +0 -0
  72. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/dependency_links.txt +0 -0
  73. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/entry_points.txt +0 -0
  74. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/requires.txt +0 -0
  75. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/src/tribalmemory.egg-info/top_level.txt +0 -0
  76. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_embedding_portability.py +0 -0
  77. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_import_export.py +0 -0
  78. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_local_embeddings.py +0 -0
  79. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_mcp_integration.py +0 -0
  80. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_memory_harness.py +0 -0
  81. {tribalmemory-0.1.0 → tribalmemory-0.2.0}/tests/test_performance.py +0 -0
  82. {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.1.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
+ [![asciinema demo](https://img.shields.io/badge/demo-asciinema-d40000)](https://asciinema.org/a/ZM74iIXzM07SV21P)
58
+ [![PyPI](https://img.shields.io/pypi/v/tribalmemory)](https://pypi.org/project/tribalmemory/)
59
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](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/memories \
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 "http://localhost:18790/memories/search?query=what+database&limit=5"
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
+ [![asciinema demo](https://img.shields.io/badge/demo-asciinema-d40000)](https://asciinema.org/a/ZM74iIXzM07SV21P)
14
+ [![PyPI](https://img.shields.io/pypi/v/tribalmemory)](https://pypi.org/project/tribalmemory/)
15
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](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/memories \
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 "http://localhost:18790/memories/search?query=what+database&limit=5"
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
@@ -7,7 +7,7 @@ where = ["src"]
7
7
 
8
8
  [project]
9
9
  name = "tribalmemory"
10
- version = "0.1.0"
10
+ version = "0.2.0"
11
11
  description = "Shared memory infrastructure for multi-instance AI agents"
12
12
  readme = "README.md"
13
13
  license = {text = "Apache-2.0"}
@@ -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
- # MCP config for Claude Code (claude_desktop_config.json)
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
- # Claude Code MCP config paths by platform
140
- claude_config_paths = [
141
- Path.home() / ".claude" / "claude_desktop_config.json", # Claude Code CLI (all platforms)
142
- Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json", # Claude Desktop (macOS)
143
- Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json", # Claude Desktop (Windows)
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
- config_path = None
147
- for p in claude_config_paths:
148
- if p.exists():
149
- config_path = p
150
- break
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
- # Read existing config or start fresh
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
- print(
163
- f"⚠️ Existing config has invalid JSON: {e}"
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
- else:
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 = "tribalmemory-mcp"',
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."""