get-claudia 1.42.1 → 1.42.3
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.
- package/README.md +83 -33
- package/bin/index.js +1 -1
- package/memory-daemon/claudia_memory/daemon/health.py +25 -0
- package/memory-daemon/claudia_memory/mcp/server.py +43 -2
- package/memory-daemon/claudia_memory/services/consolidate.py +47 -0
- package/memory-daemon/scripts/install.ps1 +5 -1
- package/memory-daemon/scripts/install.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h3 align="center">A thinking partner who tracks relationships, not just tasks.</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
|
|
8
|
+
Remembers your people. Catches your commitments. Learns how you work.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -17,6 +17,7 @@ Catches commitments. Remembers context. Connects the dots across your network.
|
|
|
17
17
|
<p align="center">
|
|
18
18
|
<a href="#try-it-in-30-seconds"><strong>Try the Demo</strong></a> ·
|
|
19
19
|
<a href="#what-makes-claudia-different">Why Claudia</a> ·
|
|
20
|
+
<a href="#how-her-mind-works">Her Mind</a> ·
|
|
20
21
|
<a href="#quick-start">Install</a> ·
|
|
21
22
|
<a href="#how-it-works">How It Works</a>
|
|
22
23
|
</p>
|
|
@@ -42,26 +43,53 @@ You make a promise in a meeting. Nobody tracks it. You promise a deliverable on
|
|
|
42
43
|
<p>Say "I'll send that by Friday" and she tracks it. On Friday morning, she reminds you.</p>
|
|
43
44
|
</td>
|
|
44
45
|
<td width="33%" align="center">
|
|
45
|
-
<h3>🔗
|
|
46
|
-
<p>
|
|
46
|
+
<h3>🔗 Knows Your People</h3>
|
|
47
|
+
<p>Every person she meets gets a living profile: relationship health, contact trends, connected entities. Ask about anyone and she has the full picture.</p>
|
|
47
48
|
</td>
|
|
48
49
|
<td width="33%" align="center">
|
|
49
|
-
<h3>⚠️
|
|
50
|
-
<p>
|
|
50
|
+
<h3>⚠️ Spots Patterns You Miss</h3>
|
|
51
|
+
<p>Overcommitting again? A key relationship going cold? The same mistake twice? She sees it forming and speaks up.</p>
|
|
51
52
|
</td>
|
|
52
53
|
</tr>
|
|
53
54
|
<tr>
|
|
54
55
|
<td width="33%" align="center">
|
|
55
|
-
<h3>📄 Shows Her
|
|
56
|
+
<h3>📄 Shows Her Sources</h3>
|
|
56
57
|
<p>Every fact traces to its source. Ask "how do you know that?" and she shows the receipt.</p>
|
|
57
58
|
</td>
|
|
58
59
|
<td width="33%" align="center">
|
|
59
|
-
<h3>🧠
|
|
60
|
-
<p>Memory syncs to an Obsidian vault
|
|
60
|
+
<h3>🧠 Second Brain in Obsidian</h3>
|
|
61
|
+
<p>Memory syncs to an Obsidian vault organized by activity: Active projects, Relationships, Reference, Archive. Graph view maps your world. Plain markdown you own forever.</p>
|
|
61
62
|
</td>
|
|
62
63
|
<td width="33%" align="center">
|
|
63
|
-
<h3
|
|
64
|
-
<p>
|
|
64
|
+
<h3>🌙 Learns in the Background</h3>
|
|
65
|
+
<p>Overnight, old memories fade, near-duplicates merge, and patterns surface. Each morning she knows a little more than yesterday.</p>
|
|
66
|
+
</td>
|
|
67
|
+
</tr>
|
|
68
|
+
</table>
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## How Her Mind Works
|
|
73
|
+
|
|
74
|
+
<table>
|
|
75
|
+
<tr>
|
|
76
|
+
<td width="50%" align="center">
|
|
77
|
+
<h3>💾 Remember</h3>
|
|
78
|
+
<p>Every fact is stored with who said it, when, and how confident she is. Embeddings capture <em>meaning</em>, not just keywords, so "we pushed the launch" and "timeline shifted" connect naturally.</p>
|
|
79
|
+
</td>
|
|
80
|
+
<td width="50%" align="center">
|
|
81
|
+
<h3>🔍 Recall</h3>
|
|
82
|
+
<p>Search blends meaning similarity, importance, recency, and full-text matching. Accessing a memory strengthens it, just like the rehearsal effect in human cognition.</p>
|
|
83
|
+
</td>
|
|
84
|
+
</tr>
|
|
85
|
+
<tr>
|
|
86
|
+
<td width="50%" align="center">
|
|
87
|
+
<h3>🌙 Consolidate</h3>
|
|
88
|
+
<p>Overnight background jobs fade old memories, merge near-duplicates, and surface patterns: cooling relationships, overdue commitments, repeated behaviors. She wakes up sharper.</p>
|
|
89
|
+
</td>
|
|
90
|
+
<td width="50%" align="center">
|
|
91
|
+
<h3>📓 Vault</h3>
|
|
92
|
+
<p>Memory syncs to an Obsidian vault organized with PARA: Active projects, Relationships, Reference material, Archive. Graph view shows how your world connects. Plain markdown you own forever.</p>
|
|
65
93
|
</td>
|
|
66
94
|
</tr>
|
|
67
95
|
</table>
|
|
@@ -153,7 +181,37 @@ Claudia: Sarah Chen is CEO at Meridian Ventures. You met at SaaStr 2025.
|
|
|
153
181
|
Source: Call notes from Jan 28, Email thread Jan 30
|
|
154
182
|
```
|
|
155
183
|
|
|
156
|
-
|
|
184
|
+
**Morning brief pulls everything together:**
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
You: /morning-brief
|
|
188
|
+
|
|
189
|
+
Claudia: ☀️ Morning Brief — Wednesday, Feb 19
|
|
190
|
+
|
|
191
|
+
⚠️ Needs Attention:
|
|
192
|
+
• Send updated pitch deck to Sarah Chen [OVERDUE]
|
|
193
|
+
• Quarterly review with Acme Corp is tomorrow
|
|
194
|
+
|
|
195
|
+
👀 Watch:
|
|
196
|
+
• Haven't spoken to Marcus in 18 days (usually weekly)
|
|
197
|
+
• Two new commitments this week, zero closed
|
|
198
|
+
|
|
199
|
+
📊 Today: 3 meetings, 2 open commitments
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Pattern detection works across weeks, not just moments:**
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
Claudia: I've noticed something across the last few weeks.
|
|
206
|
+
You've taken on 4 new commitments without closing any.
|
|
207
|
+
Last time this happened (early January), two slipped
|
|
208
|
+
past deadline.
|
|
209
|
+
|
|
210
|
+
Want me to flag the lowest-priority ones so you can
|
|
211
|
+
decide what to defer?
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Every conversation builds on the last. She remembers.
|
|
157
215
|
|
|
158
216
|
---
|
|
159
217
|
|
|
@@ -180,8 +238,9 @@ Claudia detects your work style and generates structure that fits:
|
|
|
180
238
|
| `/meeting-prep [person]` | One-page briefing before a call |
|
|
181
239
|
| `/capture-meeting` | Process notes into decisions, commitments, action items |
|
|
182
240
|
| `/what-am-i-missing` | Surface risks, overdue items, cooling relationships |
|
|
183
|
-
| `/
|
|
184
|
-
| `/
|
|
241
|
+
| `/sync-vault` | Sync memory to Obsidian vault |
|
|
242
|
+
| `/brain` | Launch 3D brain visualizer |
|
|
243
|
+
| `/deep-context [topic]` | Full-context deep analysis |
|
|
185
244
|
| `/memory-audit` | See everything Claudia knows, with source chains |
|
|
186
245
|
|
|
187
246
|
<details>
|
|
@@ -196,8 +255,10 @@ Claudia detects your work style and generates structure that fits:
|
|
|
196
255
|
| `/new-person [name]` | Create a relationship file |
|
|
197
256
|
| `/pipeline-review` | Active deals, capacity, pipeline health |
|
|
198
257
|
| `/client-health` | Status across all client relationships |
|
|
199
|
-
| `/
|
|
200
|
-
| `/
|
|
258
|
+
| `/inbox-check` | Review messages from connected channels |
|
|
259
|
+
| `/meditate` | End-of-session reflection and persistent learnings |
|
|
260
|
+
| `/fix-duplicates` | Find and merge duplicate entities |
|
|
261
|
+
| `/memory-health` | Check memory system health |
|
|
201
262
|
| `/diagnose` | Check memory daemon health and troubleshoot |
|
|
202
263
|
|
|
203
264
|
</details>
|
|
@@ -224,12 +285,14 @@ You ──► Claude Code ──► Reads Claudia's templates ──► Becomes
|
|
|
224
285
|
▼
|
|
225
286
|
Memory daemon (local) ◄── MCP tools
|
|
226
287
|
│
|
|
227
|
-
|
|
228
|
-
▼
|
|
229
|
-
|
|
230
|
-
|
|
288
|
+
┌──────┼──────┐
|
|
289
|
+
▼ ▼ ▼
|
|
290
|
+
SQLite Ollama Obsidian vault
|
|
291
|
+
+vectors (PARA structure)
|
|
231
292
|
```
|
|
232
293
|
|
|
294
|
+
**Local extraction, zero API calls.** Paste a meeting transcript or email thread. A local language model (Qwen3, SmolLM3, or Llama 3.2 via Ollama) extracts entities, commitments, and decisions in seconds. Claude reviews the extractions and applies judgment. No data leaves your machine.
|
|
295
|
+
|
|
233
296
|
<details>
|
|
234
297
|
<summary><strong>Technical deep dive</strong></summary>
|
|
235
298
|
|
|
@@ -249,26 +312,13 @@ For full architecture diagrams, see [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
|
249
312
|
|
|
250
313
|
</details>
|
|
251
314
|
|
|
252
|
-
<details>
|
|
253
|
-
<summary><strong>Cognitive tools (local LLM extraction)</strong></summary>
|
|
254
|
-
|
|
255
|
-
Paste a meeting transcript. A local language model extracts structured data (entities, facts, commitments) in seconds. Claude reviews and applies judgment.
|
|
256
|
-
|
|
257
|
-
- Runs locally via [Ollama](https://ollama.com), no API keys
|
|
258
|
-
- Models: Qwen3-4B (recommended), SmolLM3-3B, Llama 3.2-3B
|
|
259
|
-
- Falls back gracefully when no model installed
|
|
260
|
-
|
|
261
|
-
Four extraction modes: **meeting**, **email**, **document**, **general**.
|
|
262
|
-
|
|
263
|
-
</details>
|
|
264
|
-
|
|
265
315
|
---
|
|
266
316
|
|
|
267
317
|
## Privacy and Safety
|
|
268
318
|
|
|
269
319
|
- **Fully local.** Memory, embeddings, cognitive tools run on your machine. No external APIs for storage.
|
|
270
320
|
- **No external actions without approval.** Every email, calendar event, external action requires your explicit "yes."
|
|
271
|
-
- **Your data in two formats.** SQLite database (`~/.claudia/memory/`) for fast semantic search, plus
|
|
321
|
+
- **Your data in two formats.** SQLite database (`~/.claudia/memory/`) for fast semantic search, plus a PARA-organized Obsidian vault for reading and graph navigation. Two independent copies you own forever.
|
|
272
322
|
- **Delete anything, anytime.** Full control over your data. No lock-in, no cloud dependency.
|
|
273
323
|
|
|
274
324
|
---
|
package/bin/index.js
CHANGED
|
@@ -160,6 +160,31 @@ class HealthCheckHandler(BaseHTTPRequestHandler):
|
|
|
160
160
|
else:
|
|
161
161
|
self.send_error(404, "Not Found")
|
|
162
162
|
|
|
163
|
+
def do_POST(self):
|
|
164
|
+
"""Handle POST requests"""
|
|
165
|
+
if self.path == "/backup":
|
|
166
|
+
self._send_backup_response()
|
|
167
|
+
else:
|
|
168
|
+
self.send_error(405, "Method Not Allowed")
|
|
169
|
+
|
|
170
|
+
def _send_backup_response(self):
|
|
171
|
+
"""Trigger a database backup and return the path."""
|
|
172
|
+
try:
|
|
173
|
+
db = get_db()
|
|
174
|
+
path = db.backup()
|
|
175
|
+
self._send_json({"status": "ok", "path": str(path)})
|
|
176
|
+
except Exception as e:
|
|
177
|
+
logger.exception("Error triggering backup")
|
|
178
|
+
self._send_json({"status": "error", "message": str(e)}, code=500)
|
|
179
|
+
|
|
180
|
+
def _send_json(self, data: dict, code: int = 200):
|
|
181
|
+
"""Helper to send a JSON response."""
|
|
182
|
+
body = json.dumps(data).encode()
|
|
183
|
+
self.send_response(code)
|
|
184
|
+
self.send_header("Content-Type", "application/json")
|
|
185
|
+
self.end_headers()
|
|
186
|
+
self.wfile.write(body)
|
|
187
|
+
|
|
163
188
|
def _send_health_response(self):
|
|
164
189
|
"""Send basic health check response"""
|
|
165
190
|
health = {
|
|
@@ -722,6 +722,19 @@ async def list_tools() -> ListToolsResult:
|
|
|
722
722
|
"properties": {},
|
|
723
723
|
},
|
|
724
724
|
),
|
|
725
|
+
Tool(
|
|
726
|
+
name="memory.backup",
|
|
727
|
+
title="Trigger Database Backup",
|
|
728
|
+
description=(
|
|
729
|
+
"Trigger an immediate backup of the memory database. Returns the path "
|
|
730
|
+
"of the newly created backup file. Backups use a timestamp suffix and "
|
|
731
|
+
"older backups are pruned automatically per the retention policy."
|
|
732
|
+
),
|
|
733
|
+
inputSchema={
|
|
734
|
+
"type": "object",
|
|
735
|
+
"properties": {},
|
|
736
|
+
},
|
|
737
|
+
),
|
|
725
738
|
Tool(
|
|
726
739
|
name="memory.project_health",
|
|
727
740
|
title="Project Health Check",
|
|
@@ -2587,8 +2600,14 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
2587
2600
|
)
|
|
2588
2601
|
|
|
2589
2602
|
elif name == "memory.system_health":
|
|
2590
|
-
|
|
2591
|
-
report =
|
|
2603
|
+
import urllib.request, urllib.error
|
|
2604
|
+
report = None
|
|
2605
|
+
try:
|
|
2606
|
+
with urllib.request.urlopen("http://localhost:3848/status", timeout=2) as resp:
|
|
2607
|
+
report = json.loads(resp.read().decode())
|
|
2608
|
+
except (urllib.error.URLError, OSError):
|
|
2609
|
+
from ..daemon.health import build_status_report
|
|
2610
|
+
report = build_status_report()
|
|
2592
2611
|
embedding_svc = get_embedding_service()
|
|
2593
2612
|
if hasattr(embedding_svc, '_model_mismatch') and embedding_svc._model_mismatch:
|
|
2594
2613
|
if "components" not in report:
|
|
@@ -2603,6 +2622,28 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
2603
2622
|
]
|
|
2604
2623
|
)
|
|
2605
2624
|
|
|
2625
|
+
elif name == "memory.backup":
|
|
2626
|
+
import urllib.request, urllib.error
|
|
2627
|
+
result = None
|
|
2628
|
+
try:
|
|
2629
|
+
req = urllib.request.Request(
|
|
2630
|
+
"http://localhost:3848/backup", method="POST", data=b""
|
|
2631
|
+
)
|
|
2632
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
2633
|
+
result = json.loads(resp.read().decode())
|
|
2634
|
+
except (urllib.error.URLError, OSError):
|
|
2635
|
+
# Daemon not running — trigger backup directly
|
|
2636
|
+
backup_path = get_db().backup()
|
|
2637
|
+
result = {"status": "ok", "path": str(backup_path)}
|
|
2638
|
+
return CallToolResult(
|
|
2639
|
+
content=[
|
|
2640
|
+
TextContent(
|
|
2641
|
+
type="text",
|
|
2642
|
+
text=json.dumps(result, indent=2),
|
|
2643
|
+
)
|
|
2644
|
+
]
|
|
2645
|
+
)
|
|
2646
|
+
|
|
2606
2647
|
elif name == "memory.sync_vault":
|
|
2607
2648
|
from ..config import _project_id
|
|
2608
2649
|
from ..services.vault_sync import run_vault_sync
|
|
@@ -2010,6 +2010,49 @@ class ConsolidateService:
|
|
|
2010
2010
|
|
|
2011
2011
|
logger.debug(f"Merged reflection {duplicate['id']} into {primary['id']}")
|
|
2012
2012
|
|
|
2013
|
+
def close_stale_episodes(self) -> int:
|
|
2014
|
+
"""Auto-close orphan episodes that have no end_session call.
|
|
2015
|
+
|
|
2016
|
+
Single-turn sessions and interrupted sessions leave episodes with
|
|
2017
|
+
``ended_at IS NULL``. After 24 hours these will never be closed
|
|
2018
|
+
naturally, so this pass marks them as summarized with a synthetic
|
|
2019
|
+
summary to prevent them from appearing as false positives in health
|
|
2020
|
+
reports.
|
|
2021
|
+
|
|
2022
|
+
Returns the number of episodes closed.
|
|
2023
|
+
"""
|
|
2024
|
+
cutoff = (datetime.utcnow() - timedelta(hours=24)).isoformat()
|
|
2025
|
+
try:
|
|
2026
|
+
# Find stale open episodes and close them. ended_at is set to the
|
|
2027
|
+
# timestamp of the latest buffered turn if one exists, otherwise to
|
|
2028
|
+
# started_at itself (single-turn / empty sessions).
|
|
2029
|
+
self.db.execute(
|
|
2030
|
+
"""
|
|
2031
|
+
UPDATE episodes
|
|
2032
|
+
SET
|
|
2033
|
+
ended_at = COALESCE(
|
|
2034
|
+
(
|
|
2035
|
+
SELECT MAX(created_at) FROM turn_buffer
|
|
2036
|
+
WHERE turn_buffer.session_id = episodes.session_id
|
|
2037
|
+
),
|
|
2038
|
+
started_at
|
|
2039
|
+
),
|
|
2040
|
+
is_summarized = 1,
|
|
2041
|
+
summary = 'Auto-closed: session ended without explicit end_session call'
|
|
2042
|
+
WHERE ended_at IS NULL
|
|
2043
|
+
AND started_at < ?
|
|
2044
|
+
""",
|
|
2045
|
+
(cutoff,),
|
|
2046
|
+
)
|
|
2047
|
+
rows = self.db.execute("SELECT changes()", fetch=True)
|
|
2048
|
+
count = rows[0][0] if rows else 0
|
|
2049
|
+
if count:
|
|
2050
|
+
logger.info(f"Auto-closed {count} stale open episode(s)")
|
|
2051
|
+
return count
|
|
2052
|
+
except Exception as e:
|
|
2053
|
+
logger.warning(f"close_stale_episodes failed: {e}")
|
|
2054
|
+
return 0
|
|
2055
|
+
|
|
2013
2056
|
def run_retention_cleanup(self) -> Dict[str, int]:
|
|
2014
2057
|
"""Clean up old data per retention policies.
|
|
2015
2058
|
|
|
@@ -2018,6 +2061,7 @@ class ConsolidateService:
|
|
|
2018
2061
|
- Expired predictions past retention window
|
|
2019
2062
|
- Archived turn_buffer from old episodes
|
|
2020
2063
|
- Old metrics rows
|
|
2064
|
+
- Auto-closes stale open episodes (no end_session after 24 h)
|
|
2021
2065
|
"""
|
|
2022
2066
|
results = {}
|
|
2023
2067
|
now = datetime.utcnow()
|
|
@@ -2074,6 +2118,9 @@ class ConsolidateService:
|
|
|
2074
2118
|
logger.warning(f"Metrics cleanup failed: {e}")
|
|
2075
2119
|
results["metrics_deleted"] = 0
|
|
2076
2120
|
|
|
2121
|
+
# Auto-close orphan episodes (no end_session after 24 h)
|
|
2122
|
+
results["stale_episodes_closed"] = self.close_stale_episodes()
|
|
2123
|
+
|
|
2077
2124
|
logger.info(f"Retention cleanup: {results}")
|
|
2078
2125
|
return results
|
|
2079
2126
|
|
|
@@ -366,9 +366,13 @@ try {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
# Create the scheduled task action
|
|
369
|
+
$taskArgs = "-m claudia_memory --standalone"
|
|
370
|
+
if ($env:CLAUDIA_PROJECT_PATH) {
|
|
371
|
+
$taskArgs += " --project-dir `"$($env:CLAUDIA_PROJECT_PATH)`""
|
|
372
|
+
}
|
|
369
373
|
$action = New-ScheduledTaskAction `
|
|
370
374
|
-Execute $VENV_PYTHON `
|
|
371
|
-
-Argument
|
|
375
|
+
-Argument $taskArgs `
|
|
372
376
|
-WorkingDirectory $DAEMON_DIR
|
|
373
377
|
|
|
374
378
|
# Trigger: at logon for current user
|
|
@@ -518,7 +518,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
|
518
518
|
<string>$VENV_DIR/bin/python</string>
|
|
519
519
|
<string>-m</string>
|
|
520
520
|
<string>claudia_memory</string>
|
|
521
|
-
<string>--standalone</string>
|
|
521
|
+
<string>--standalone</string>$(if [ -n "$CLAUDIA_PROJECT_PATH" ]; then printf '\n <string>--project-dir</string>\n <string>%s</string>' "$CLAUDIA_PROJECT_PATH"; fi)
|
|
522
522
|
</array>
|
|
523
523
|
<key>WorkingDirectory</key>
|
|
524
524
|
<string>$DAEMON_DIR</string>
|