1use serde_json::json;
4
5use super::protocol::{ToolAnnotations, ToolDefinition};
6
7pub struct ToolDef {
9 pub name: &'static str,
10 pub description: &'static str,
11 pub schema: &'static str,
12 pub annotations: ToolAnnotations,
13}
14
15pub const TOOL_DEFINITIONS: &[ToolDef] = &[
17 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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
2292pub 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 let json = serde_json::to_string(ann).expect("serialization must succeed");
2431 assert_eq!(json, "{}");
2433 }
2434}