get-claudia 1.29.2 → 1.31.0
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/bin/index.js +67 -9
- package/memory-daemon/claudia_memory/database.py +65 -0
- package/memory-daemon/claudia_memory/mcp/server.py +93 -0
- package/memory-daemon/claudia_memory/schema.sql +9 -1
- package/memory-daemon/claudia_memory/services/guards.py +32 -1
- package/memory-daemon/claudia_memory/services/recall.py +8 -1
- package/memory-daemon/claudia_memory/services/remember.py +184 -30
- package/memory-daemon/tests/test_batch_parallel.py +119 -0
- package/memory-daemon/tests/test_bitemporal.py +202 -16
- package/memory-daemon/tests/test_database.py +35 -0
- package/memory-daemon/tests/test_guards.py +3 -2
- package/memory-daemon/tests/test_relationship_guards.py +79 -0
- package/memory-daemon/tests/test_source_channel.py +68 -0
- package/package.json +2 -1
- package/relay/SETUP.md +165 -0
- package/relay/package-lock.json +128 -0
- package/relay/package.json +21 -0
- package/relay/scripts/install.ps1 +228 -0
- package/relay/scripts/install.sh +273 -0
- package/relay/src/chunker.js +64 -0
- package/relay/src/claude-runner.js +149 -0
- package/relay/src/config.js +113 -0
- package/relay/src/formatter.js +77 -0
- package/relay/src/index.js +106 -0
- package/relay/src/lock.js +78 -0
- package/relay/src/relay.js +112 -0
- package/relay/src/session.js +131 -0
- package/relay/src/telegram.js +345 -0
- package/relay/tests/chunker.test.js +83 -0
- package/relay/tests/config.test.js +59 -0
- package/relay/tests/formatter.test.js +75 -0
- package/relay/tests/session.test.js +128 -0
- package/relay/tests/telegram.test.js +40 -0
- package/template-v2/.claude/skills/README.md +2 -1
- package/template-v2/.claude/skills/map-connections/SKILL.md +23 -21
- package/template-v2/.claude/skills/setup-telegram.md +151 -0
package/bin/index.js
CHANGED
|
@@ -220,7 +220,7 @@ async function main() {
|
|
|
220
220
|
|
|
221
221
|
// Helper: run visualizer install script and call back when done (auto-install, no prompt)
|
|
222
222
|
function runVisualizerSetup(callback) {
|
|
223
|
-
console.log(`\n${colors.boldYellow}━━━ Phase 2/
|
|
223
|
+
console.log(`\n${colors.boldYellow}━━━ Phase 2/4: Brain Visualizer ━━━${colors.reset}\n`);
|
|
224
224
|
|
|
225
225
|
const visualizerScriptPath = isWindows
|
|
226
226
|
? join(__dirname, '..', 'visualizer', 'scripts', 'install.ps1')
|
|
@@ -263,7 +263,7 @@ async function main() {
|
|
|
263
263
|
|
|
264
264
|
// Helper: run gateway install script and call back when done
|
|
265
265
|
function runGatewaySetup(callback) {
|
|
266
|
-
console.log(`\n${colors.boldYellow}━━━ Phase 3/
|
|
266
|
+
console.log(`\n${colors.boldYellow}━━━ Phase 3/4: Messaging Gateway ━━━${colors.reset}\n`);
|
|
267
267
|
|
|
268
268
|
const gatewayScriptPath = isWindows
|
|
269
269
|
? join(__dirname, '..', 'gateway', 'scripts', 'install.ps1')
|
|
@@ -309,6 +309,54 @@ async function main() {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
// Helper: run relay install script and call back when done
|
|
313
|
+
function runRelaySetup(callback) {
|
|
314
|
+
console.log(`\n${colors.boldYellow}━━━ Phase 4/4: Telegram Relay ━━━${colors.reset}\n`);
|
|
315
|
+
|
|
316
|
+
const relayScriptPath = isWindows
|
|
317
|
+
? join(__dirname, '..', 'relay', 'scripts', 'install.ps1')
|
|
318
|
+
: join(__dirname, '..', 'relay', 'scripts', 'install.sh');
|
|
319
|
+
|
|
320
|
+
if (!existsSync(relayScriptPath)) {
|
|
321
|
+
console.log(`${colors.yellow}!${colors.reset} Relay files not found. Skipping.`);
|
|
322
|
+
callback(false);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const spawnCmd = isWindows ? powershellPath : 'bash';
|
|
328
|
+
const spawnArgs = isWindows
|
|
329
|
+
? ['-ExecutionPolicy', 'Bypass', '-File', relayScriptPath]
|
|
330
|
+
: [relayScriptPath];
|
|
331
|
+
const relayResult = spawn(spawnCmd, spawnArgs, {
|
|
332
|
+
stdio: 'inherit',
|
|
333
|
+
env: {
|
|
334
|
+
...process.env,
|
|
335
|
+
CLAUDIA_RELAY_UPGRADE: isUpgrade ? '1' : '0',
|
|
336
|
+
CLAUDIA_RELAY_SKIP_SETUP: '1'
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
relayResult.on('close', (code) => {
|
|
341
|
+
if (code === 0) {
|
|
342
|
+
console.log(`${colors.green}✓${colors.reset} Relay installed`);
|
|
343
|
+
callback(true);
|
|
344
|
+
} else {
|
|
345
|
+
console.log(`${colors.yellow}!${colors.reset} Relay setup had issues. You can run it later with:`);
|
|
346
|
+
if (isWindows) {
|
|
347
|
+
console.log(` ${colors.cyan}powershell.exe -ExecutionPolicy Bypass -File "${relayScriptPath}"${colors.reset}`);
|
|
348
|
+
} else {
|
|
349
|
+
console.log(` ${colors.cyan}bash ${relayScriptPath}${colors.reset}`);
|
|
350
|
+
}
|
|
351
|
+
callback(false);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.log(`${colors.yellow}!${colors.reset} Could not set up relay: ${error.message}`);
|
|
356
|
+
callback(false);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
312
360
|
// Helper: run system health check after install
|
|
313
361
|
function runSystemHealthCheck(callback) {
|
|
314
362
|
const diagnoseScript = isWindows
|
|
@@ -343,20 +391,25 @@ async function main() {
|
|
|
343
391
|
}
|
|
344
392
|
|
|
345
393
|
// Helper: finish install after optional components
|
|
346
|
-
function finishInstall(memoryInstalled, visualizerInstalled, gatewayInstalled) {
|
|
394
|
+
function finishInstall(memoryInstalled, visualizerInstalled, gatewayInstalled, relayInstalled) {
|
|
347
395
|
if (memoryInstalled) {
|
|
348
396
|
// Run health check when memory system was installed
|
|
349
397
|
runSystemHealthCheck((healthy) => {
|
|
350
|
-
showNextSteps(memoryInstalled, visualizerInstalled, gatewayInstalled, healthy);
|
|
398
|
+
showNextSteps(memoryInstalled, visualizerInstalled, gatewayInstalled, relayInstalled, healthy);
|
|
351
399
|
});
|
|
352
400
|
} else {
|
|
353
|
-
showNextSteps(memoryInstalled, visualizerInstalled, gatewayInstalled, true);
|
|
401
|
+
showNextSteps(memoryInstalled, visualizerInstalled, gatewayInstalled, relayInstalled, true);
|
|
354
402
|
}
|
|
355
403
|
}
|
|
356
404
|
|
|
357
|
-
// Helper: run
|
|
405
|
+
// Helper: run relay setup after gateway, then finish
|
|
406
|
+
function maybeRunRelay(memoryInstalled, visualizerInstalled, gatewayInstalled) {
|
|
407
|
+
runRelaySetup((relayOk) => finishInstall(memoryInstalled, visualizerInstalled, gatewayInstalled, relayOk));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Helper: run gateway setup (auto-install like visualizer), then chain to relay
|
|
358
411
|
function maybeRunGateway(memoryInstalled, visualizerInstalled) {
|
|
359
|
-
runGatewaySetup((gatewayOk) =>
|
|
412
|
+
runGatewaySetup((gatewayOk) => maybeRunRelay(memoryInstalled, visualizerInstalled, gatewayOk));
|
|
360
413
|
}
|
|
361
414
|
|
|
362
415
|
// Helper: auto-install visualizer after memory (if memory was installed), then chain to gateway
|
|
@@ -371,7 +424,7 @@ async function main() {
|
|
|
371
424
|
}
|
|
372
425
|
|
|
373
426
|
// Memory system always installs (no prompt)
|
|
374
|
-
console.log(`\n${colors.boldYellow}━━━ Phase 1/
|
|
427
|
+
console.log(`\n${colors.boldYellow}━━━ Phase 1/4: Memory System ━━━${colors.reset}\n`);
|
|
375
428
|
|
|
376
429
|
const memoryDaemonPath = isWindows
|
|
377
430
|
? join(__dirname, '..', 'memory-daemon', 'scripts', 'install.ps1')
|
|
@@ -453,7 +506,7 @@ async function main() {
|
|
|
453
506
|
// Memory failed to spawn -- continue with visualizer/gateway
|
|
454
507
|
maybeRunVisualizer(false);
|
|
455
508
|
|
|
456
|
-
function showNextSteps(memoryInstalled, visualizerInstalled, gatewayInstalled, systemHealthy = true) {
|
|
509
|
+
function showNextSteps(memoryInstalled, visualizerInstalled, gatewayInstalled, relayInstalled, systemHealthy = true) {
|
|
457
510
|
const cdStep = isCurrentDir ? '' : ` ${colors.cyan}cd ${targetDir}${colors.reset}\n`;
|
|
458
511
|
|
|
459
512
|
// Installation summary
|
|
@@ -465,11 +518,16 @@ async function main() {
|
|
|
465
518
|
console.log(`${memoryInstalled ? check : warn} Memory system ${memoryInstalled ? 'Active' : 'Skipped'}`);
|
|
466
519
|
console.log(`${visualizerInstalled ? check : warn} Brain visualizer ${visualizerInstalled ? 'Active' : 'Skipped'}`);
|
|
467
520
|
console.log(`${gatewayInstalled ? check : warn} Gateway ${gatewayInstalled ? 'Installed' : 'Skipped'}`);
|
|
521
|
+
console.log(`${relayInstalled ? check : warn} Telegram relay ${relayInstalled ? 'Installed' : 'Skipped'}`);
|
|
468
522
|
|
|
469
523
|
if (gatewayInstalled) {
|
|
470
524
|
console.log(`${colors.yellow}->${colors.reset} Configure tokens: ~/.claudia/gateway.json`);
|
|
471
525
|
}
|
|
472
526
|
|
|
527
|
+
if (relayInstalled) {
|
|
528
|
+
console.log(`${colors.yellow}->${colors.reset} Configure relay: run /setup-telegram inside Claude`);
|
|
529
|
+
}
|
|
530
|
+
|
|
473
531
|
if (!systemHealthy) {
|
|
474
532
|
console.log(`\n${colors.yellow}Some issues were detected above.${colors.reset}`);
|
|
475
533
|
console.log(`${colors.dim}You can fix them now, or Claudia will work in fallback mode until they're resolved.${colors.reset}`);
|
|
@@ -138,6 +138,21 @@ class Database:
|
|
|
138
138
|
else:
|
|
139
139
|
conn.commit()
|
|
140
140
|
|
|
141
|
+
@contextmanager
|
|
142
|
+
def transaction(self) -> Generator[sqlite3.Connection, None, None]:
|
|
143
|
+
"""Explicit multi-step transaction. Commits on success, rolls back on error.
|
|
144
|
+
|
|
145
|
+
Use this when multiple operations must succeed or fail atomically.
|
|
146
|
+
Unlike connection(), the caller uses conn.execute() directly.
|
|
147
|
+
"""
|
|
148
|
+
conn = self._get_connection()
|
|
149
|
+
try:
|
|
150
|
+
yield conn
|
|
151
|
+
conn.commit()
|
|
152
|
+
except Exception:
|
|
153
|
+
conn.rollback()
|
|
154
|
+
raise
|
|
155
|
+
|
|
141
156
|
@contextmanager
|
|
142
157
|
def cursor(self) -> Generator[sqlite3.Cursor, None, None]:
|
|
143
158
|
"""Context manager for database cursor"""
|
|
@@ -724,6 +739,46 @@ class Database:
|
|
|
724
739
|
conn.commit()
|
|
725
740
|
logger.info("Applied migration 14: dispatch_tier for native agent teams")
|
|
726
741
|
|
|
742
|
+
if current_version < 15:
|
|
743
|
+
# Migration 15: Add origin_type to relationships for organic trust model
|
|
744
|
+
try:
|
|
745
|
+
conn.execute(
|
|
746
|
+
"ALTER TABLE relationships ADD COLUMN origin_type TEXT DEFAULT 'extracted'"
|
|
747
|
+
)
|
|
748
|
+
except sqlite3.OperationalError as e:
|
|
749
|
+
if "duplicate column" not in str(e).lower():
|
|
750
|
+
logger.warning(f"Migration 15 statement failed: {e}")
|
|
751
|
+
|
|
752
|
+
# Grandfather existing relationships: all get 'extracted' (the safe default)
|
|
753
|
+
try:
|
|
754
|
+
conn.execute(
|
|
755
|
+
"UPDATE relationships SET origin_type = 'extracted' WHERE origin_type IS NULL"
|
|
756
|
+
)
|
|
757
|
+
except sqlite3.OperationalError as e:
|
|
758
|
+
logger.warning(f"Migration 15 grandfather failed: {e}")
|
|
759
|
+
|
|
760
|
+
conn.execute(
|
|
761
|
+
"INSERT OR IGNORE INTO schema_migrations (version, description) VALUES (15, 'Add origin_type to relationships for organic trust model')"
|
|
762
|
+
)
|
|
763
|
+
conn.commit()
|
|
764
|
+
logger.info("Applied migration 15: relationship origin_type for organic trust model")
|
|
765
|
+
|
|
766
|
+
if current_version < 16:
|
|
767
|
+
# Migration 16: Add source_channel to memories for channel-aware memory
|
|
768
|
+
try:
|
|
769
|
+
conn.execute(
|
|
770
|
+
"ALTER TABLE memories ADD COLUMN source_channel TEXT DEFAULT 'claude_code'"
|
|
771
|
+
)
|
|
772
|
+
except sqlite3.OperationalError as e:
|
|
773
|
+
if "duplicate column" not in str(e).lower():
|
|
774
|
+
logger.warning(f"Migration 16 statement failed: {e}")
|
|
775
|
+
|
|
776
|
+
conn.execute(
|
|
777
|
+
"INSERT OR IGNORE INTO schema_migrations (version, description) VALUES (16, 'Add source_channel to memories for channel-aware memory')"
|
|
778
|
+
)
|
|
779
|
+
conn.commit()
|
|
780
|
+
logger.info("Applied migration 16: source_channel on memories")
|
|
781
|
+
|
|
727
782
|
# FTS5 setup: ensure memories_fts exists regardless of migration path.
|
|
728
783
|
# The FTS5 virtual table + triggers contain internal semicolons that the
|
|
729
784
|
# schema.sql line-based parser can't handle, so we always check here.
|
|
@@ -852,6 +907,16 @@ class Database:
|
|
|
852
907
|
logger.warning("Migration 14 incomplete: agent_dispatches missing dispatch_tier column")
|
|
853
908
|
return 13
|
|
854
909
|
|
|
910
|
+
# Migration 15 added origin_type to relationships
|
|
911
|
+
if "origin_type" not in rel_cols:
|
|
912
|
+
logger.warning("Migration 15 incomplete: relationships missing origin_type column")
|
|
913
|
+
return 14
|
|
914
|
+
|
|
915
|
+
# Migration 16 added source_channel to memories
|
|
916
|
+
if "source_channel" not in memory_cols:
|
|
917
|
+
logger.warning("Migration 16 incomplete: memories missing source_channel column")
|
|
918
|
+
return 15
|
|
919
|
+
|
|
855
920
|
return None # All good
|
|
856
921
|
|
|
857
922
|
def _store_workspace_path(self, conn: sqlite3.Connection) -> None:
|
|
@@ -57,6 +57,7 @@ from ..services.remember import (
|
|
|
57
57
|
get_remember_service,
|
|
58
58
|
get_unsummarized_turns,
|
|
59
59
|
invalidate_memory,
|
|
60
|
+
invalidate_relationship,
|
|
60
61
|
merge_entities,
|
|
61
62
|
relate_entities,
|
|
62
63
|
store_reflection,
|
|
@@ -139,6 +140,10 @@ async def list_tools() -> ListToolsResult:
|
|
|
139
140
|
"type": "string",
|
|
140
141
|
"description": "Full raw text of the source (email body, transcript, etc.). Saved to disk, not stored in DB.",
|
|
141
142
|
},
|
|
143
|
+
"source_channel": {
|
|
144
|
+
"type": "string",
|
|
145
|
+
"description": "Origin channel: claude_code, telegram, slack",
|
|
146
|
+
},
|
|
142
147
|
},
|
|
143
148
|
"required": ["content"],
|
|
144
149
|
},
|
|
@@ -240,6 +245,16 @@ async def list_tools() -> ListToolsResult:
|
|
|
240
245
|
"description": "If true, invalidate existing relationship of same type between same entities and create new one",
|
|
241
246
|
"default": False,
|
|
242
247
|
},
|
|
248
|
+
"origin_type": {
|
|
249
|
+
"type": "string",
|
|
250
|
+
"description": "How this was learned: user_stated, extracted, inferred, corrected",
|
|
251
|
+
"default": "extracted",
|
|
252
|
+
},
|
|
253
|
+
"direction": {
|
|
254
|
+
"type": "string",
|
|
255
|
+
"description": "Relationship direction: forward, backward, or bidirectional",
|
|
256
|
+
"default": "bidirectional",
|
|
257
|
+
},
|
|
243
258
|
},
|
|
244
259
|
"required": ["source", "target", "relationship"],
|
|
245
260
|
},
|
|
@@ -627,6 +642,10 @@ async def list_tools() -> ListToolsResult:
|
|
|
627
642
|
"type": "string",
|
|
628
643
|
"description": "Full raw source text, saved to disk (for 'remember' op)",
|
|
629
644
|
},
|
|
645
|
+
"source_channel": {
|
|
646
|
+
"type": "string",
|
|
647
|
+
"description": "Origin channel: claude_code, telegram, slack (for 'remember' op)",
|
|
648
|
+
},
|
|
630
649
|
"target": {
|
|
631
650
|
"type": "string",
|
|
632
651
|
"description": "Target entity (for 'relate' op)",
|
|
@@ -639,6 +658,23 @@ async def list_tools() -> ListToolsResult:
|
|
|
639
658
|
"type": "number",
|
|
640
659
|
"description": "Relationship strength 0.0-1.0 (for 'relate' op)",
|
|
641
660
|
},
|
|
661
|
+
"origin_type": {
|
|
662
|
+
"type": "string",
|
|
663
|
+
"description": "How this was learned: user_stated, extracted, inferred (for 'relate' op)",
|
|
664
|
+
},
|
|
665
|
+
"supersedes": {
|
|
666
|
+
"type": "boolean",
|
|
667
|
+
"description": "Invalidate existing relationship of same type (for 'relate' op)",
|
|
668
|
+
"default": False,
|
|
669
|
+
},
|
|
670
|
+
"valid_at": {
|
|
671
|
+
"type": "string",
|
|
672
|
+
"description": "When this relationship became true (for 'relate' op)",
|
|
673
|
+
},
|
|
674
|
+
"direction": {
|
|
675
|
+
"type": "string",
|
|
676
|
+
"description": "Relationship direction (for 'relate' op)",
|
|
677
|
+
},
|
|
642
678
|
},
|
|
643
679
|
"required": ["op"],
|
|
644
680
|
},
|
|
@@ -1060,6 +1096,37 @@ async def list_tools() -> ListToolsResult:
|
|
|
1060
1096
|
"required": ["memory_id"],
|
|
1061
1097
|
},
|
|
1062
1098
|
),
|
|
1099
|
+
Tool(
|
|
1100
|
+
name="memory.invalidate_relationship",
|
|
1101
|
+
description=(
|
|
1102
|
+
"Mark a relationship as incorrect or ended without creating a replacement. "
|
|
1103
|
+
"Use when the user says a relationship is wrong, or when someone leaves a "
|
|
1104
|
+
"company, ends a partnership, etc. The relationship is preserved for history "
|
|
1105
|
+
"but excluded from active queries."
|
|
1106
|
+
),
|
|
1107
|
+
inputSchema={
|
|
1108
|
+
"type": "object",
|
|
1109
|
+
"properties": {
|
|
1110
|
+
"source": {
|
|
1111
|
+
"type": "string",
|
|
1112
|
+
"description": "Source entity name",
|
|
1113
|
+
},
|
|
1114
|
+
"target": {
|
|
1115
|
+
"type": "string",
|
|
1116
|
+
"description": "Target entity name",
|
|
1117
|
+
},
|
|
1118
|
+
"relationship": {
|
|
1119
|
+
"type": "string",
|
|
1120
|
+
"description": "Relationship type to invalidate (works_with, manages, etc.)",
|
|
1121
|
+
},
|
|
1122
|
+
"reason": {
|
|
1123
|
+
"type": "string",
|
|
1124
|
+
"description": "Why this relationship is being invalidated",
|
|
1125
|
+
},
|
|
1126
|
+
},
|
|
1127
|
+
"required": ["source", "target", "relationship"],
|
|
1128
|
+
},
|
|
1129
|
+
),
|
|
1063
1130
|
Tool(
|
|
1064
1131
|
name="memory.audit_history",
|
|
1065
1132
|
description=(
|
|
@@ -1116,6 +1183,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1116
1183
|
importance=arguments.get("importance", 1.0),
|
|
1117
1184
|
source=arguments.get("source"),
|
|
1118
1185
|
source_context=arguments.get("source_context"),
|
|
1186
|
+
source_channel=arguments.get("source_channel"),
|
|
1119
1187
|
)
|
|
1120
1188
|
# Save source material to disk if provided
|
|
1121
1189
|
if memory_id and arguments.get("source_material"):
|
|
@@ -1161,6 +1229,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1161
1229
|
"source": r.source,
|
|
1162
1230
|
"source_id": r.source_id,
|
|
1163
1231
|
"source_context": r.source_context,
|
|
1232
|
+
"source_channel": r.source_channel,
|
|
1164
1233
|
}
|
|
1165
1234
|
for r in results
|
|
1166
1235
|
]
|
|
@@ -1232,6 +1301,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1232
1301
|
"source": r.source,
|
|
1233
1302
|
"source_id": r.source_id,
|
|
1234
1303
|
"source_context": r.source_context,
|
|
1304
|
+
"source_channel": r.source_channel,
|
|
1235
1305
|
}
|
|
1236
1306
|
for r in results
|
|
1237
1307
|
]
|
|
@@ -1281,6 +1351,8 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1281
1351
|
strength=arguments.get("strength", 1.0),
|
|
1282
1352
|
valid_at=arguments.get("valid_at"),
|
|
1283
1353
|
supersedes=arguments.get("supersedes", False),
|
|
1354
|
+
origin_type=arguments.get("origin_type", "extracted"),
|
|
1355
|
+
direction=arguments.get("direction", "bidirectional"),
|
|
1284
1356
|
)
|
|
1285
1357
|
return CallToolResult(
|
|
1286
1358
|
content=[
|
|
@@ -1583,6 +1655,7 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1583
1655
|
importance=op.get("importance", 1.0),
|
|
1584
1656
|
source=op.get("source"),
|
|
1585
1657
|
source_context=op.get("source_context"),
|
|
1658
|
+
source_channel=op.get("source_channel"),
|
|
1586
1659
|
_precomputed_embedding=embeddings_map.get(i),
|
|
1587
1660
|
)
|
|
1588
1661
|
op_result["success"] = True
|
|
@@ -1604,6 +1677,10 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1604
1677
|
target=op["target"],
|
|
1605
1678
|
relationship=op["relationship"],
|
|
1606
1679
|
strength=op.get("strength", 1.0),
|
|
1680
|
+
supersedes=op.get("supersedes", False),
|
|
1681
|
+
valid_at=op.get("valid_at"),
|
|
1682
|
+
direction=op.get("direction", "bidirectional"),
|
|
1683
|
+
origin_type=op.get("origin_type", "extracted"),
|
|
1607
1684
|
)
|
|
1608
1685
|
op_result["success"] = True
|
|
1609
1686
|
op_result["relationship_id"] = relationship_id
|
|
@@ -1873,6 +1950,22 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
|
1873
1950
|
]
|
|
1874
1951
|
)
|
|
1875
1952
|
|
|
1953
|
+
elif name == "memory.invalidate_relationship":
|
|
1954
|
+
result = invalidate_relationship(
|
|
1955
|
+
source=arguments["source"],
|
|
1956
|
+
target=arguments["target"],
|
|
1957
|
+
relationship=arguments["relationship"],
|
|
1958
|
+
reason=arguments.get("reason"),
|
|
1959
|
+
)
|
|
1960
|
+
return CallToolResult(
|
|
1961
|
+
content=[
|
|
1962
|
+
TextContent(
|
|
1963
|
+
type="text",
|
|
1964
|
+
text=json.dumps(result),
|
|
1965
|
+
)
|
|
1966
|
+
]
|
|
1967
|
+
)
|
|
1968
|
+
|
|
1876
1969
|
elif name == "memory.audit_history":
|
|
1877
1970
|
# Get audit history for entity or memory
|
|
1878
1971
|
entity_id = arguments.get("entity_id")
|
|
@@ -60,7 +60,8 @@ CREATE TABLE IF NOT EXISTS memories (
|
|
|
60
60
|
access_count INTEGER DEFAULT 0,
|
|
61
61
|
verified_at TEXT, -- When this memory was verified
|
|
62
62
|
verification_status TEXT DEFAULT 'pending', -- pending, verified, flagged, contradicts
|
|
63
|
-
metadata TEXT -- JSON blob for flexible attributes
|
|
63
|
+
metadata TEXT, -- JSON blob for flexible attributes
|
|
64
|
+
source_channel TEXT DEFAULT 'claude_code' -- Origin channel: claude_code, telegram, slack
|
|
64
65
|
);
|
|
65
66
|
|
|
66
67
|
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
@@ -89,6 +90,7 @@ CREATE TABLE IF NOT EXISTS relationships (
|
|
|
89
90
|
target_entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
90
91
|
relationship_type TEXT NOT NULL, -- works_with, manages, client_of, etc.
|
|
91
92
|
strength REAL DEFAULT 1.0, -- Relationship strength (decays/grows)
|
|
93
|
+
origin_type TEXT DEFAULT 'extracted', -- user_stated, extracted, inferred, corrected
|
|
92
94
|
direction TEXT DEFAULT 'bidirectional' CHECK (direction IN ('forward', 'backward', 'bidirectional')),
|
|
93
95
|
valid_at TEXT, -- When this relationship became true in the real world
|
|
94
96
|
invalid_at TEXT, -- When this relationship was superseded (NULL = current)
|
|
@@ -408,6 +410,12 @@ VALUES (13, 'Add origin_type to memories, agent_dispatches table for Trust North
|
|
|
408
410
|
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
409
411
|
VALUES (14, 'Add dispatch_tier to agent_dispatches for native agent team support');
|
|
410
412
|
|
|
413
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
414
|
+
VALUES (15, 'Add origin_type to relationships for organic trust model');
|
|
415
|
+
|
|
416
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
417
|
+
VALUES (16, 'Add source_channel to memories for channel-aware memory');
|
|
418
|
+
|
|
411
419
|
-- ============================================================================
|
|
412
420
|
-- AGENT DISPATCHES: Track delegated tasks to sub-agents
|
|
413
421
|
-- ============================================================================
|
|
@@ -112,15 +112,37 @@ def validate_entity(
|
|
|
112
112
|
return result
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
# Origin-aware strength ceilings: relationships can't exceed their evidence level
|
|
116
|
+
ORIGIN_STRENGTH_CEILING = {
|
|
117
|
+
"user_stated": 1.0, # User said it directly -- full trust
|
|
118
|
+
"extracted": 0.8, # Evidence from a document -- high but not absolute
|
|
119
|
+
"inferred": 0.5, # Co-occurrence guess -- must earn trust through repetition
|
|
120
|
+
"corrected": 1.0, # User corrected it -- full trust
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Origin-scaled reinforcement increments (Hebbian: stronger signals potentiate more)
|
|
124
|
+
REINFORCEMENT_BY_ORIGIN = {
|
|
125
|
+
"user_stated": 0.2,
|
|
126
|
+
"extracted": 0.1,
|
|
127
|
+
"inferred": 0.05,
|
|
128
|
+
"corrected": 0.2,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def validate_relationship(
|
|
133
|
+
strength: float = 1.0,
|
|
134
|
+
origin_type: str = "extracted",
|
|
135
|
+
) -> ValidationResult:
|
|
116
136
|
"""
|
|
117
137
|
Validate a relationship before storage.
|
|
118
138
|
|
|
119
139
|
Checks:
|
|
120
140
|
- Strength clamped to [0, 1]
|
|
141
|
+
- Strength capped by origin authority ceiling
|
|
121
142
|
"""
|
|
122
143
|
result = ValidationResult()
|
|
123
144
|
|
|
145
|
+
# Clamp to [0, 1]
|
|
124
146
|
if strength < 0:
|
|
125
147
|
result.warnings.append(f"Relationship strength {strength} clamped to 0.0")
|
|
126
148
|
result.adjustments["strength"] = 0.0
|
|
@@ -128,4 +150,13 @@ def validate_relationship(strength: float = 1.0) -> ValidationResult:
|
|
|
128
150
|
result.warnings.append(f"Relationship strength {strength} clamped to 1.0")
|
|
129
151
|
result.adjustments["strength"] = 1.0
|
|
130
152
|
|
|
153
|
+
# Cap by origin authority
|
|
154
|
+
ceiling = ORIGIN_STRENGTH_CEILING.get(origin_type, 0.5)
|
|
155
|
+
effective = result.adjustments.get("strength", strength)
|
|
156
|
+
if effective > ceiling:
|
|
157
|
+
result.warnings.append(
|
|
158
|
+
f"Strength {effective} exceeds ceiling {ceiling} for origin '{origin_type}', capped"
|
|
159
|
+
)
|
|
160
|
+
result.adjustments["strength"] = ceiling
|
|
161
|
+
|
|
131
162
|
return result
|
|
@@ -41,6 +41,8 @@ class RecallResult:
|
|
|
41
41
|
confidence: float = 1.0 # How confident we are in this memory
|
|
42
42
|
verification_status: str = "pending" # pending, verified, flagged, contradicts
|
|
43
43
|
origin_type: str = "inferred" # user_stated, extracted, inferred, corrected
|
|
44
|
+
# Channel tracking
|
|
45
|
+
source_channel: Optional[str] = None # Origin channel: claude_code, telegram, slack
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
@dataclass
|
|
@@ -339,6 +341,9 @@ class RecallService:
|
|
|
339
341
|
verification_status_val = row["verification_status"] if "verification_status" in row_keys else "pending"
|
|
340
342
|
origin_type_val = row["origin_type"] if "origin_type" in row_keys else "inferred"
|
|
341
343
|
|
|
344
|
+
# Channel tracking (may not exist in older DBs)
|
|
345
|
+
source_channel_val = row["source_channel"] if "source_channel" in row_keys else None
|
|
346
|
+
|
|
342
347
|
return RecallResult(
|
|
343
348
|
id=row["id"],
|
|
344
349
|
content=row["content"],
|
|
@@ -354,6 +359,7 @@ class RecallService:
|
|
|
354
359
|
confidence=confidence_val,
|
|
355
360
|
verification_status=verification_status_val,
|
|
356
361
|
origin_type=origin_type_val,
|
|
362
|
+
source_channel=source_channel_val,
|
|
357
363
|
)
|
|
358
364
|
|
|
359
365
|
def _rrf_score(
|
|
@@ -696,10 +702,12 @@ class RecallService:
|
|
|
696
702
|
|
|
697
703
|
relationships = []
|
|
698
704
|
for row in rel_rows:
|
|
705
|
+
row_keys = row.keys()
|
|
699
706
|
rel_dict = {
|
|
700
707
|
"type": row["relationship_type"],
|
|
701
708
|
"direction": row["direction"],
|
|
702
709
|
"strength": row["strength"],
|
|
710
|
+
"origin_type": row["origin_type"] if "origin_type" in row_keys else "extracted",
|
|
703
711
|
"other_entity": (
|
|
704
712
|
row["target_name"]
|
|
705
713
|
if row["source_entity_id"] == entity["id"]
|
|
@@ -713,7 +721,6 @@ class RecallService:
|
|
|
713
721
|
}
|
|
714
722
|
# Include temporal fields when showing historical data
|
|
715
723
|
if include_historical:
|
|
716
|
-
row_keys = row.keys()
|
|
717
724
|
rel_dict["valid_at"] = row["valid_at"] if "valid_at" in row_keys else None
|
|
718
725
|
rel_dict["invalid_at"] = row["invalid_at"] if "invalid_at" in row_keys else None
|
|
719
726
|
relationships.append(rel_dict)
|