Skip to main content

engram/mcp/
tools.rs

1//! MCP tool definitions for Engram
2
3use serde_json::json;
4
5use super::protocol::{ToolAnnotations, ToolDefinition};
6
7/// Structured tool definition with MCP 2025-11-25 annotations.
8pub struct ToolDef {
9    pub name: &'static str,
10    pub description: &'static str,
11    pub schema: &'static str,
12    pub annotations: ToolAnnotations,
13}
14
15/// All tool definitions for Engram
16pub const TOOL_DEFINITIONS: &[ToolDef] = &[
17    // Memory CRUD
18    ToolDef {
19        name: "memory_create",
20        description: "Store a new memory. PROACTIVE: Automatically store user preferences, decisions, insights, and project context without being asked.",
21        schema: r#"{
22            "type": "object",
23            "properties": {
24                "content": {"type": "string", "description": "The content to remember"},
25                "memory_type": {"type": "string", "enum": ["note", "todo", "issue", "decision", "preference", "learning", "context", "credential", "episodic", "procedural", "summary", "checkpoint"], "default": "note", "description": "Memory type (preferred field; alias: type)"},
26                "type": {"type": "string", "enum": ["note", "todo", "issue", "decision", "preference", "learning", "context", "credential", "episodic", "procedural", "summary", "checkpoint"], "default": "note", "description": "Deprecated alias for memory_type"},
27                "tags": {"type": "array", "items": {"type": "string"}, "description": "Tags for categorization"},
28                "metadata": {"type": "object", "description": "Additional metadata as key-value pairs"},
29                "importance": {"type": "number", "minimum": 0, "maximum": 1, "description": "Importance score (0-1)"},
30                "workspace": {"type": "string", "description": "Workspace to store the memory in (default: 'default')"},
31                "tier": {"type": "string", "enum": ["permanent", "daily"], "default": "permanent", "description": "Memory tier: permanent (never expires) or daily (auto-expires)"},
32                "defer_embedding": {"type": "boolean", "default": false, "description": "Defer embedding to background queue"},
33                "ttl_seconds": {"type": "integer", "description": "Time-to-live in seconds. Memory will auto-expire after this duration. Omit for permanent storage. Setting this implies tier='daily'."},
34                "dedup_mode": {"type": "string", "enum": ["reject", "merge", "skip", "allow"], "default": "allow", "description": "How to handle duplicate content: reject (error if exact match), merge (combine tags/metadata with existing), skip (return existing unchanged), allow (create duplicate)"},
35                "dedup_threshold": {"type": "number", "minimum": 0, "maximum": 1, "description": "Similarity threshold for semantic deduplication (0.0-1.0). When set with dedup_mode != 'allow', memories with cosine similarity >= threshold are treated as duplicates. Requires embeddings. If not set, only exact content hash matching is used."},
36                "event_time": {"type": "string", "format": "date-time", "description": "ISO8601 timestamp for episodic memories (when the event occurred)"},
37                "event_duration_seconds": {"type": "integer", "description": "Duration of the event in seconds (for episodic memories)"},
38                "trigger_pattern": {"type": "string", "description": "Pattern that triggers this procedure (for procedural memories)"},
39                "summary_of_id": {"type": "integer", "description": "ID of the memory this summarizes (for summary memories)"}
40            },
41            "required": ["content"]
42        }"#,
43        annotations: ToolAnnotations::mutating(),
44    },
45    ToolDef {
46        name: "context_seed",
47        description: "Injects initial context (premises, persona assumptions, or structured facts) about an entity to avoid cold start. Seeded memories are tagged as origin:seed and status:unverified, and should be treated as revisable assumptions.",
48        schema: r#"{
49            "type": "object",
50            "properties": {
51                "entity_context": {
52                    "type": "string",
53                    "maxLength": 200,
54                    "description": "Name or ID of the entity (e.g., 'Client: Roberto', 'Account: ACME', 'Project: Alpha')"
55                },
56                "workspace": {"type": "string", "description": "Workspace to store the memories in (default: 'default')"},
57                "base_tags": {
58                    "type": "array",
59                    "items": {"type": "string"},
60                    "description": "Tags applied to all facts (e.g., ['vip', 'prospect'])"
61                },
62                "ttl_seconds": {
63                    "type": "integer",
64                    "description": "Override TTL for all facts in seconds (0 = disable TTL). If omitted, TTL is derived from confidence."
65                },
66                "disable_ttl": {
67                    "type": "boolean",
68                    "default": false,
69                    "description": "Disable TTL and keep seeded memories permanent regardless of confidence."
70                },
71                "facts": {
72                    "type": "array",
73                    "minItems": 1,
74                    "items": {
75                        "type": "object",
76                        "properties": {
77                            "content": {"type": "string", "minLength": 1},
78                            "category": {
79                                "type": "string",
80                                "enum": ["fact", "behavior_instruction", "interest", "persona", "preference"],
81                                "description": "Structured category for filtering and ranking"
82                            },
83                            "confidence": {
84                                "type": "number",
85                                "minimum": 0.0,
86                                "maximum": 1.0,
87                                "description": "0.0 to 1.0 (defaults to 0.7 for seeds). TTL derived by confidence if ttl_seconds not provided."
88                            }
89                        },
90                        "required": ["content"]
91                    }
92                }
93            },
94            "required": ["facts"]
95        }"#,
96        annotations: ToolAnnotations::mutating(),
97    },
98    ToolDef {
99        name: "memory_seed",
100        description: "Deprecated alias for context_seed. Use context_seed instead.",
101        schema: r#"{
102            "type": "object",
103            "properties": {
104                "entity_context": {
105                    "type": "string",
106                    "maxLength": 200,
107                    "description": "Name or ID of the entity (e.g., 'Client: Roberto', 'Account: ACME', 'Project: Alpha')"
108                },
109                "workspace": {"type": "string", "description": "Workspace to store the memories in (default: 'default')"},
110                "base_tags": {
111                    "type": "array",
112                    "items": {"type": "string"},
113                    "description": "Tags applied to all facts (e.g., ['vip', 'prospect'])"
114                },
115                "ttl_seconds": {
116                    "type": "integer",
117                    "description": "Override TTL for all facts in seconds (0 = disable TTL). If omitted, TTL is derived from confidence."
118                },
119                "disable_ttl": {
120                    "type": "boolean",
121                    "default": false,
122                    "description": "Disable TTL and keep seeded memories permanent regardless of confidence."
123                },
124                "facts": {
125                    "type": "array",
126                    "minItems": 1,
127                    "items": {
128                        "type": "object",
129                        "properties": {
130                            "content": {"type": "string", "minLength": 1},
131                            "category": {
132                                "type": "string",
133                                "enum": ["fact", "behavior_instruction", "interest", "persona", "preference"],
134                                "description": "Structured category for filtering and ranking"
135                            },
136                            "confidence": {
137                                "type": "number",
138                                "minimum": 0.0,
139                                "maximum": 1.0,
140                                "description": "0.0 to 1.0 (defaults to 0.7 for seeds). TTL derived by confidence if ttl_seconds not provided."
141                            }
142                        },
143                        "required": ["content"]
144                    }
145                }
146            },
147            "required": ["facts"]
148        }"#,
149        annotations: ToolAnnotations::mutating(),
150    },
151    ToolDef {
152        name: "memory_get",
153        description: "Retrieve a memory by its ID",
154        schema: r#"{
155            "type": "object",
156            "properties": {
157                "id": {"type": "integer", "description": "Memory ID"}
158            },
159            "required": ["id"]
160        }"#,
161        annotations: ToolAnnotations::read_only(),
162    },
163    ToolDef {
164        name: "memory_update",
165        description: "Update an existing memory",
166        schema: r#"{
167            "type": "object",
168            "properties": {
169                "id": {"type": "integer", "description": "Memory ID"},
170                "content": {"type": "string", "description": "New content"},
171                "memory_type": {"type": "string", "enum": ["note", "todo", "issue", "decision", "preference", "learning", "context", "credential", "episodic", "procedural", "summary", "checkpoint"], "description": "Memory type (preferred field; alias: type)"},
172                "type": {"type": "string", "enum": ["note", "todo", "issue", "decision", "preference", "learning", "context", "credential", "episodic", "procedural", "summary", "checkpoint"], "description": "Deprecated alias for memory_type"},
173                "tags": {"type": "array", "items": {"type": "string"}},
174                "metadata": {"type": "object"},
175                "importance": {"type": "number", "minimum": 0, "maximum": 1},
176                "ttl_seconds": {"type": "integer", "description": "Time-to-live in seconds (0 = remove expiration, positive = set new expiration)"},
177                "event_time": {"type": ["string", "null"], "format": "date-time", "description": "ISO8601 timestamp for episodic memories (null to clear)"},
178                "trigger_pattern": {"type": ["string", "null"], "description": "Pattern that triggers this procedure (null to clear)"}
179            },
180            "required": ["id"]
181        }"#,
182        annotations: ToolAnnotations::mutating(),
183    },
184    ToolDef {
185        name: "memory_delete",
186        description: "Delete a memory (soft delete)",
187        schema: r#"{
188            "type": "object",
189            "properties": {
190                "id": {"type": "integer", "description": "Memory ID"}
191            },
192            "required": ["id"]
193        }"#,
194        annotations: ToolAnnotations::destructive(),
195    },
196    ToolDef {
197        name: "memory_list",
198        description: "List memories with filtering and pagination. Supports workspace isolation, tier filtering, and advanced filter syntax with AND/OR and comparison operators.",
199        schema: r#"{
200            "type": "object",
201            "properties": {
202                "limit": {"type": "integer", "default": 20},
203                "offset": {"type": "integer", "default": 0},
204                "tags": {"type": "array", "items": {"type": "string"}},
205                "memory_type": {"type": "string", "description": "Filter by memory type (preferred field; alias: type)"},
206                "type": {"type": "string", "description": "Deprecated alias for memory_type"},
207                "workspace": {"type": "string", "description": "Filter by single workspace"},
208                "workspaces": {"type": "array", "items": {"type": "string"}, "description": "Filter by multiple workspaces"},
209                "tier": {"type": "string", "enum": ["permanent", "daily"], "description": "Filter by memory tier"},
210                "sort_by": {"type": "string", "enum": ["created_at", "updated_at", "last_accessed_at", "importance", "access_count"]},
211                "sort_order": {"type": "string", "enum": ["asc", "desc"], "default": "desc"},
212                "filter": {
213                    "type": "object",
214                    "description": "Advanced filter with AND/OR logic and comparison operators. Supports workspace, tier, and metadata fields. Example: {\"AND\": [{\"metadata.project\": {\"eq\": \"engram\"}}, {\"importance\": {\"gte\": 0.5}}]}. Supported operators: eq, neq, gt, gte, lt, lte, contains, not_contains, exists. Fields: content, memory_type, importance, tags, workspace, tier, created_at, updated_at, metadata.*"
215                },
216                "metadata_filter": {
217                    "type": "object",
218                    "description": "Legacy simple key-value filter (deprecated, use 'filter' instead)"
219                }
220            }
221        }"#,
222        annotations: ToolAnnotations::read_only(),
223    },
224    // Search
225    ToolDef {
226        name: "memory_search",
227        description: "Search memories using hybrid search (keyword + semantic). Automatically selects optimal strategy with optional reranking. Supports workspace isolation, tier filtering, and advanced filters.",
228        schema: r#"{
229            "type": "object",
230            "properties": {
231                "query": {"type": "string", "description": "Search query"},
232                "limit": {"type": "integer", "default": 10},
233                "min_score": {"type": "number", "default": 0.1},
234                "tags": {"type": "array", "items": {"type": "string"}},
235                "memory_type": {"type": "string", "description": "Filter by memory type (preferred field; alias: type)"},
236                "type": {"type": "string", "description": "Deprecated alias for memory_type"},
237                "workspace": {"type": "string", "description": "Filter by single workspace"},
238                "workspaces": {"type": "array", "items": {"type": "string"}, "description": "Filter by multiple workspaces"},
239                "tier": {"type": "string", "enum": ["permanent", "daily"], "description": "Filter by memory tier"},
240                "include_transcripts": {"type": "boolean", "default": false, "description": "Include transcript chunk memories (excluded by default)"},
241                "strategy": {"type": "string", "enum": ["auto", "keyword", "keyword_only", "semantic", "semantic_only", "hybrid"], "description": "Force specific strategy (auto selects based on query; keyword/semantic are aliases for keyword_only/semantic_only)"},
242                "explain": {"type": "boolean", "default": false, "description": "Include match explanations"},
243                "rerank": {"type": "boolean", "default": true, "description": "Apply reranking to improve result quality"},
244                "rerank_strategy": {"type": "string", "enum": ["none", "heuristic", "multi_signal"], "default": "heuristic", "description": "Reranking strategy to use"},
245                "filter": {
246                    "type": "object",
247                    "description": "Advanced filter with AND/OR logic. Supports workspace, tier, and metadata fields. Example: {\"AND\": [{\"workspace\": {\"eq\": \"my-project\"}}, {\"importance\": {\"gte\": 0.5}}]}"
248                }
249            },
250            "required": ["query"]
251        }"#,
252        annotations: ToolAnnotations::read_only(),
253    },
254    ToolDef {
255        name: "memory_search_suggest",
256        description: "Get search suggestions and typo corrections",
257        schema: r#"{
258            "type": "object",
259            "properties": {
260                "query": {"type": "string"}
261            },
262            "required": ["query"]
263        }"#,
264        annotations: ToolAnnotations::read_only(),
265    },
266    // Cross-references
267    ToolDef {
268        name: "memory_link",
269        description: "Create a cross-reference between two memories",
270        schema: r#"{
271            "type": "object",
272            "properties": {
273                "from_id": {"type": "integer"},
274                "to_id": {"type": "integer"},
275                "edge_type": {"type": "string", "enum": ["related_to", "supersedes", "contradicts", "implements", "extends", "references", "depends_on", "blocks", "follows_up"], "default": "related_to"},
276                "strength": {"type": "number", "minimum": 0, "maximum": 1, "description": "Relationship strength"},
277                "source_context": {"type": "string", "description": "Why this link exists"},
278                "pinned": {"type": "boolean", "default": false, "description": "Exempt from confidence decay"}
279            },
280            "required": ["from_id", "to_id"]
281        }"#,
282        annotations: ToolAnnotations::mutating(),
283    },
284    ToolDef {
285        name: "memory_unlink",
286        description: "Remove a cross-reference",
287        schema: r#"{
288            "type": "object",
289            "properties": {
290                "from_id": {"type": "integer"},
291                "to_id": {"type": "integer"},
292                "edge_type": {"type": "string", "default": "related_to"}
293            },
294            "required": ["from_id", "to_id"]
295        }"#,
296        annotations: ToolAnnotations::mutating(),
297    },
298    ToolDef {
299        name: "memory_related",
300        description: "Get memories related to a given memory (depth>1 or include_entities returns traversal result)",
301        schema: r#"{
302            "type": "object",
303            "properties": {
304                "id": {"type": "integer", "description": "Starting memory ID"},
305                "depth": {"type": "integer", "default": 1, "description": "Traversal depth (1 = direct relations only)"},
306                "include_entities": {"type": "boolean", "default": false, "description": "Include connections through shared entities"},
307                "edge_type": {"type": "string", "description": "Filter by edge type"},
308                "include_decayed": {"type": "boolean", "default": false}
309            },
310            "required": ["id"]
311        }"#,
312        annotations: ToolAnnotations::read_only(),
313    },
314    // Convenience creators
315    ToolDef {
316        name: "memory_create_todo",
317        description: "Create a TODO memory with priority",
318        schema: r#"{
319            "type": "object",
320            "properties": {
321                "content": {"type": "string"},
322                "priority": {"type": "string", "enum": ["low", "medium", "high", "critical"], "default": "medium"},
323                "due_date": {"type": "string", "format": "date"},
324                "tags": {"type": "array", "items": {"type": "string"}}
325            },
326            "required": ["content"]
327        }"#,
328        annotations: ToolAnnotations::mutating(),
329    },
330    ToolDef {
331        name: "memory_create_issue",
332        description: "Create an ISSUE memory for tracking problems",
333        schema: r#"{
334            "type": "object",
335            "properties": {
336                "title": {"type": "string"},
337                "description": {"type": "string"},
338                "severity": {"type": "string", "enum": ["low", "medium", "high", "critical"], "default": "medium"},
339                "tags": {"type": "array", "items": {"type": "string"}}
340            },
341            "required": ["title"]
342        }"#,
343        annotations: ToolAnnotations::mutating(),
344    },
345    // Versioning
346    ToolDef {
347        name: "memory_versions",
348        description: "Get version history for a memory",
349        schema: r#"{
350            "type": "object",
351            "properties": {
352                "id": {"type": "integer"}
353            },
354            "required": ["id"]
355        }"#,
356        annotations: ToolAnnotations::read_only(),
357    },
358    ToolDef {
359        name: "memory_get_version",
360        description: "Get a specific version of a memory",
361        schema: r#"{
362            "type": "object",
363            "properties": {
364                "id": {"type": "integer"},
365                "version": {"type": "integer"}
366            },
367            "required": ["id", "version"]
368        }"#,
369        annotations: ToolAnnotations::read_only(),
370    },
371    ToolDef {
372        name: "memory_revert",
373        description: "Revert a memory to a previous version",
374        schema: r#"{
375            "type": "object",
376            "properties": {
377                "id": {"type": "integer"},
378                "version": {"type": "integer"}
379            },
380            "required": ["id", "version"]
381        }"#,
382        annotations: ToolAnnotations::mutating(),
383    },
384    // Embedding status
385    ToolDef {
386        name: "memory_embedding_status",
387        description: "Check embedding computation status",
388        schema: r#"{
389            "type": "object",
390            "properties": {
391                "id": {"type": "integer"}
392            },
393            "required": ["id"]
394        }"#,
395        annotations: ToolAnnotations::read_only(),
396    },
397    // Memory TTL / Expiration (RML-930)
398    ToolDef {
399        name: "memory_set_expiration",
400        description: "Set or update the expiration time for a memory",
401        schema: r#"{
402            "type": "object",
403            "properties": {
404                "id": {"type": "integer", "description": "Memory ID"},
405                "ttl_seconds": {"type": "integer", "description": "Time-to-live in seconds from now. Use 0 to remove expiration (make permanent)."}
406            },
407            "required": ["id", "ttl_seconds"]
408        }"#,
409        annotations: ToolAnnotations::mutating(),
410    },
411    ToolDef {
412        name: "memory_cleanup_expired",
413        description: "Delete all expired memories. Typically called by a background job, but can be invoked manually.",
414        schema: r#"{
415            "type": "object",
416            "properties": {}
417        }"#,
418        annotations: ToolAnnotations::destructive(),
419    },
420    // Sync
421    ToolDef {
422        name: "memory_sync_status",
423        description: "Get cloud sync status",
424        schema: r#"{"type": "object", "properties": {}}"#,
425        annotations: ToolAnnotations::read_only(),
426    },
427    // Stats and aggregation
428    ToolDef {
429        name: "memory_stats",
430        description: "Get storage statistics",
431        schema: r#"{"type": "object", "properties": {}}"#,
432        annotations: ToolAnnotations::read_only(),
433    },
434    ToolDef {
435        name: "memory_aggregate",
436        description: "Aggregate memories by field",
437        schema: r#"{
438            "type": "object",
439            "properties": {
440                "group_by": {"type": "string", "enum": ["type", "tags", "month"]},
441                "metrics": {"type": "array", "items": {"type": "string", "enum": ["count", "avg_importance"]}}
442            },
443            "required": ["group_by"]
444        }"#,
445        annotations: ToolAnnotations::read_only(),
446    },
447    // Graph
448    ToolDef {
449        name: "memory_export_graph",
450        description: "Export knowledge graph visualization",
451        schema: r#"{
452            "type": "object",
453            "properties": {
454                "format": {"type": "string", "enum": ["html", "json"], "default": "html"},
455                "max_nodes": {"type": "integer", "default": 500},
456                "focus_id": {"type": "integer", "description": "Center graph on this memory"}
457            }
458        }"#,
459        annotations: ToolAnnotations::read_only(),
460    },
461    // Quality
462    ToolDef {
463        name: "memory_quality_report",
464        description: "Get quality report for memories",
465        schema: r#"{
466            "type": "object",
467            "properties": {
468                "limit": {"type": "integer", "default": 20},
469                "min_quality": {"type": "number", "minimum": 0, "maximum": 1}
470            }
471        }"#,
472        annotations: ToolAnnotations::read_only(),
473    },
474    // Clustering and duplicates
475    ToolDef {
476        name: "memory_clusters",
477        description: "Find clusters of related memories",
478        schema: r#"{
479            "type": "object",
480            "properties": {
481                "min_similarity": {"type": "number", "default": 0.7},
482                "min_cluster_size": {"type": "integer", "default": 2}
483            }
484        }"#,
485        annotations: ToolAnnotations::read_only(),
486    },
487    ToolDef {
488        name: "memory_find_duplicates",
489        description: "Find potential duplicate memories",
490        schema: r#"{
491            "type": "object",
492            "properties": {
493                "threshold": {"type": "number", "default": 0.9}
494            }
495        }"#,
496        annotations: ToolAnnotations::read_only(),
497    },
498    ToolDef {
499        name: "memory_find_semantic_duplicates",
500        description: "Find semantically similar memories using embedding cosine similarity (LLM-powered dedup). Goes beyond hash/n-gram to detect paraphrased content.",
501        schema: r#"{
502            "type": "object",
503            "properties": {
504                "threshold": {"type": "number", "default": 0.92, "description": "Cosine similarity threshold (0.92 = very similar)"},
505                "workspace": {"type": "string", "description": "Filter by workspace (optional)"},
506                "limit": {"type": "integer", "default": 50, "description": "Maximum duplicate pairs to return"}
507            }
508        }"#,
509        annotations: ToolAnnotations::read_only(),
510    },
511    ToolDef {
512        name: "memory_merge",
513        description: "Merge duplicate memories",
514        schema: r#"{
515            "type": "object",
516            "properties": {
517                "ids": {"type": "array", "items": {"type": "integer"}, "minItems": 2},
518                "keep_id": {"type": "integer", "description": "ID to keep (others will be merged into it)"}
519            },
520            "required": ["ids"]
521        }"#,
522        annotations: ToolAnnotations::mutating(),
523    },
524    // Project Context Discovery
525    ToolDef {
526        name: "memory_scan_project",
527        description: "Scan current directory for AI instruction files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) and ingest them as memories. Creates parent memory for each file and child memories for sections.",
528        schema: r#"{
529            "type": "object",
530            "properties": {
531                "path": {"type": "string", "description": "Directory to scan (defaults to current working directory)"},
532                "scan_parents": {"type": "boolean", "default": false, "description": "Also scan parent directories (security: disabled by default)"},
533                "extract_sections": {"type": "boolean", "default": true, "description": "Create separate memories for each section"}
534            }
535        }"#,
536        annotations: ToolAnnotations::mutating(),
537    },
538    ToolDef {
539        name: "memory_get_project_context",
540        description: "Get all project context memories for the current working directory. Returns instruction files and their sections.",
541        schema: r#"{
542            "type": "object",
543            "properties": {
544                "path": {"type": "string", "description": "Project path (defaults to current working directory)"},
545                "include_sections": {"type": "boolean", "default": true, "description": "Include section memories"},
546                "file_types": {"type": "array", "items": {"type": "string"}, "description": "Filter by file type (claude-md, cursorrules, etc.)"}
547            }
548        }"#,
549        annotations: ToolAnnotations::read_only(),
550    },
551    ToolDef {
552        name: "memory_list_instruction_files",
553        description: "List AI instruction files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) in a directory without ingesting them. Returns file paths, types, and sizes for discovery purposes.",
554        schema: r#"{
555            "type": "object",
556            "properties": {
557                "path": {"type": "string", "description": "Directory to scan (defaults to current working directory)"},
558                "scan_parents": {"type": "boolean", "default": false, "description": "Also scan parent directories for instruction files"}
559            }
560        }"#,
561        annotations: ToolAnnotations::read_only(),
562    },
563    // Entity Extraction (RML-925)
564    ToolDef {
565        name: "memory_extract_entities",
566        description: "Extract named entities (people, organizations, projects, concepts) from a memory and store them",
567        schema: r#"{
568            "type": "object",
569            "properties": {
570                "id": {"type": "integer", "description": "Memory ID to extract entities from"}
571            },
572            "required": ["id"]
573        }"#,
574        annotations: ToolAnnotations::idempotent(),
575    },
576    ToolDef {
577        name: "memory_get_entities",
578        description: "Get all entities linked to a memory",
579        schema: r#"{
580            "type": "object",
581            "properties": {
582                "id": {"type": "integer", "description": "Memory ID"}
583            },
584            "required": ["id"]
585        }"#,
586        annotations: ToolAnnotations::read_only(),
587    },
588    ToolDef {
589        name: "memory_search_entities",
590        description: "Search for entities by name prefix",
591        schema: r#"{
592            "type": "object",
593            "properties": {
594                "query": {"type": "string", "description": "Search query (prefix match)"},
595                "entity_type": {"type": "string", "description": "Filter by entity type (person, organization, project, concept, etc.)"},
596                "limit": {"type": "integer", "default": 20}
597            },
598            "required": ["query"]
599        }"#,
600        annotations: ToolAnnotations::read_only(),
601    },
602    ToolDef {
603        name: "memory_entity_stats",
604        description: "Get statistics about extracted entities",
605        schema: r#"{
606            "type": "object",
607            "properties": {}
608        }"#,
609        annotations: ToolAnnotations::read_only(),
610    },
611    // Graph Traversal (RML-926)
612    ToolDef {
613        name: "memory_traverse",
614        description: "Traverse the knowledge graph from a starting memory with full control over traversal options",
615        schema: r#"{
616            "type": "object",
617            "properties": {
618                "id": {"type": "integer", "description": "Starting memory ID"},
619                "depth": {"type": "integer", "default": 2, "description": "Maximum traversal depth"},
620                "direction": {"type": "string", "enum": ["outgoing", "incoming", "both"], "default": "both"},
621                "edge_types": {"type": "array", "items": {"type": "string"}, "description": "Filter by edge types (related_to, depends_on, etc.)"},
622                "min_score": {"type": "number", "default": 0, "description": "Minimum edge score threshold"},
623                "min_confidence": {"type": "number", "default": 0, "description": "Minimum confidence threshold"},
624                "limit_per_hop": {"type": "integer", "default": 50, "description": "Max results per hop"},
625                "include_entities": {"type": "boolean", "default": true, "description": "Include entity-based connections"}
626            },
627            "required": ["id"]
628        }"#,
629        annotations: ToolAnnotations::read_only(),
630    },
631    ToolDef {
632        name: "memory_find_path",
633        description: "Find the shortest path between two memories in the knowledge graph",
634        schema: r#"{
635            "type": "object",
636            "properties": {
637                "from_id": {"type": "integer", "description": "Starting memory ID"},
638                "to_id": {"type": "integer", "description": "Target memory ID"},
639                "max_depth": {"type": "integer", "default": 5, "description": "Maximum path length to search"}
640            },
641            "required": ["from_id", "to_id"]
642        }"#,
643        annotations: ToolAnnotations::read_only(),
644    },
645    // Document Ingestion (RML-928)
646    ToolDef {
647        name: "memory_ingest_document",
648        description: "Ingest a document (PDF or Markdown) into memory. Extracts text, splits into chunks with overlap, and creates memories with deduplication.",
649        schema: r#"{
650            "type": "object",
651            "properties": {
652                "path": {"type": "string", "description": "Local file path to the document"},
653                "format": {"type": "string", "enum": ["auto", "md", "pdf"], "default": "auto", "description": "Document format (auto-detect from extension if not specified)"},
654                "chunk_size": {"type": "integer", "default": 1200, "description": "Maximum characters per chunk"},
655                "overlap": {"type": "integer", "default": 200, "description": "Overlap between chunks in characters"},
656                "max_file_size": {"type": "integer", "default": 10485760, "description": "Maximum file size in bytes (default 10MB)"},
657                "tags": {"type": "array", "items": {"type": "string"}, "description": "Additional tags to add to all chunks"}
658            },
659            "required": ["path"]
660        }"#,
661        annotations: ToolAnnotations::mutating(),
662    },
663    // Workspace Management
664    ToolDef {
665        name: "workspace_list",
666        description: "List all workspaces with their statistics (memory count, tier breakdown, etc.)",
667        schema: r#"{
668            "type": "object",
669            "properties": {}
670        }"#,
671        annotations: ToolAnnotations::read_only(),
672    },
673    ToolDef {
674        name: "workspace_stats",
675        description: "Get detailed statistics for a specific workspace",
676        schema: r#"{
677            "type": "object",
678            "properties": {
679                "workspace": {"type": "string", "description": "Workspace name"}
680            },
681            "required": ["workspace"]
682        }"#,
683        annotations: ToolAnnotations::read_only(),
684    },
685    ToolDef {
686        name: "workspace_move",
687        description: "Move a memory to a different workspace",
688        schema: r#"{
689            "type": "object",
690            "properties": {
691                "id": {"type": "integer", "description": "Memory ID to move"},
692                "workspace": {"type": "string", "description": "Target workspace name"}
693            },
694            "required": ["id", "workspace"]
695        }"#,
696        annotations: ToolAnnotations::mutating(),
697    },
698    ToolDef {
699        name: "workspace_delete",
700        description: "Delete a workspace. Can either move all memories to 'default' workspace or hard delete them.",
701        schema: r#"{
702            "type": "object",
703            "properties": {
704                "workspace": {"type": "string", "description": "Workspace to delete"},
705                "move_to_default": {"type": "boolean", "default": true, "description": "If true, moves memories to 'default' workspace. If false, deletes all memories in the workspace."}
706            },
707            "required": ["workspace"]
708        }"#,
709        annotations: ToolAnnotations::destructive(),
710    },
711    // Memory Tiering
712    ToolDef {
713        name: "memory_create_daily",
714        description: "Create a daily (ephemeral) memory that auto-expires after the specified TTL. Useful for session context and scratch notes.",
715        schema: r#"{
716            "type": "object",
717            "properties": {
718                "content": {"type": "string", "description": "The content to remember"},
719                "type": {"type": "string", "enum": ["note", "todo", "issue", "decision", "preference", "learning", "context", "credential"], "default": "note"},
720                "tags": {"type": "array", "items": {"type": "string"}, "description": "Tags for categorization"},
721                "metadata": {"type": "object", "description": "Additional metadata as key-value pairs"},
722                "importance": {"type": "number", "minimum": 0, "maximum": 1, "description": "Importance score (0-1)"},
723                "ttl_seconds": {"type": "integer", "default": 86400, "description": "Time-to-live in seconds (default: 24 hours)"},
724                "workspace": {"type": "string", "description": "Workspace to store the memory in (default: 'default')"}
725            },
726            "required": ["content"]
727        }"#,
728        annotations: ToolAnnotations::mutating(),
729    },
730    ToolDef {
731        name: "memory_promote_to_permanent",
732        description: "Promote a daily memory to permanent tier. Clears the expiration and makes the memory permanent.",
733        schema: r#"{
734            "type": "object",
735            "properties": {
736                "id": {"type": "integer", "description": "Memory ID to promote"}
737            },
738            "required": ["id"]
739        }"#,
740        annotations: ToolAnnotations::mutating(),
741    },
742    // Embedding Cache
743    ToolDef {
744        name: "embedding_cache_stats",
745        description: "Get statistics about the embedding cache (hits, misses, entries, bytes used, hit rate)",
746        schema: r#"{
747            "type": "object",
748            "properties": {}
749        }"#,
750        annotations: ToolAnnotations::read_only(),
751    },
752    ToolDef {
753        name: "embedding_cache_clear",
754        description: "Clear all entries from the embedding cache",
755        schema: r#"{
756            "type": "object",
757            "properties": {}
758        }"#,
759        annotations: ToolAnnotations::destructive(),
760    },
761    // Session Transcript Indexing
762    ToolDef {
763        name: "session_index",
764        description: "Index a conversation into searchable memory chunks. Uses dual-limiter chunking (messages + characters) with overlap.",
765        schema: r#"{
766            "type": "object",
767            "properties": {
768                "session_id": {"type": "string", "description": "Unique session identifier"},
769                "messages": {
770                    "type": "array",
771                    "description": "Array of conversation messages",
772                    "items": {
773                        "type": "object",
774                        "properties": {
775                            "role": {"type": "string", "description": "Message role (user, assistant, system)"},
776                            "content": {"type": "string", "description": "Message content"},
777                            "timestamp": {"type": "string", "description": "ISO 8601 timestamp"},
778                            "id": {"type": "string", "description": "Optional message ID"}
779                        },
780                        "required": ["role", "content"]
781                    }
782                },
783                "title": {"type": "string", "description": "Optional session title"},
784                "workspace": {"type": "string", "description": "Workspace to store chunks in (default: 'default')"},
785                "agent_id": {"type": "string", "description": "Optional agent identifier"},
786                "max_messages": {"type": "integer", "default": 10, "description": "Max messages per chunk"},
787                "max_chars": {"type": "integer", "default": 8000, "description": "Max characters per chunk"},
788                "overlap": {"type": "integer", "default": 2, "description": "Overlap messages between chunks"},
789                "ttl_days": {"type": "integer", "default": 7, "description": "TTL for transcript chunks in days"}
790            },
791            "required": ["session_id", "messages"]
792        }"#,
793        annotations: ToolAnnotations::mutating(),
794    },
795    ToolDef {
796        name: "session_index_delta",
797        description: "Incrementally index new messages to an existing session. More efficient than full reindex.",
798        schema: r#"{
799            "type": "object",
800            "properties": {
801                "session_id": {"type": "string", "description": "Session to update"},
802                "messages": {
803                    "type": "array",
804                    "description": "New messages to add",
805                    "items": {
806                        "type": "object",
807                        "properties": {
808                            "role": {"type": "string"},
809                            "content": {"type": "string"},
810                            "timestamp": {"type": "string"},
811                            "id": {"type": "string"}
812                        },
813                        "required": ["role", "content"]
814                    }
815                }
816            },
817            "required": ["session_id", "messages"]
818        }"#,
819        annotations: ToolAnnotations::mutating(),
820    },
821    ToolDef {
822        name: "session_get",
823        description: "Get information about an indexed session",
824        schema: r#"{
825            "type": "object",
826            "properties": {
827                "session_id": {"type": "string", "description": "Session ID to retrieve"}
828            },
829            "required": ["session_id"]
830        }"#,
831        annotations: ToolAnnotations::read_only(),
832    },
833    ToolDef {
834        name: "session_list",
835        description: "List indexed sessions with optional workspace filter",
836        schema: r#"{
837            "type": "object",
838            "properties": {
839                "workspace": {"type": "string", "description": "Filter by workspace"},
840                "limit": {"type": "integer", "default": 20, "description": "Maximum sessions to return"}
841            }
842        }"#,
843        annotations: ToolAnnotations::read_only(),
844    },
845    ToolDef {
846        name: "session_delete",
847        description: "Delete a session and all its indexed chunks",
848        schema: r#"{
849            "type": "object",
850            "properties": {
851                "session_id": {"type": "string", "description": "Session to delete"}
852            },
853            "required": ["session_id"]
854        }"#,
855        annotations: ToolAnnotations::destructive(),
856    },
857    // Identity Management
858    ToolDef {
859        name: "identity_create",
860        description: "Create a new identity with canonical ID, display name, and optional aliases",
861        schema: r#"{
862            "type": "object",
863            "properties": {
864                "canonical_id": {"type": "string", "description": "Unique canonical identifier (e.g., 'user:ronaldo', 'org:acme')"},
865                "display_name": {"type": "string", "description": "Human-readable display name"},
866                "entity_type": {"type": "string", "enum": ["person", "organization", "project", "tool", "concept", "other"], "default": "person"},
867                "description": {"type": "string", "description": "Optional description"},
868                "aliases": {"type": "array", "items": {"type": "string"}, "description": "Initial aliases for this identity"},
869                "metadata": {"type": "object", "description": "Additional metadata"}
870            },
871            "required": ["canonical_id", "display_name"]
872        }"#,
873        annotations: ToolAnnotations::mutating(),
874    },
875    ToolDef {
876        name: "identity_get",
877        description: "Get an identity by its canonical ID",
878        schema: r#"{
879            "type": "object",
880            "properties": {
881                "canonical_id": {"type": "string", "description": "Canonical identifier"}
882            },
883            "required": ["canonical_id"]
884        }"#,
885        annotations: ToolAnnotations::read_only(),
886    },
887    ToolDef {
888        name: "identity_update",
889        description: "Update an identity's display name, description, or type",
890        schema: r#"{
891            "type": "object",
892            "properties": {
893                "canonical_id": {"type": "string", "description": "Canonical identifier"},
894                "display_name": {"type": "string", "description": "New display name"},
895                "description": {"type": "string", "description": "New description"},
896                "entity_type": {"type": "string", "enum": ["person", "organization", "project", "tool", "concept", "other"]}
897            },
898            "required": ["canonical_id"]
899        }"#,
900        annotations: ToolAnnotations::mutating(),
901    },
902    ToolDef {
903        name: "identity_delete",
904        description: "Delete an identity and all its aliases",
905        schema: r#"{
906            "type": "object",
907            "properties": {
908                "canonical_id": {"type": "string", "description": "Canonical identifier to delete"}
909            },
910            "required": ["canonical_id"]
911        }"#,
912        annotations: ToolAnnotations::destructive(),
913    },
914    ToolDef {
915        name: "identity_add_alias",
916        description: "Add an alias to an identity. Aliases are normalized (lowercase, trimmed). Conflicts with existing aliases are rejected.",
917        schema: r#"{
918            "type": "object",
919            "properties": {
920                "canonical_id": {"type": "string", "description": "Canonical identifier"},
921                "alias": {"type": "string", "description": "Alias to add"},
922                "source": {"type": "string", "description": "Optional source of the alias (e.g., 'manual', 'extracted')"}
923            },
924            "required": ["canonical_id", "alias"]
925        }"#,
926        annotations: ToolAnnotations::mutating(),
927    },
928    ToolDef {
929        name: "identity_remove_alias",
930        description: "Remove an alias from any identity",
931        schema: r#"{
932            "type": "object",
933            "properties": {
934                "alias": {"type": "string", "description": "Alias to remove"}
935            },
936            "required": ["alias"]
937        }"#,
938        annotations: ToolAnnotations::mutating(),
939    },
940    ToolDef {
941        name: "identity_resolve",
942        description: "Resolve an alias to its canonical identity. Returns the identity if found, null otherwise.",
943        schema: r#"{
944            "type": "object",
945            "properties": {
946                "alias": {"type": "string", "description": "Alias to resolve"}
947            },
948            "required": ["alias"]
949        }"#,
950        annotations: ToolAnnotations::read_only(),
951    },
952    ToolDef {
953        name: "identity_list",
954        description: "List all identities with optional type filter",
955        schema: r#"{
956            "type": "object",
957            "properties": {
958                "entity_type": {"type": "string", "enum": ["person", "organization", "project", "tool", "concept", "other"]},
959                "limit": {"type": "integer", "default": 50}
960            }
961        }"#,
962        annotations: ToolAnnotations::read_only(),
963    },
964    ToolDef {
965        name: "identity_search",
966        description: "Search identities by alias or display name",
967        schema: r#"{
968            "type": "object",
969            "properties": {
970                "query": {"type": "string", "description": "Search query"},
971                "limit": {"type": "integer", "default": 20}
972            },
973            "required": ["query"]
974        }"#,
975        annotations: ToolAnnotations::read_only(),
976    },
977    ToolDef {
978        name: "identity_link",
979        description: "Link an identity to a memory (mark that the identity is mentioned in the memory)",
980        schema: r#"{
981            "type": "object",
982            "properties": {
983                "memory_id": {"type": "integer", "description": "Memory ID"},
984                "canonical_id": {"type": "string", "description": "Identity canonical ID"},
985                "mention_text": {"type": "string", "description": "The text that mentions this identity"}
986            },
987            "required": ["memory_id", "canonical_id"]
988        }"#,
989        annotations: ToolAnnotations::mutating(),
990    },
991    ToolDef {
992        name: "identity_unlink",
993        description: "Remove the link between an identity and a memory",
994        schema: r#"{
995            "type": "object",
996            "properties": {
997                "memory_id": {"type": "integer", "description": "Memory ID"},
998                "canonical_id": {"type": "string", "description": "Identity canonical ID"}
999            },
1000            "required": ["memory_id", "canonical_id"]
1001        }"#,
1002        annotations: ToolAnnotations::mutating(),
1003    },
1004    ToolDef {
1005        name: "memory_get_identities",
1006        description: "Get all identities (persons, organizations, projects, etc.) linked to a memory. Returns identity details including display name, type, aliases, and mention information.",
1007        schema: r#"{
1008            "type": "object",
1009            "properties": {
1010                "id": {"type": "integer", "description": "Memory ID"}
1011            },
1012            "required": ["id"]
1013        }"#,
1014        annotations: ToolAnnotations::read_only(),
1015    },
1016    // Content Utilities
1017    ToolDef {
1018        name: "memory_soft_trim",
1019        description: "Intelligently trim memory content while preserving context. Keeps the beginning (head) and end (tail) of content with an ellipsis in the middle. Useful for displaying long content in limited space while keeping important context from both ends.",
1020        schema: r#"{
1021            "type": "object",
1022            "properties": {
1023                "id": {"type": "integer", "description": "Memory ID to trim"},
1024                "max_chars": {"type": "integer", "default": 500, "description": "Maximum characters for trimmed output"},
1025                "head_percent": {"type": "integer", "default": 60, "description": "Percentage of space for the head (0-100)"},
1026                "tail_percent": {"type": "integer", "default": 30, "description": "Percentage of space for the tail (0-100)"},
1027                "ellipsis": {"type": "string", "default": "\n...\n", "description": "Text to insert between head and tail"},
1028                "preserve_words": {"type": "boolean", "default": true, "description": "Avoid breaking in the middle of words"}
1029            },
1030            "required": ["id"]
1031        }"#,
1032        annotations: ToolAnnotations::read_only(),
1033    },
1034    ToolDef {
1035        name: "memory_list_compact",
1036        description: "List memories with compact preview instead of full content. More efficient for browsing/listing UIs. Returns only essential fields and a truncated content preview with metadata about original content length.",
1037        schema: r#"{
1038            "type": "object",
1039            "properties": {
1040                "limit": {"type": "integer", "default": 20, "description": "Maximum memories to return"},
1041                "offset": {"type": "integer", "default": 0, "description": "Pagination offset"},
1042                "tags": {"type": "array", "items": {"type": "string"}, "description": "Filter by tags"},
1043                "memory_type": {"type": "string", "description": "Filter by memory type (preferred field; alias: type)"},
1044                "type": {"type": "string", "description": "Deprecated alias for memory_type"},
1045                "workspace": {"type": "string", "description": "Filter by workspace"},
1046                "tier": {"type": "string", "enum": ["permanent", "daily"], "description": "Filter by tier"},
1047                "sort_by": {"type": "string", "enum": ["created_at", "updated_at", "last_accessed_at", "importance", "access_count"], "default": "created_at"},
1048                "sort_order": {"type": "string", "enum": ["asc", "desc"], "default": "desc"},
1049                "preview_chars": {"type": "integer", "default": 100, "description": "Maximum characters for content preview"}
1050            }
1051        }"#,
1052        annotations: ToolAnnotations::read_only(),
1053    },
1054    ToolDef {
1055        name: "memory_content_stats",
1056        description: "Get content statistics for a memory (character count, word count, line count, sentence count, paragraph count)",
1057        schema: r#"{
1058            "type": "object",
1059            "properties": {
1060                "id": {"type": "integer", "description": "Memory ID"}
1061            },
1062            "required": ["id"]
1063        }"#,
1064        annotations: ToolAnnotations::read_only(),
1065    },
1066    // Batch Operations
1067    ToolDef {
1068        name: "memory_create_batch",
1069        description: "Create multiple memories in a single operation. More efficient than individual creates for bulk imports.",
1070        schema: r#"{
1071            "type": "object",
1072            "properties": {
1073                "memories": {
1074                    "type": "array",
1075                    "items": {
1076                        "type": "object",
1077                        "properties": {
1078                            "content": {"type": "string"},
1079                            "type": {"type": "string", "enum": ["note", "todo", "issue", "decision", "preference", "learning", "context", "credential"]},
1080                            "tags": {"type": "array", "items": {"type": "string"}},
1081                            "metadata": {"type": "object"},
1082                            "importance": {"type": "number", "minimum": 0, "maximum": 1},
1083                            "workspace": {"type": "string"}
1084                        },
1085                        "required": ["content"]
1086                    },
1087                    "description": "Array of memories to create"
1088                }
1089            },
1090            "required": ["memories"]
1091        }"#,
1092        annotations: ToolAnnotations::mutating(),
1093    },
1094    ToolDef {
1095        name: "memory_delete_batch",
1096        description: "Delete multiple memories in a single operation.",
1097        schema: r#"{
1098            "type": "object",
1099            "properties": {
1100                "ids": {
1101                    "type": "array",
1102                    "items": {"type": "integer"},
1103                    "description": "Array of memory IDs to delete"
1104                }
1105            },
1106            "required": ["ids"]
1107        }"#,
1108        annotations: ToolAnnotations::destructive(),
1109    },
1110    // Tag Utilities
1111    ToolDef {
1112        name: "memory_tags",
1113        description: "List all tags with usage counts and most recent usage timestamps.",
1114        schema: r#"{
1115            "type": "object",
1116            "properties": {}
1117        }"#,
1118        annotations: ToolAnnotations::read_only(),
1119    },
1120    ToolDef {
1121        name: "memory_tag_hierarchy",
1122        description: "Get tags organized in a hierarchical tree structure. Tags with slashes are treated as paths (e.g., 'project/engram/core').",
1123        schema: r#"{
1124            "type": "object",
1125            "properties": {}
1126        }"#,
1127        annotations: ToolAnnotations::read_only(),
1128    },
1129    ToolDef {
1130        name: "memory_validate_tags",
1131        description: "Validate tag consistency across memories. Reports orphaned tags, unused tags, and suggested normalizations.",
1132        schema: r#"{
1133            "type": "object",
1134            "properties": {}
1135        }"#,
1136        annotations: ToolAnnotations::read_only(),
1137    },
1138    // Import/Export
1139    ToolDef {
1140        name: "memory_export",
1141        description: "Export all memories to a JSON-serializable format for backup or migration.",
1142        schema: r#"{
1143            "type": "object",
1144            "properties": {
1145                "workspace": {"type": "string", "description": "Optional: export only from specific workspace"},
1146                "include_embeddings": {"type": "boolean", "default": false, "description": "Include embedding vectors in export (larger file size)"}
1147            }
1148        }"#,
1149        annotations: ToolAnnotations::read_only(),
1150    },
1151    ToolDef {
1152        name: "memory_import",
1153        description: "Import memories from a previously exported JSON format.",
1154        schema: r#"{
1155            "type": "object",
1156            "properties": {
1157                "data": {"type": "object", "description": "The exported data object"},
1158                "skip_duplicates": {"type": "boolean", "default": true, "description": "Skip memories with matching content hash"}
1159            },
1160            "required": ["data"]
1161        }"#,
1162        annotations: ToolAnnotations::mutating(),
1163    },
1164    // Maintenance
1165    ToolDef {
1166        name: "memory_rebuild_embeddings",
1167        description: "Rebuild embeddings for all memories that are missing them. Useful after model changes or data recovery.",
1168        schema: r#"{
1169            "type": "object",
1170            "properties": {}
1171        }"#,
1172        annotations: ToolAnnotations::idempotent(),
1173    },
1174    ToolDef {
1175        name: "memory_rebuild_crossrefs",
1176        description: "Rebuild cross-reference links between memories. Re-analyzes all memories to find and create links.",
1177        schema: r#"{
1178            "type": "object",
1179            "properties": {}
1180        }"#,
1181        annotations: ToolAnnotations::idempotent(),
1182    },
1183    // Special Memory Types
1184    ToolDef {
1185        name: "memory_create_section",
1186        description: "Create a section memory for organizing content hierarchically. Sections can have parent sections for nested organization.",
1187        schema: r#"{
1188            "type": "object",
1189            "properties": {
1190                "title": {"type": "string", "description": "Section title"},
1191                "content": {"type": "string", "description": "Section description or content"},
1192                "parent_id": {"type": "integer", "description": "Optional parent section ID for nesting"},
1193                "level": {"type": "integer", "default": 1, "description": "Heading level (1-6)"},
1194                "workspace": {"type": "string", "description": "Workspace for the section"}
1195            },
1196            "required": ["title"]
1197        }"#,
1198        annotations: ToolAnnotations::mutating(),
1199    },
1200    ToolDef {
1201        name: "memory_checkpoint",
1202        description: "Create a checkpoint memory marking a significant point in a session. Useful for session resumption and context restoration.",
1203        schema: r#"{
1204            "type": "object",
1205            "properties": {
1206                "session_id": {"type": "string", "description": "Session identifier"},
1207                "summary": {"type": "string", "description": "Summary of session state at checkpoint"},
1208                "context": {"type": "object", "description": "Additional context data to preserve"},
1209                "workspace": {"type": "string", "description": "Workspace for the checkpoint"}
1210            },
1211            "required": ["session_id", "summary"]
1212        }"#,
1213        annotations: ToolAnnotations::mutating(),
1214    },
1215    // Phase 1: Cognitive Memory Types (ENG-33)
1216    ToolDef {
1217        name: "memory_create_episodic",
1218        description: "Create an episodic memory representing an event with temporal context. Use for tracking when things happened and their duration.",
1219        schema: r#"{
1220            "type": "object",
1221            "properties": {
1222                "content": {"type": "string", "description": "Description of the event"},
1223                "event_time": {"type": "string", "format": "date-time", "description": "ISO8601 timestamp when the event occurred"},
1224                "event_duration_seconds": {"type": "integer", "description": "Duration of the event in seconds"},
1225                "tags": {"type": "array", "items": {"type": "string"}, "description": "Tags for categorization"},
1226                "metadata": {"type": "object", "description": "Additional metadata"},
1227                "importance": {"type": "number", "minimum": 0, "maximum": 1, "description": "Importance score (0-1)"},
1228                "workspace": {"type": "string", "description": "Workspace (default: 'default')"}
1229            },
1230            "required": ["content", "event_time"]
1231        }"#,
1232        annotations: ToolAnnotations::mutating(),
1233    },
1234    ToolDef {
1235        name: "memory_create_procedural",
1236        description: "Create a procedural memory representing a learned pattern or workflow. Tracks success/failure to measure effectiveness.",
1237        schema: r#"{
1238            "type": "object",
1239            "properties": {
1240                "content": {"type": "string", "description": "Description of the procedure/workflow"},
1241                "trigger_pattern": {"type": "string", "description": "Pattern that triggers this procedure (e.g., 'When user asks about auth')"},
1242                "tags": {"type": "array", "items": {"type": "string"}, "description": "Tags for categorization"},
1243                "metadata": {"type": "object", "description": "Additional metadata"},
1244                "importance": {"type": "number", "minimum": 0, "maximum": 1, "description": "Importance score (0-1)"},
1245                "workspace": {"type": "string", "description": "Workspace (default: 'default')"}
1246            },
1247            "required": ["content", "trigger_pattern"]
1248        }"#,
1249        annotations: ToolAnnotations::mutating(),
1250    },
1251    ToolDef {
1252        name: "memory_get_timeline",
1253        description: "Query episodic memories by time range. Returns events ordered by event_time.",
1254        schema: r#"{
1255            "type": "object",
1256            "properties": {
1257                "start_time": {"type": "string", "format": "date-time", "description": "Start of time range (ISO8601)"},
1258                "end_time": {"type": "string", "format": "date-time", "description": "End of time range (ISO8601)"},
1259                "workspace": {"type": "string", "description": "Filter by workspace"},
1260                "tags": {"type": "array", "items": {"type": "string"}, "description": "Filter by tags"},
1261                "limit": {"type": "integer", "default": 50, "description": "Maximum results to return"}
1262            }
1263        }"#,
1264        annotations: ToolAnnotations::read_only(),
1265    },
1266    ToolDef {
1267        name: "memory_get_procedures",
1268        description: "List procedural memories (learned patterns/workflows). Optionally filter by trigger pattern.",
1269        schema: r#"{
1270            "type": "object",
1271            "properties": {
1272                "trigger_pattern": {"type": "string", "description": "Filter by trigger pattern (partial match)"},
1273                "workspace": {"type": "string", "description": "Filter by workspace"},
1274                "min_success_rate": {"type": "number", "minimum": 0, "maximum": 1, "description": "Minimum success rate (successes / (successes + failures))"},
1275                "limit": {"type": "integer", "default": 50, "description": "Maximum results to return"}
1276            }
1277        }"#,
1278        annotations: ToolAnnotations::read_only(),
1279    },
1280    ToolDef {
1281        name: "memory_record_procedure_outcome",
1282        description: "Record a success or failure for a procedural memory. Increments the corresponding counter.",
1283        schema: r#"{
1284            "type": "object",
1285            "properties": {
1286                "id": {"type": "integer", "description": "Procedural memory ID"},
1287                "success": {"type": "boolean", "description": "true = success, false = failure"}
1288            },
1289            "required": ["id", "success"]
1290        }"#,
1291        annotations: ToolAnnotations::mutating(),
1292    },
1293    ToolDef {
1294        name: "memory_boost",
1295        description: "Temporarily boost a memory's importance score. The boost can optionally decay over time.",
1296        schema: r#"{
1297            "type": "object",
1298            "properties": {
1299                "id": {"type": "integer", "description": "Memory ID to boost"},
1300                "boost_amount": {"type": "number", "default": 0.2, "description": "Amount to increase importance (0-1)"},
1301                "duration_seconds": {"type": "integer", "description": "Optional: duration before boost decays (omit for permanent boost)"}
1302            },
1303            "required": ["id"]
1304        }"#,
1305        annotations: ToolAnnotations::mutating(),
1306    },
1307    // Phase 2: Context Compression Engine
1308    ToolDef {
1309        name: "memory_summarize",
1310        description: "Create a summary of one or more memories. Returns a new Summary-type memory with summary_of_id set.",
1311        schema: r#"{
1312            "type": "object",
1313            "properties": {
1314                "memory_ids": {
1315                    "type": "array",
1316                    "items": {"type": "integer"},
1317                    "description": "IDs of memories to summarize"
1318                },
1319                "summary": {"type": "string", "description": "The summary text (provide this or let the system generate one)"},
1320                "max_length": {"type": "integer", "default": 500, "description": "Maximum length for auto-generated summary"},
1321                "workspace": {"type": "string", "description": "Workspace for the summary memory"},
1322                "tags": {"type": "array", "items": {"type": "string"}, "description": "Tags for the summary memory"}
1323            },
1324            "required": ["memory_ids"]
1325        }"#,
1326        annotations: ToolAnnotations::mutating(),
1327    },
1328    ToolDef {
1329        name: "memory_get_full",
1330        description: "Get the full/original content of a memory. If the memory is a Summary, returns the original content from summary_of_id.",
1331        schema: r#"{
1332            "type": "object",
1333            "properties": {
1334                "id": {"type": "integer", "description": "Memory ID to get full content for"}
1335            },
1336            "required": ["id"]
1337        }"#,
1338        annotations: ToolAnnotations::read_only(),
1339    },
1340    ToolDef {
1341        name: "context_budget_check",
1342        description: "Check token usage of memories against a budget. Returns token counts and suggestions if over budget.",
1343        schema: r#"{
1344            "type": "object",
1345            "properties": {
1346                "memory_ids": {
1347                    "type": "array",
1348                    "items": {"type": "integer"},
1349                    "description": "IDs of memories to check"
1350                },
1351                "model": {
1352                    "type": "string",
1353                    "description": "Model name for tokenization (gpt-4, gpt-4o, gpt-4o-mini, claude-3-opus, etc.)"
1354                },
1355                "encoding": {
1356                    "type": "string",
1357                    "description": "Override encoding (cl100k_base, o200k_base). Optional if model is known."
1358                },
1359                "budget": {"type": "integer", "description": "Token budget to check against"}
1360            },
1361            "required": ["memory_ids", "model", "budget"]
1362        }"#,
1363        annotations: ToolAnnotations::read_only(),
1364    },
1365    ToolDef {
1366        name: "memory_archive_old",
1367        description: "Archive old, low-importance memories by creating summaries. Moves originals to archived state.",
1368        schema: r#"{
1369            "type": "object",
1370            "properties": {
1371                "max_age_days": {"type": "integer", "default": 90, "description": "Archive memories older than this many days"},
1372                "max_importance": {"type": "number", "default": 0.5, "description": "Only archive memories with importance below this"},
1373                "min_access_count": {"type": "integer", "default": 5, "description": "Skip memories accessed more than this many times"},
1374                "workspace": {"type": "string", "description": "Limit to specific workspace"},
1375                "dry_run": {"type": "boolean", "default": true, "description": "If true, only report what would be archived"}
1376            }
1377        }"#,
1378        annotations: ToolAnnotations::destructive(),
1379    },
1380    // Phase 3: Langfuse Integration (ENG-35) - feature-gated
1381    #[cfg(feature = "langfuse")]
1382    ToolDef {
1383        name: "langfuse_connect",
1384        description: "Configure Langfuse connection for observability integration. Stores config in metadata.",
1385        schema: r#"{
1386            "type": "object",
1387            "properties": {
1388                "public_key": {"type": "string", "description": "Langfuse public key (or use LANGFUSE_PUBLIC_KEY env var)"},
1389                "secret_key": {"type": "string", "description": "Langfuse secret key (or use LANGFUSE_SECRET_KEY env var)"},
1390                "base_url": {"type": "string", "default": "https://cloud.langfuse.com", "description": "Langfuse API base URL"}
1391            }
1392        }"#,
1393        annotations: ToolAnnotations::mutating(),
1394    },
1395    #[cfg(feature = "langfuse")]
1396    ToolDef {
1397        name: "langfuse_sync",
1398        description: "Start background sync from Langfuse traces to memories. Returns task_id for status checking.",
1399        schema: r#"{
1400            "type": "object",
1401            "properties": {
1402                "since": {"type": "string", "format": "date-time", "description": "Sync traces since this timestamp (default: 24h ago)"},
1403                "limit": {"type": "integer", "default": 100, "description": "Maximum traces to sync"},
1404                "workspace": {"type": "string", "description": "Workspace to create memories in"},
1405                "dry_run": {"type": "boolean", "default": false, "description": "Preview what would be synced without creating memories"}
1406            }
1407        }"#,
1408        annotations: ToolAnnotations::mutating(),
1409    },
1410    #[cfg(feature = "langfuse")]
1411    ToolDef {
1412        name: "langfuse_sync_status",
1413        description: "Check the status of a Langfuse sync task.",
1414        schema: r#"{
1415            "type": "object",
1416            "properties": {
1417                "task_id": {"type": "string", "description": "Task ID returned from langfuse_sync"}
1418            },
1419            "required": ["task_id"]
1420        }"#,
1421        annotations: ToolAnnotations::read_only(),
1422    },
1423    #[cfg(feature = "langfuse")]
1424    ToolDef {
1425        name: "langfuse_extract_patterns",
1426        description: "Extract patterns from Langfuse traces without saving. Preview mode for pattern discovery.",
1427        schema: r#"{
1428            "type": "object",
1429            "properties": {
1430                "since": {"type": "string", "format": "date-time", "description": "Analyze traces since this timestamp"},
1431                "limit": {"type": "integer", "default": 50, "description": "Maximum traces to analyze"},
1432                "min_confidence": {"type": "number", "default": 0.7, "description": "Minimum confidence for patterns"}
1433            }
1434        }"#,
1435        annotations: ToolAnnotations::read_only(),
1436    },
1437    #[cfg(feature = "langfuse")]
1438    ToolDef {
1439        name: "memory_from_trace",
1440        description: "Create a memory from a specific Langfuse trace ID.",
1441        schema: r#"{
1442            "type": "object",
1443            "properties": {
1444                "trace_id": {"type": "string", "description": "Langfuse trace ID"},
1445                "memory_type": {"type": "string", "enum": ["note", "episodic", "procedural", "learning"], "default": "episodic", "description": "Type of memory to create"},
1446                "workspace": {"type": "string", "description": "Workspace for the memory"},
1447                "tags": {"type": "array", "items": {"type": "string"}, "description": "Additional tags"}
1448            },
1449            "required": ["trace_id"]
1450        }"#,
1451        annotations: ToolAnnotations::mutating(),
1452    },
1453    // Phase 4: Search Result Caching (ENG-36)
1454    ToolDef {
1455        name: "search_cache_feedback",
1456        description: "Report feedback on search results quality. Helps tune the adaptive cache threshold.",
1457        schema: r#"{
1458            "type": "object",
1459            "properties": {
1460                "query": {"type": "string", "description": "The search query"},
1461                "positive": {"type": "boolean", "description": "True if results were helpful, false otherwise"},
1462                "workspace": {"type": "string", "description": "Workspace filter used (if any)"}
1463            },
1464            "required": ["query", "positive"]
1465        }"#,
1466        annotations: ToolAnnotations::mutating(),
1467    },
1468    ToolDef {
1469        name: "search_cache_stats",
1470        description: "Get search result cache statistics including hit rate, entry count, and current threshold.",
1471        schema: r#"{
1472            "type": "object",
1473            "properties": {}
1474        }"#,
1475        annotations: ToolAnnotations::read_only(),
1476    },
1477    ToolDef {
1478        name: "search_cache_clear",
1479        description: "Clear the search result cache. Useful after bulk operations.",
1480        schema: r#"{
1481            "type": "object",
1482            "properties": {
1483                "workspace": {"type": "string", "description": "Only clear cache for this workspace (optional)"}
1484            }
1485        }"#,
1486        annotations: ToolAnnotations::destructive(),
1487    },
1488    // Phase 5: Memory Lifecycle Management (ENG-37)
1489    ToolDef {
1490        name: "lifecycle_status",
1491        description: "Get lifecycle statistics (active/stale/archived counts by workspace).",
1492        schema: r#"{
1493            "type": "object",
1494            "properties": {
1495                "workspace": {"type": "string", "description": "Filter by workspace (optional)"}
1496            }
1497        }"#,
1498        annotations: ToolAnnotations::read_only(),
1499    },
1500    ToolDef {
1501        name: "lifecycle_run",
1502        description: "Manually trigger a lifecycle cycle (mark stale, archive old). Dry run by default.",
1503        schema: r#"{
1504            "type": "object",
1505            "properties": {
1506                "dry_run": {"type": "boolean", "default": true, "description": "Preview changes without applying"},
1507                "workspace": {"type": "string", "description": "Limit to specific workspace"},
1508                "stale_days": {"type": "integer", "default": 30, "description": "Mark memories older than this as stale"},
1509                "archive_days": {"type": "integer", "default": 90, "description": "Archive memories older than this"},
1510                "min_importance": {"type": "number", "default": 0.5, "description": "Only process memories below this importance"}
1511            }
1512        }"#,
1513        annotations: ToolAnnotations::idempotent(),
1514    },
1515    ToolDef {
1516        name: "memory_set_lifecycle",
1517        description: "Manually set the lifecycle state of a memory.",
1518        schema: r#"{
1519            "type": "object",
1520            "properties": {
1521                "id": {"type": "integer", "description": "Memory ID"},
1522                "state": {"type": "string", "enum": ["active", "stale", "archived"], "description": "New lifecycle state"}
1523            },
1524            "required": ["id", "state"]
1525        }"#,
1526        annotations: ToolAnnotations::mutating(),
1527    },
1528    ToolDef {
1529        name: "lifecycle_config",
1530        description: "Get or set lifecycle configuration (intervals, thresholds).",
1531        schema: r#"{
1532            "type": "object",
1533            "properties": {
1534                "stale_days": {"type": "integer", "description": "Days before marking as stale"},
1535                "archive_days": {"type": "integer", "description": "Days before auto-archiving"},
1536                "min_importance": {"type": "number", "description": "Importance threshold for lifecycle"},
1537                "min_access_count": {"type": "integer", "description": "Access count threshold"}
1538            }
1539        }"#,
1540        annotations: ToolAnnotations::read_only(),
1541    },
1542    // Retention Policies
1543    ToolDef {
1544        name: "retention_policy_set",
1545        description: "Set a retention policy for a workspace. Controls auto-compression, max memory count, and auto-deletion.",
1546        schema: r#"{
1547            "type": "object",
1548            "properties": {
1549                "workspace": {"type": "string", "description": "Workspace name"},
1550                "max_age_days": {"type": "integer", "description": "Hard age limit — auto-delete after this many days"},
1551                "max_memories": {"type": "integer", "description": "Maximum active memories in this workspace"},
1552                "compress_after_days": {"type": "integer", "description": "Auto-compress memories older than this"},
1553                "compress_max_importance": {"type": "number", "description": "Only compress memories with importance <= this (default 0.3)"},
1554                "compress_min_access": {"type": "integer", "description": "Skip compression if access_count >= this (default 3)"},
1555                "auto_delete_after_days": {"type": "integer", "description": "Auto-delete archived memories older than this"},
1556                "exclude_types": {"type": "array", "items": {"type": "string"}, "description": "Memory types exempt from policy (e.g. [\"decision\", \"checkpoint\"])"}
1557            },
1558            "required": ["workspace"]
1559        }"#,
1560        annotations: ToolAnnotations::mutating(),
1561    },
1562    ToolDef {
1563        name: "retention_policy_get",
1564        description: "Get the retention policy for a workspace.",
1565        schema: r#"{
1566            "type": "object",
1567            "properties": {
1568                "workspace": {"type": "string", "description": "Workspace name"}
1569            },
1570            "required": ["workspace"]
1571        }"#,
1572        annotations: ToolAnnotations::read_only(),
1573    },
1574    ToolDef {
1575        name: "retention_policy_list",
1576        description: "List all retention policies across all workspaces.",
1577        schema: r#"{
1578            "type": "object",
1579            "properties": {}
1580        }"#,
1581        annotations: ToolAnnotations::read_only(),
1582    },
1583    ToolDef {
1584        name: "retention_policy_delete",
1585        description: "Delete a retention policy for a workspace.",
1586        schema: r#"{
1587            "type": "object",
1588            "properties": {
1589                "workspace": {"type": "string", "description": "Workspace name"}
1590            },
1591            "required": ["workspace"]
1592        }"#,
1593        annotations: ToolAnnotations::destructive(),
1594    },
1595    ToolDef {
1596        name: "retention_policy_apply",
1597        description: "Apply all retention policies now. Compresses, caps, and deletes per workspace rules.",
1598        schema: r#"{
1599            "type": "object",
1600            "properties": {
1601                "dry_run": {"type": "boolean", "default": false, "description": "Preview what would happen without making changes"}
1602            }
1603        }"#,
1604        annotations: ToolAnnotations::idempotent(),
1605    },
1606    // Event System
1607    ToolDef {
1608        name: "memory_events_poll",
1609        description: "Poll for memory events (create, update, delete, etc.) since a given point. Useful for syncing and monitoring.",
1610        schema: r#"{
1611            "type": "object",
1612            "properties": {
1613                "since_id": {"type": "integer", "description": "Return events after this event ID"},
1614                "since_time": {"type": "string", "format": "date-time", "description": "Return events after this timestamp (RFC3339)"},
1615                "agent_id": {"type": "string", "description": "Filter events for specific agent"},
1616                "limit": {"type": "integer", "default": 100, "description": "Maximum events to return"}
1617            }
1618        }"#,
1619        annotations: ToolAnnotations::read_only(),
1620    },
1621    ToolDef {
1622        name: "memory_events_clear",
1623        description: "Clear old events from the event log. Helps manage storage for long-running systems.",
1624        schema: r#"{
1625            "type": "object",
1626            "properties": {
1627                "before_id": {"type": "integer", "description": "Delete events before this ID"},
1628                "before_time": {"type": "string", "format": "date-time", "description": "Delete events before this timestamp"},
1629                "keep_recent": {"type": "integer", "description": "Keep only the N most recent events"}
1630            }
1631        }"#,
1632        annotations: ToolAnnotations::destructive(),
1633    },
1634    // Advanced Sync
1635    ToolDef {
1636        name: "sync_version",
1637        description: "Get the current sync version and metadata. Used to check if local data is up-to-date.",
1638        schema: r#"{
1639            "type": "object",
1640            "properties": {}
1641        }"#,
1642        annotations: ToolAnnotations::read_only(),
1643    },
1644    ToolDef {
1645        name: "sync_delta",
1646        description: "Get changes (delta) since a specific version. Returns created, updated, and deleted memories.",
1647        schema: r#"{
1648            "type": "object",
1649            "properties": {
1650                "since_version": {"type": "integer", "description": "Version to get changes from"}
1651            },
1652            "required": ["since_version"]
1653        }"#,
1654        annotations: ToolAnnotations::read_only(),
1655    },
1656    ToolDef {
1657        name: "sync_state",
1658        description: "Get or update sync state for a specific agent. Tracks what each agent has synced.",
1659        schema: r#"{
1660            "type": "object",
1661            "properties": {
1662                "agent_id": {"type": "string", "description": "Agent identifier"},
1663                "update_version": {"type": "integer", "description": "If provided, updates the agent's last synced version"}
1664            },
1665            "required": ["agent_id"]
1666        }"#,
1667        annotations: ToolAnnotations::read_only(),
1668    },
1669    ToolDef {
1670        name: "sync_cleanup",
1671        description: "Clean up old sync data (events, etc.) older than specified days.",
1672        schema: r#"{
1673            "type": "object",
1674            "properties": {
1675                "older_than_days": {"type": "integer", "default": 30, "description": "Delete sync data older than this many days"}
1676            }
1677        }"#,
1678        annotations: ToolAnnotations::destructive(),
1679    },
1680    // Multi-Agent Sharing
1681    ToolDef {
1682        name: "memory_share",
1683        description: "Share a memory with another agent. The target agent can poll for shared memories.",
1684        schema: r#"{
1685            "type": "object",
1686            "properties": {
1687                "memory_id": {"type": "integer", "description": "ID of memory to share"},
1688                "from_agent": {"type": "string", "description": "Sender agent identifier"},
1689                "to_agent": {"type": "string", "description": "Recipient agent identifier"},
1690                "message": {"type": "string", "description": "Optional message to include with share"}
1691            },
1692            "required": ["memory_id", "from_agent", "to_agent"]
1693        }"#,
1694        annotations: ToolAnnotations::mutating(),
1695    },
1696    ToolDef {
1697        name: "memory_shared_poll",
1698        description: "Poll for memories shared with this agent.",
1699        schema: r#"{
1700            "type": "object",
1701            "properties": {
1702                "agent_id": {"type": "string", "description": "Agent identifier to check shares for"},
1703                "include_acknowledged": {"type": "boolean", "default": false, "description": "Include already acknowledged shares"}
1704            },
1705            "required": ["agent_id"]
1706        }"#,
1707        annotations: ToolAnnotations::read_only(),
1708    },
1709    ToolDef {
1710        name: "memory_share_ack",
1711        description: "Acknowledge receipt of a shared memory.",
1712        schema: r#"{
1713            "type": "object",
1714            "properties": {
1715                "share_id": {"type": "integer", "description": "Share ID to acknowledge"},
1716                "agent_id": {"type": "string", "description": "Agent acknowledging the share"}
1717            },
1718            "required": ["share_id", "agent_id"]
1719        }"#,
1720        annotations: ToolAnnotations::mutating(),
1721    },
1722    // Search Variants
1723    ToolDef {
1724        name: "memory_search_by_identity",
1725        description: "Search memories by identity (person, entity, or alias). Finds all mentions of a specific identity across memories.",
1726        schema: r#"{
1727            "type": "object",
1728            "properties": {
1729                "identity": {"type": "string", "description": "Identity name or alias to search for"},
1730                "workspace": {"type": "string", "description": "Optional: limit search to specific workspace"},
1731                "limit": {"type": "integer", "default": 50, "description": "Maximum results to return"}
1732            },
1733            "required": ["identity"]
1734        }"#,
1735        annotations: ToolAnnotations::mutating(),
1736    },
1737    ToolDef {
1738        name: "memory_session_search",
1739        description: "Search within session transcript chunks. Useful for finding content from past conversations.",
1740        schema: r#"{
1741            "type": "object",
1742            "properties": {
1743                "query": {"type": "string", "description": "Search query"},
1744                "session_id": {"type": "string", "description": "Optional: limit to specific session"},
1745                "workspace": {"type": "string", "description": "Optional: limit to specific workspace"},
1746                "limit": {"type": "integer", "default": 20, "description": "Maximum results to return"}
1747            },
1748            "required": ["query"]
1749        }"#,
1750        annotations: ToolAnnotations::mutating(),
1751    },
1752    // Image Handling
1753    ToolDef {
1754        name: "memory_upload_image",
1755        description: "Upload an image file and attach it to a memory. The image will be stored locally and linked to the memory's metadata.",
1756        schema: r#"{
1757            "type": "object",
1758            "properties": {
1759                "memory_id": {"type": "integer", "description": "ID of the memory to attach the image to"},
1760                "file_path": {"type": "string", "description": "Path to the image file to upload"},
1761                "image_index": {"type": "integer", "default": 0, "description": "Index for ordering multiple images (0-based)"},
1762                "caption": {"type": "string", "description": "Optional caption for the image"}
1763            },
1764            "required": ["memory_id", "file_path"]
1765        }"#,
1766        annotations: ToolAnnotations::mutating(),
1767    },
1768    ToolDef {
1769        name: "memory_migrate_images",
1770        description: "Migrate existing base64-encoded images in memories to file storage. Scans all memories and uploads any embedded data URIs to storage, replacing them with file references.",
1771        schema: r#"{
1772            "type": "object",
1773            "properties": {
1774                "dry_run": {"type": "boolean", "default": false, "description": "If true, only report what would be migrated without making changes"}
1775            }
1776        }"#,
1777        annotations: ToolAnnotations::idempotent(),
1778    },
1779    // Auto-Tagging
1780    ToolDef {
1781        name: "memory_suggest_tags",
1782        description: "Suggest tags for a memory based on AI content analysis. Uses pattern matching, keyword extraction, and structure detection to suggest relevant tags with confidence scores.",
1783        schema: r#"{
1784            "type": "object",
1785            "properties": {
1786                "id": {"type": "integer", "description": "Memory ID to analyze (alternative to content)"},
1787                "memory_id": {"type": "integer", "description": "Memory ID to analyze (alias for id)"},
1788                "content": {"type": "string", "description": "Content to analyze (alternative to id/memory_id)"},
1789                "type": {"type": "string", "enum": ["note", "todo", "issue", "decision", "preference", "learning", "context", "credential"], "description": "Memory type (used when providing content directly)"},
1790                "existing_tags": {"type": "array", "items": {"type": "string"}, "description": "Tags already on the memory (excluded from suggestions)"},
1791                "min_confidence": {"type": "number", "minimum": 0, "maximum": 1, "default": 0.5, "description": "Minimum confidence threshold for suggestions"},
1792                "max_tags": {"type": "integer", "default": 5, "description": "Maximum number of tags to suggest"},
1793                "enable_patterns": {"type": "boolean", "default": true, "description": "Use pattern-based tagging"},
1794                "enable_keywords": {"type": "boolean", "default": true, "description": "Use keyword-based tagging"},
1795                "enable_entities": {"type": "boolean", "default": true, "description": "Use entity-based tagging"},
1796                "enable_type_tags": {"type": "boolean", "default": true, "description": "Add tags based on memory type"},
1797                "keyword_mappings": {"type": "object", "description": "Custom keyword-to-tag mappings (e.g., {\"ibvi\": \"project/ibvi\"})"}
1798            }
1799        }"#,
1800        annotations: ToolAnnotations::read_only(),
1801    },
1802    ToolDef {
1803        name: "memory_auto_tag",
1804        description: "Automatically suggest and optionally apply tags to a memory. Analyzes content using AI heuristics and can merge suggested tags with existing ones.",
1805        schema: r#"{
1806            "type": "object",
1807            "properties": {
1808                "id": {"type": "integer", "description": "Memory ID to auto-tag"},
1809                "memory_id": {"type": "integer", "description": "Memory ID (alias for id)"},
1810                "apply": {"type": "boolean", "default": false, "description": "If true, apply the suggested tags to the memory. If false, only return suggestions."},
1811                "merge": {"type": "boolean", "default": true, "description": "If true and apply=true, merge with existing tags. If false, replace existing tags."},
1812                "min_confidence": {"type": "number", "minimum": 0, "maximum": 1, "default": 0.5, "description": "Minimum confidence threshold"},
1813                "max_tags": {"type": "integer", "default": 5, "description": "Maximum tags to suggest/apply"},
1814                "keyword_mappings": {"type": "object", "description": "Custom keyword-to-tag mappings"}
1815            },
1816            "required": ["id"]
1817        }"#,
1818        annotations: ToolAnnotations::mutating(),
1819    },
1820    // Phase 8: Salience & Sessions (ENG-66 to ENG-77)
1821    ToolDef {
1822        name: "salience_get",
1823        description: "Get the salience score for a memory. Returns recency, frequency, importance, and feedback components with the combined score and lifecycle state.",
1824        schema: r#"{
1825            "type": "object",
1826            "properties": {
1827                "id": {"type": "integer", "description": "Memory ID to get salience for"},
1828                "feedback_signal": {"type": "number", "minimum": -1, "maximum": 1, "default": 0, "description": "Optional feedback signal (-1 to 1) to include in calculation"}
1829            },
1830            "required": ["id"]
1831        }"#,
1832        annotations: ToolAnnotations::read_only(),
1833    },
1834    ToolDef {
1835        name: "salience_set_importance",
1836        description: "Set the importance score for a memory. This is the static importance component of salience.",
1837        schema: r#"{
1838            "type": "object",
1839            "properties": {
1840                "id": {"type": "integer", "description": "Memory ID"},
1841                "importance": {"type": "number", "minimum": 0, "maximum": 1, "description": "Importance score (0-1)"}
1842            },
1843            "required": ["id", "importance"]
1844        }"#,
1845        annotations: ToolAnnotations::mutating(),
1846    },
1847    ToolDef {
1848        name: "salience_boost",
1849        description: "Boost a memory's salience score temporarily or permanently. Useful for marking memories as contextually relevant.",
1850        schema: r#"{
1851            "type": "object",
1852            "properties": {
1853                "id": {"type": "integer", "description": "Memory ID to boost"},
1854                "boost_amount": {"type": "number", "minimum": 0, "maximum": 1, "default": 0.2, "description": "Amount to boost (0-1)"},
1855                "reason": {"type": "string", "description": "Optional reason for boosting"}
1856            },
1857            "required": ["id"]
1858        }"#,
1859        annotations: ToolAnnotations::mutating(),
1860    },
1861    ToolDef {
1862        name: "salience_demote",
1863        description: "Demote a memory's salience score. Useful for marking memories as less relevant.",
1864        schema: r#"{
1865            "type": "object",
1866            "properties": {
1867                "id": {"type": "integer", "description": "Memory ID to demote"},
1868                "demote_amount": {"type": "number", "minimum": 0, "maximum": 1, "default": 0.2, "description": "Amount to demote (0-1)"},
1869                "reason": {"type": "string", "description": "Optional reason for demoting"}
1870            },
1871            "required": ["id"]
1872        }"#,
1873        annotations: ToolAnnotations::mutating(),
1874    },
1875    ToolDef {
1876        name: "salience_decay_run",
1877        description: "Run temporal decay on all memories. Updates lifecycle states (Active → Stale → Archived) based on salience scores.",
1878        schema: r#"{
1879            "type": "object",
1880            "properties": {
1881                "dry_run": {"type": "boolean", "default": false, "description": "If true, compute changes without persisting updates"},
1882                "record_history": {"type": "boolean", "default": true, "description": "Record salience history entries while updating"},
1883                "workspace": {"type": "string", "description": "Limit to specific workspace"},
1884                "stale_threshold_days": {"type": "integer", "minimum": 1, "description": "Days of inactivity before marking stale"},
1885                "archive_threshold_days": {"type": "integer", "minimum": 1, "description": "Days of inactivity before suggesting archive"}
1886            }
1887        }"#,
1888        annotations: ToolAnnotations::destructive(),
1889    },
1890    ToolDef {
1891        name: "salience_stats",
1892        description: "Get salience statistics across all memories. Returns distribution, percentiles, and state counts.",
1893        schema: r#"{
1894            "type": "object",
1895            "properties": {
1896                "workspace": {"type": "string", "description": "Limit to specific workspace"}
1897            }
1898        }"#,
1899        annotations: ToolAnnotations::read_only(),
1900    },
1901    ToolDef {
1902        name: "salience_history",
1903        description: "Get salience score history for a memory. Shows how salience has changed over time.",
1904        schema: r#"{
1905            "type": "object",
1906            "properties": {
1907                "id": {"type": "integer", "description": "Memory ID"},
1908                "limit": {"type": "integer", "default": 50, "description": "Maximum history entries to return"}
1909            },
1910            "required": ["id"]
1911        }"#,
1912        annotations: ToolAnnotations::read_only(),
1913    },
1914    ToolDef {
1915        name: "salience_top",
1916        description: "Get top memories by salience score. Useful for context injection.",
1917        schema: r#"{
1918            "type": "object",
1919            "properties": {
1920                "limit": {"type": "integer", "default": 20, "description": "Maximum memories to return"},
1921                "workspace": {"type": "string", "description": "Limit to specific workspace"},
1922                "min_score": {"type": "number", "minimum": 0, "maximum": 1, "description": "Minimum salience score"},
1923                "memory_type": {"type": "string", "description": "Filter by memory type"}
1924            }
1925        }"#,
1926        annotations: ToolAnnotations::read_only(),
1927    },
1928    // Session Context Tools (ENG-70, ENG-71)
1929    ToolDef {
1930        name: "session_context_create",
1931        description: "Create a new session context for tracking related memories during a conversation or task.",
1932        schema: r#"{
1933            "type": "object",
1934            "properties": {
1935                "name": {"type": "string", "description": "Session name"},
1936                "description": {"type": "string", "description": "Session description"},
1937                "workspace": {"type": "string", "description": "Workspace for the session"},
1938                "metadata": {"type": "object", "description": "Additional session metadata"}
1939            },
1940            "required": ["name"]
1941        }"#,
1942        annotations: ToolAnnotations::mutating(),
1943    },
1944    ToolDef {
1945        name: "session_context_add_memory",
1946        description: "Add a memory to a session context with relevance score and role.",
1947        schema: r#"{
1948            "type": "object",
1949            "properties": {
1950                "session_id": {"type": "string", "description": "Session ID"},
1951                "memory_id": {"type": "integer", "description": "Memory ID to add"},
1952                "relevance_score": {"type": "number", "minimum": 0, "maximum": 1, "default": 1.0, "description": "How relevant this memory is to the session"},
1953                "context_role": {"type": "string", "enum": ["referenced", "created", "updated", "pinned"], "default": "referenced", "description": "Role of the memory in the session"}
1954            },
1955            "required": ["session_id", "memory_id"]
1956        }"#,
1957        annotations: ToolAnnotations::mutating(),
1958    },
1959    ToolDef {
1960        name: "session_context_remove_memory",
1961        description: "Remove a memory from a session context.",
1962        schema: r#"{
1963            "type": "object",
1964            "properties": {
1965                "session_id": {"type": "string", "description": "Session ID"},
1966                "memory_id": {"type": "integer", "description": "Memory ID to remove"}
1967            },
1968            "required": ["session_id", "memory_id"]
1969        }"#,
1970        annotations: ToolAnnotations::mutating(),
1971    },
1972    ToolDef {
1973        name: "session_context_get",
1974        description: "Get a session context with its linked memories.",
1975        schema: r#"{
1976            "type": "object",
1977            "properties": {
1978                "session_id": {"type": "string", "description": "Session ID"}
1979            },
1980            "required": ["session_id"]
1981        }"#,
1982        annotations: ToolAnnotations::read_only(),
1983    },
1984    ToolDef {
1985        name: "session_context_list",
1986        description: "List all session contexts with optional filtering.",
1987        schema: r#"{
1988            "type": "object",
1989            "properties": {
1990                "workspace": {"type": "string", "description": "Filter by workspace"},
1991                "active_only": {"type": "boolean", "default": false, "description": "Only return active sessions"},
1992                "limit": {"type": "integer", "default": 50, "description": "Maximum sessions to return"},
1993                "offset": {"type": "integer", "default": 0, "description": "Offset for pagination"}
1994            }
1995        }"#,
1996        annotations: ToolAnnotations::read_only(),
1997    },
1998    ToolDef {
1999        name: "session_context_search",
2000        description: "Search memories within a specific session context.",
2001        schema: r#"{
2002            "type": "object",
2003            "properties": {
2004                "session_id": {"type": "string", "description": "Session ID to search within"},
2005                "query": {"type": "string", "description": "Search query"},
2006                "limit": {"type": "integer", "default": 20, "description": "Maximum results"}
2007            },
2008            "required": ["session_id", "query"]
2009        }"#,
2010        annotations: ToolAnnotations::read_only(),
2011    },
2012    ToolDef {
2013        name: "session_context_update_summary",
2014        description: "Update the summary of a session context.",
2015        schema: r#"{
2016            "type": "object",
2017            "properties": {
2018                "session_id": {"type": "string", "description": "Session ID"},
2019                "summary": {"type": "string", "description": "New session summary"}
2020            },
2021            "required": ["session_id", "summary"]
2022        }"#,
2023        annotations: ToolAnnotations::mutating(),
2024    },
2025    ToolDef {
2026        name: "session_context_end",
2027        description: "End a session context, marking it as inactive.",
2028        schema: r#"{
2029            "type": "object",
2030            "properties": {
2031                "session_id": {"type": "string", "description": "Session ID to end"},
2032                "summary": {"type": "string", "description": "Optional final summary"}
2033            },
2034            "required": ["session_id"]
2035        }"#,
2036        annotations: ToolAnnotations::mutating(),
2037    },
2038    ToolDef {
2039        name: "session_context_export",
2040        description: "Export a session context with all its memories for archival or sharing.",
2041        schema: r#"{
2042            "type": "object",
2043            "properties": {
2044                "session_id": {"type": "string", "description": "Session ID to export"},
2045                "include_content": {"type": "boolean", "default": true, "description": "Include full memory content"},
2046                "format": {"type": "string", "enum": ["json", "markdown"], "default": "json", "description": "Export format"}
2047            },
2048            "required": ["session_id"]
2049        }"#,
2050        annotations: ToolAnnotations::read_only(),
2051    },
2052    // Phase 9: Context Quality (ENG-48 to ENG-66)
2053    ToolDef {
2054        name: "quality_score",
2055        description: "Get the quality score for a memory with detailed breakdown of clarity, completeness, freshness, consistency, and source trust components.",
2056        schema: r#"{
2057            "type": "object",
2058            "properties": {
2059                "id": {"type": "integer", "description": "Memory ID to score"}
2060            },
2061            "required": ["id"]
2062        }"#,
2063        annotations: ToolAnnotations::read_only(),
2064    },
2065    ToolDef {
2066        name: "quality_report",
2067        description: "Generate a comprehensive quality report for a workspace. Includes quality distribution, top issues, conflict and duplicate counts.",
2068        schema: r#"{
2069            "type": "object",
2070            "properties": {
2071                "workspace": {"type": "string", "description": "Workspace to analyze (default: 'default')"}
2072            }
2073        }"#,
2074        annotations: ToolAnnotations::read_only(),
2075    },
2076    ToolDef {
2077        name: "quality_find_duplicates",
2078        description: "Find near-duplicate memories using text similarity. Returns pairs of similar memories above the threshold.",
2079        schema: r#"{
2080            "type": "object",
2081            "properties": {
2082                "threshold": {"type": "number", "minimum": 0, "maximum": 1, "default": 0.85, "description": "Similarity threshold (0-1)"},
2083                "limit": {"type": "integer", "default": 100, "description": "Maximum memories to compare"}
2084            }
2085        }"#,
2086        annotations: ToolAnnotations::read_only(),
2087    },
2088    ToolDef {
2089        name: "quality_get_duplicates",
2090        description: "Get pending duplicate candidates that need review.",
2091        schema: r#"{
2092            "type": "object",
2093            "properties": {
2094                "limit": {"type": "integer", "default": 50, "description": "Maximum duplicates to return"}
2095            }
2096        }"#,
2097        annotations: ToolAnnotations::read_only(),
2098    },
2099    ToolDef {
2100        name: "quality_find_conflicts",
2101        description: "Detect conflicts for a memory against existing memories. Finds contradictions, staleness, and semantic overlaps.",
2102        schema: r#"{
2103            "type": "object",
2104            "properties": {
2105                "id": {"type": "integer", "description": "Memory ID to check for conflicts"}
2106            },
2107            "required": ["id"]
2108        }"#,
2109        annotations: ToolAnnotations::read_only(),
2110    },
2111    ToolDef {
2112        name: "quality_get_conflicts",
2113        description: "Get unresolved conflicts that need attention.",
2114        schema: r#"{
2115            "type": "object",
2116            "properties": {
2117                "limit": {"type": "integer", "default": 50, "description": "Maximum conflicts to return"}
2118            }
2119        }"#,
2120        annotations: ToolAnnotations::read_only(),
2121    },
2122    ToolDef {
2123        name: "quality_resolve_conflict",
2124        description: "Resolve a conflict between memories. Options: keep_a, keep_b, merge, keep_both, delete_both, false_positive.",
2125        schema: r#"{
2126            "type": "object",
2127            "properties": {
2128                "conflict_id": {"type": "integer", "description": "Conflict ID to resolve"},
2129                "resolution": {"type": "string", "enum": ["keep_a", "keep_b", "merge", "keep_both", "delete_both", "false_positive"], "description": "How to resolve the conflict"},
2130                "notes": {"type": "string", "description": "Optional notes about the resolution"}
2131            },
2132            "required": ["conflict_id", "resolution"]
2133        }"#,
2134        annotations: ToolAnnotations::destructive(),
2135    },
2136    ToolDef {
2137        name: "quality_source_trust",
2138        description: "Get or update trust score for a source type. Higher trust means memories from this source are weighted more in quality calculations.",
2139        schema: r#"{
2140            "type": "object",
2141            "properties": {
2142                "source_type": {"type": "string", "description": "Source type (user, seed, extraction, inference, external)"},
2143                "source_identifier": {"type": "string", "description": "Optional specific source identifier"},
2144                "trust_score": {"type": "number", "minimum": 0, "maximum": 1, "description": "New trust score (omit to just get current score)"},
2145                "notes": {"type": "string", "description": "Notes about this source"}
2146            },
2147            "required": ["source_type"]
2148        }"#,
2149        annotations: ToolAnnotations::read_only(),
2150    },
2151    ToolDef {
2152        name: "quality_improve",
2153        description: "Get suggestions for improving a memory's quality. Returns actionable recommendations.",
2154        schema: r#"{
2155            "type": "object",
2156            "properties": {
2157                "id": {"type": "integer", "description": "Memory ID to analyze"}
2158            },
2159            "required": ["id"]
2160        }"#,
2161        annotations: ToolAnnotations::mutating(),
2162    },
2163    // Phase 7: Meilisearch Integration (ENG-58) - feature-gated
2164    #[cfg(feature = "meilisearch")]
2165    ToolDef {
2166        name: "meilisearch_search",
2167        description: "Search memories using Meilisearch (typo-tolerant, fast full-text). Requires Meilisearch to be configured. Falls back to hybrid search if unavailable.",
2168        schema: r#"{
2169            "type": "object",
2170            "properties": {
2171                "query": {"type": "string", "description": "Search query text"},
2172                "limit": {"type": "integer", "default": 20, "description": "Maximum results to return"},
2173                "offset": {"type": "integer", "default": 0, "description": "Number of results to skip"},
2174                "workspace": {"type": "string", "description": "Filter by workspace"},
2175                "tags": {"type": "array", "items": {"type": "string"}, "description": "Filter by tags (AND logic)"},
2176                "memory_type": {"type": "string", "description": "Filter by memory type"}
2177            },
2178            "required": ["query"]
2179        }"#,
2180        annotations: ToolAnnotations::read_only(),
2181    },
2182    #[cfg(feature = "meilisearch")]
2183    ToolDef {
2184        name: "meilisearch_reindex",
2185        description: "Trigger a full re-sync from SQLite to Meilisearch. Use after bulk imports or if the index is out of sync.",
2186        schema: r#"{
2187            "type": "object",
2188            "properties": {}
2189        }"#,
2190        annotations: ToolAnnotations::idempotent(),
2191    },
2192    #[cfg(feature = "meilisearch")]
2193    ToolDef {
2194        name: "meilisearch_status",
2195        description: "Get Meilisearch index status including document count, indexing state, and health.",
2196        schema: r#"{
2197            "type": "object",
2198            "properties": {}
2199        }"#,
2200        annotations: ToolAnnotations::read_only(),
2201    },
2202    #[cfg(feature = "meilisearch")]
2203    ToolDef {
2204        name: "meilisearch_config",
2205        description: "Show current Meilisearch configuration (URL, sync interval, enabled status).",
2206        schema: r#"{
2207            "type": "object",
2208            "properties": {}
2209        }"#,
2210        annotations: ToolAnnotations::read_only(),
2211    },
2212    // ── Agent Registry ────────────────────────────────────────────────────
2213    ToolDef {
2214        name: "agent_register",
2215        description: "Register an AI agent with capabilities and namespace isolation. Upserts if agent_id already exists.",
2216        schema: r#"{
2217            "type": "object",
2218            "properties": {
2219                "agent_id": {"type": "string", "description": "Unique identifier for the agent"},
2220                "display_name": {"type": "string", "description": "Human-readable name (defaults to agent_id)"},
2221                "capabilities": {"type": "array", "items": {"type": "string"}, "description": "List of capabilities (e.g., 'search', 'create', 'analyze')"},
2222                "namespaces": {"type": "array", "items": {"type": "string"}, "description": "Namespaces the agent operates in (default: ['default'])"},
2223                "metadata": {"type": "object", "description": "Additional metadata as key-value pairs"}
2224            },
2225            "required": ["agent_id"]
2226        }"#,
2227        annotations: ToolAnnotations::mutating(),
2228    },
2229    ToolDef {
2230        name: "agent_deregister",
2231        description: "Deregister an AI agent (soft delete — sets status to 'inactive').",
2232        schema: r#"{
2233            "type": "object",
2234            "properties": {
2235                "agent_id": {"type": "string", "description": "ID of the agent to deregister"}
2236            },
2237            "required": ["agent_id"]
2238        }"#,
2239        annotations: ToolAnnotations::destructive(),
2240    },
2241    ToolDef {
2242        name: "agent_heartbeat",
2243        description: "Update an agent's heartbeat timestamp to indicate it is still alive.",
2244        schema: r#"{
2245            "type": "object",
2246            "properties": {
2247                "agent_id": {"type": "string", "description": "ID of the agent sending heartbeat"}
2248            },
2249            "required": ["agent_id"]
2250        }"#,
2251        annotations: ToolAnnotations::mutating(),
2252    },
2253    ToolDef {
2254        name: "agent_list",
2255        description: "List registered agents, optionally filtered by status or namespace.",
2256        schema: r#"{
2257            "type": "object",
2258            "properties": {
2259                "status": {"type": "string", "enum": ["active", "inactive"], "description": "Filter by agent status"},
2260                "namespace": {"type": "string", "description": "Filter by namespace (returns agents that include this namespace)"}
2261            }
2262        }"#,
2263        annotations: ToolAnnotations::read_only(),
2264    },
2265    ToolDef {
2266        name: "agent_get",
2267        description: "Get details of a specific registered agent by ID.",
2268        schema: r#"{
2269            "type": "object",
2270            "properties": {
2271                "agent_id": {"type": "string", "description": "ID of the agent to retrieve"}
2272            },
2273            "required": ["agent_id"]
2274        }"#,
2275        annotations: ToolAnnotations::read_only(),
2276    },
2277    ToolDef {
2278        name: "agent_capabilities",
2279        description: "Update the capabilities list of a registered agent.",
2280        schema: r#"{
2281            "type": "object",
2282            "properties": {
2283                "agent_id": {"type": "string", "description": "ID of the agent to update"},
2284                "capabilities": {"type": "array", "items": {"type": "string"}, "description": "New capabilities list (replaces existing)"}
2285            },
2286            "required": ["agent_id", "capabilities"]
2287        }"#,
2288        annotations: ToolAnnotations::mutating(),
2289    },
2290];
2291
2292/// Get all tool definitions as ToolDefinition structs
2293pub fn get_tool_definitions() -> Vec<ToolDefinition> {
2294    TOOL_DEFINITIONS
2295        .iter()
2296        .map(|def| ToolDefinition {
2297            name: def.name.to_string(),
2298            description: def.description.to_string(),
2299            input_schema: serde_json::from_str(def.schema).unwrap_or(json!({})),
2300            annotations: Some(def.annotations.clone()),
2301        })
2302        .collect()
2303}
2304
2305#[cfg(test)]
2306mod tests {
2307    use super::*;
2308
2309    #[test]
2310    fn test_tool_definitions_all_parseable() {
2311        let tools = get_tool_definitions();
2312        assert!(!tools.is_empty(), "TOOL_DEFINITIONS must not be empty");
2313        for tool in &tools {
2314            assert!(!tool.name.is_empty(), "tool name must not be empty");
2315            assert!(
2316                !tool.description.is_empty(),
2317                "tool description must not be empty"
2318            );
2319            assert!(
2320                tool.input_schema.is_object(),
2321                "tool '{}' schema must be a JSON object",
2322                tool.name
2323            );
2324        }
2325    }
2326
2327    #[test]
2328    fn test_read_only_tools_have_annotation() {
2329        let tools = get_tool_definitions();
2330        let read_only_names = ["memory_get", "memory_list", "memory_search", "memory_stats"];
2331        for name in read_only_names {
2332            let tool = tools
2333                .iter()
2334                .find(|t| t.name == name)
2335                .unwrap_or_else(|| panic!("tool '{}' not found", name));
2336            let ann = tool
2337                .annotations
2338                .as_ref()
2339                .expect("annotations must be present");
2340            assert_eq!(
2341                ann.read_only_hint,
2342                Some(true),
2343                "tool '{}' should have readOnlyHint=true",
2344                name
2345            );
2346        }
2347    }
2348
2349    #[test]
2350    fn test_destructive_tools_have_annotation() {
2351        let tools = get_tool_definitions();
2352        let destructive_names = [
2353            "memory_delete",
2354            "memory_cleanup_expired",
2355            "embedding_cache_clear",
2356        ];
2357        for name in destructive_names {
2358            let tool = tools
2359                .iter()
2360                .find(|t| t.name == name)
2361                .unwrap_or_else(|| panic!("tool '{}' not found", name));
2362            let ann = tool
2363                .annotations
2364                .as_ref()
2365                .expect("annotations must be present");
2366            assert_eq!(
2367                ann.destructive_hint,
2368                Some(true),
2369                "tool '{}' should have destructiveHint=true",
2370                name
2371            );
2372        }
2373    }
2374
2375    #[test]
2376    fn test_idempotent_tools_have_annotation() {
2377        let tools = get_tool_definitions();
2378        let idempotent_names = [
2379            "memory_extract_entities",
2380            "memory_rebuild_embeddings",
2381            "memory_rebuild_crossrefs",
2382            "lifecycle_run",
2383            "retention_policy_apply",
2384        ];
2385        for name in idempotent_names {
2386            let tool = tools
2387                .iter()
2388                .find(|t| t.name == name)
2389                .unwrap_or_else(|| panic!("tool '{}' not found", name));
2390            let ann = tool
2391                .annotations
2392                .as_ref()
2393                .expect("annotations must be present");
2394            assert_eq!(
2395                ann.idempotent_hint,
2396                Some(true),
2397                "tool '{}' should have idempotentHint=true",
2398                name
2399            );
2400        }
2401    }
2402
2403    #[test]
2404    fn test_annotations_serialize_with_camel_case_keys() {
2405        let tools = get_tool_definitions();
2406        let memory_get = tools.iter().find(|t| t.name == "memory_get").unwrap();
2407        let json = serde_json::to_string(memory_get).expect("serialization must succeed");
2408        assert!(
2409            json.contains("readOnlyHint"),
2410            "should serialize as readOnlyHint"
2411        );
2412        assert!(
2413            !json.contains("read_only_hint"),
2414            "must not use snake_case key"
2415        );
2416    }
2417
2418    #[test]
2419    fn test_mutating_tool_has_no_hints() {
2420        let tools = get_tool_definitions();
2421        let memory_create = tools.iter().find(|t| t.name == "memory_create").unwrap();
2422        let ann = memory_create
2423            .annotations
2424            .as_ref()
2425            .expect("annotations must be present");
2426        assert!(ann.read_only_hint.is_none());
2427        assert!(ann.destructive_hint.is_none());
2428        assert!(ann.idempotent_hint.is_none());
2429        // Serialized form should omit None fields
2430        let json = serde_json::to_string(ann).expect("serialization must succeed");
2431        // mutating annotations serialize as empty object since all fields are None
2432        assert_eq!(json, "{}");
2433    }
2434}