Created
November 13, 2025 14:56
-
-
Save sixlive/3eeb52d6a64888ebd0a0f5cfd8e925b7 to your computer and use it in GitHub Desktop.
Iris Memory Tools - A complete set of MCP tools for semantic memory management
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace App\Tools\Memory; | |
| use App\Models\Memory; | |
| use Prism\Prism\Tool; | |
| class CategorizeMemoriesTool extends Tool | |
| { | |
| public function __construct() | |
| { | |
| $this | |
| ->as('categorize_memories') | |
| ->for('Browse all memories in specific category, sorted by importance. Use when exploring topic area or user asks "what do you know about my work?"') | |
| ->withStringParameter( | |
| 'category', | |
| 'Category: personal|professional|hobbies|health|relationships|preferences|goals' | |
| ) | |
| ->withNumberParameter( | |
| 'minImportance', | |
| 'Min importance 0.0-1.0. Use 0.7 for critical only, 0.5 standard, 0.0 all. Default: 0.5', | |
| required: false | |
| ) | |
| ->using($this); | |
| } | |
| public function __invoke(string $category, float $minImportance = 0.5): string | |
| { | |
| $memories = Memory::getByCategory($category, $minImportance); | |
| if ($memories->isEmpty()) { | |
| return "No memories found in category '{$category}' with importance >= {$minImportance}"; | |
| } | |
| return view('tools.memory.category-list', [ | |
| 'category' => $category, | |
| 'minImportance' => $minImportance, | |
| 'memories' => $this->formatMemoriesForList($memories), | |
| ])->render(); | |
| } | |
| /** | |
| * @param \Illuminate\Support\Collection<int, Memory> $memories | |
| * @return array<int, array<string, mixed>> | |
| */ | |
| protected function formatMemoriesForList($memories): array | |
| { | |
| return $memories->map(fn (Memory $m) => [ | |
| 'id' => $m->id, | |
| 'importance' => number_format((float) $m->importance, 1), | |
| 'content' => $m->content, | |
| 'type' => $m->memory_type, | |
| 'tags' => $m->tags ? implode(', ', $m->tags) : null, | |
| ])->toArray(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace App\Tools\Memory; | |
| use App\Models\Memory; | |
| use Prism\Prism\Tool; | |
| class DeleteMemoryTool extends Tool | |
| { | |
| public function __construct() | |
| { | |
| $this | |
| ->as('delete_memory') | |
| ->for('Permanently remove incorrect, outdated, or unwanted memory. Use when user asks to forget something or info is demonstrably wrong. Cannot be undone.') | |
| ->withNumberParameter( | |
| 'memoryId', | |
| 'Memory ID from list_recent_memories or search results. Double-check before deletion.' | |
| ) | |
| ->using($this); | |
| } | |
| public function __invoke(int $memoryId): string | |
| { | |
| $memory = Memory::find($memoryId); | |
| if (! $memory) { | |
| return "Error: Memory ID {$memoryId} not found. Use list_recent_memories or search_memory to find the correct ID."; | |
| } | |
| $content = $memory->content; | |
| $memory->delete(); | |
| return "✓ Memory deleted successfully: \"{$content}\""; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace App\Tools\Memory; | |
| use App\Models\Memory; | |
| use Prism\Prism\Tool; | |
| class GetImportantMemoriesTool extends Tool | |
| { | |
| public function __construct() | |
| { | |
| $this | |
| ->as('get_important_memories') | |
| ->for('Retrieve most IMPORTANT memories (high-priority, frequently-accessed). Use when starting conversations to refresh key context.') | |
| ->withNumberParameter( | |
| 'limit', | |
| 'How many to retrieve (1-20). Default: 10. Sorted by importance and access frequency.', | |
| required: false | |
| ) | |
| ->using($this); | |
| } | |
| public function __invoke(int $limit = 10): string | |
| { | |
| $memories = Memory::getImportantMemories(min(max($limit, 1), 20)); | |
| if ($memories->isEmpty()) { | |
| return 'No high-importance memories (importance >= 0.7) found yet.'; | |
| } | |
| return view('tools.memory.important-list', [ | |
| 'memories' => $this->formatMemoriesForList($memories), | |
| ])->render(); | |
| } | |
| /** | |
| * @param \Illuminate\Support\Collection<int, Memory> $memories | |
| * @return array<int, array<string, mixed>> | |
| */ | |
| protected function formatMemoriesForList($memories): array | |
| { | |
| return $memories->map(fn (Memory $m, int $index) => [ | |
| 'index' => $index + 1, | |
| 'id' => $m->id, | |
| 'importance' => number_format((float) $m->importance, 1), | |
| 'accessCount' => $m->access_count, | |
| 'content' => $m->content, | |
| 'type' => $m->memory_type, | |
| 'category' => $m->category, | |
| ])->toArray(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace App\Tools\Memory; | |
| use App\Models\Memory; | |
| use Prism\Prism\Tool; | |
| class GetMemoryStatsTool extends Tool | |
| { | |
| public function __construct() | |
| { | |
| $this | |
| ->as('get_memory_stats') | |
| ->for('Overview of memory system: total stored, breakdown by source/type, recent activity. Use when user asks "how much do you know about me?"') | |
| ->using($this); | |
| } | |
| public function __invoke(): string | |
| { | |
| $total = Memory::count(); | |
| if ($total === 0) { | |
| return 'No memories stored yet. Your memory is empty.'; | |
| } | |
| $bySource = Memory::query() | |
| ->selectRaw('source, count(*) as count') | |
| ->groupBy('source') | |
| ->pluck('count', 'source') | |
| ->toArray(); | |
| $byType = Memory::query() | |
| ->selectRaw('memory_type, count(*) as count') | |
| ->groupBy('memory_type') | |
| ->pluck('count', 'memory_type') | |
| ->toArray(); | |
| $oldest = Memory::oldest()->first(); | |
| $newest = Memory::latest()->first(); | |
| return view('tools.memory.stats', [ | |
| 'total' => $total, | |
| 'bySource' => $bySource, | |
| 'byType' => $byType, | |
| 'oldestAge' => $oldest?->created_at->diffForHumans(), | |
| 'newestAge' => $newest?->created_at->diffForHumans(), | |
| ])->render(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace App\Tools\Memory; | |
| use App\Models\Memory; | |
| use Prism\Prism\Tool; | |
| class ListRecentMemoriesTool extends Tool | |
| { | |
| public function __construct() | |
| { | |
| $this | |
| ->as('list_recent_memories') | |
| ->for('Browse recent memories chronologically. Use when starting conversations to refresh context or when user asks "what do you remember?"') | |
| ->withNumberParameter( | |
| 'limit', | |
| 'How many to retrieve (1-20). Use 10 for overview, 20 for comprehensive. Default: 10', | |
| required: false | |
| ) | |
| ->using($this); | |
| } | |
| public function __invoke(int $limit = 10): string | |
| { | |
| $limit = min(max($limit, 1), 20); | |
| $memories = Memory::query() | |
| ->orderByDesc('created_at') | |
| ->limit($limit) | |
| ->get(); | |
| if ($memories->isEmpty()) { | |
| return 'No memories stored yet. Start building your memory by storing important information as you learn it.'; | |
| } | |
| return view('tools.memory.recent-list', [ | |
| 'memories' => $this->formatMemoriesForList($memories), | |
| ])->render(); | |
| } | |
| /** | |
| * @param \Illuminate\Support\Collection<int, Memory> $memories | |
| * @return array<int, array<string, mixed>> | |
| */ | |
| protected function formatMemoriesForList($memories): array | |
| { | |
| return $memories->map(fn (Memory $m, int $index) => [ | |
| 'index' => $index + 1, | |
| 'id' => $m->id, | |
| 'content' => $m->content, | |
| 'type' => $m->memory_type, | |
| 'importance' => number_format((float) $m->importance, 1), | |
| 'tags' => $m->tags ? implode(', ', $m->tags) : null, | |
| 'category' => $m->category, | |
| 'source' => $m->source, | |
| 'stored' => $m->created_at->diffForHumans(), | |
| ])->toArray(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace App\Tools\Memory; | |
| use App\Models\Memory; | |
| use Illuminate\Support\Collection; | |
| use Prism\Prism\Enums\Provider; | |
| use Prism\Prism\Facades\Prism; | |
| use Prism\Prism\Schema\StringSchema; | |
| use Prism\Prism\Tool; | |
| class SearchMemoryTool extends Tool | |
| { | |
| public function __construct() | |
| { | |
| $this | |
| ->as('search_memory') | |
| ->for('Search memory semantically BEFORE answering questions about user. Finds related concepts, not just keywords. Filter by type/importance/tags/category.') | |
| ->withStringParameter( | |
| 'searchQuery', | |
| 'Specific question/topic. GOOD: "user programming preferences and reasons" BAD: "user info". Semantic matching finds related memories.' | |
| ) | |
| ->withStringParameter( | |
| 'memoryType', | |
| 'Filter: fact|preference|goal|event|skill|relationship|habit|context. Empty = all. Optional.', | |
| required: false | |
| ) | |
| ->withNumberParameter( | |
| 'minImportance', | |
| 'Min importance 0.0-1.0. Use 0.7+ for critical only, 0.0 for all. Default: 0.0', | |
| required: false | |
| ) | |
| ->withArrayParameter( | |
| 'tags', | |
| 'Filter by tags: ["work","coding"] finds either. Optional.', | |
| new StringSchema('tag', 'A tag to filter by'), | |
| required: false | |
| ) | |
| ->withStringParameter( | |
| 'category', | |
| 'Filter: personal|professional|hobbies|health|relationships|preferences|goals. Empty = all. Optional.', | |
| required: false | |
| ) | |
| ->withNumberParameter( | |
| 'limit', | |
| 'Max results 1-20. Default: 5. Higher for comprehensive, lower for quick.', | |
| required: false | |
| ) | |
| ->using($this); | |
| } | |
| /** | |
| * @param array<int, string>|null $tags | |
| */ | |
| public function __invoke( | |
| string $searchQuery, | |
| ?string $memoryType = null, | |
| ?float $minImportance = 0.0, | |
| ?array $tags = null, | |
| ?string $category = null, | |
| ?int $limit = 5 | |
| ): string { | |
| $embeddingResponse = Prism::embeddings() | |
| ->using(Provider::Ollama, 'granite-embedding:30m') | |
| ->fromInput($searchQuery) | |
| ->asEmbeddings(); | |
| $memories = Memory::findSimilar( | |
| $embeddingResponse->embeddings[0]->embedding, | |
| threshold: 0.6, | |
| limit: min(max($limit ?? 5, 1), 20), | |
| memoryType: $memoryType, | |
| minImportance: $minImportance, | |
| tags: $tags, | |
| category: $category | |
| ); | |
| return view('tools.memory.search-results', [ | |
| 'isEmpty' => $memories->isEmpty(), | |
| 'memories' => $this->formatMemoriesForDisplay($memories), | |
| 'query' => $searchQuery, | |
| 'filterString' => $this->buildFilterString( | |
| $memoryType, | |
| $minImportance, | |
| $tags, | |
| $category | |
| ), | |
| ])->render(); | |
| } | |
| /** | |
| * @param Collection<int, Memory> $memories | |
| * @return array<int, array<string, mixed>> | |
| */ | |
| protected function formatMemoriesForDisplay(Collection $memories): array | |
| { | |
| return $memories->map(fn (Memory $m) => [ | |
| 'id' => $m->id, | |
| 'content' => $m->content, | |
| 'type' => $m->memory_type, | |
| 'importance' => number_format((float) $m->importance, 1), | |
| 'similarity' => number_format((float) ($m->similarity ?? 0), 2), | |
| 'tags' => $m->tags ? implode(', ', $m->tags) : null, | |
| 'category' => $m->category, | |
| 'stored' => $m->created_at->diffForHumans(), | |
| 'accessCount' => $m->access_count, | |
| ])->toArray(); | |
| } | |
| /** | |
| * @param array<int, string>|null $tags | |
| */ | |
| protected function buildFilterString( | |
| ?string $memoryType, | |
| ?float $minImportance, | |
| ?array $tags, | |
| ?string $category | |
| ): string { | |
| $filters = []; | |
| if ($memoryType) { | |
| $filters[] = "type={$memoryType}"; | |
| } | |
| if ($minImportance && $minImportance > 0) { | |
| $filters[] = sprintf('importance>=%.1f', $minImportance); | |
| } | |
| if ($tags) { | |
| $filters[] = 'tags='.implode(',', $tags); | |
| } | |
| if ($category) { | |
| $filters[] = "category={$category}"; | |
| } | |
| return count($filters) > 0 ? ' (filters: '.implode(', ', $filters).')' : ''; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace App\Tools\Memory; | |
| use App\Models\Memory; | |
| use Prism\Prism\Enums\Provider; | |
| use Prism\Prism\Facades\Prism; | |
| use Prism\Prism\Schema\StringSchema; | |
| use Prism\Prism\Tool; | |
| class StoreMemoryTool extends Tool | |
| { | |
| public function __construct() | |
| { | |
| $this | |
| ->as('store_memory') | |
| ->for('Store information IMMEDIATELY when learned. Auto-classifies by type (fact/preference/goal/event/skill/relationship/habit/context) and importance (0-1). Use NOW, not later.') | |
| ->withStringParameter( | |
| 'memoryContent', | |
| 'Complete, self-contained fact with context. GOOD: "User Sarah prefers TypeScript for type safety." BAD: "Likes TypeScript." Include WHO/WHAT/WHY.' | |
| ) | |
| ->withStringParameter( | |
| 'memoryType', | |
| 'Type: fact|preference|goal|event|skill|relationship|habit|context. Default: fact', | |
| required: false | |
| ) | |
| ->withNumberParameter( | |
| 'importance', | |
| 'Priority 0.0-1.0. Critical info: 0.9-1.0, Useful: 0.6-0.8, Nice-to-know: 0.3-0.5. Default: 0.5', | |
| required: false | |
| ) | |
| ->withArrayParameter( | |
| 'tags', | |
| 'Organization tags: ["work","family"], ["coding","preferences"]. Optional.', | |
| new StringSchema('tag', 'A tag for organization'), | |
| required: false | |
| ) | |
| ->withStringParameter( | |
| 'category', | |
| 'Broad category: personal|professional|hobbies|health|relationships|preferences|goals. Optional.', | |
| required: false | |
| ) | |
| ->withStringParameter( | |
| 'context', | |
| 'When/where/why learned: "During career discussion" or "Morning routine talk". Optional.', | |
| required: false | |
| ) | |
| ->using($this); | |
| } | |
| /** | |
| * @param array<int, string>|null $tags | |
| */ | |
| public function __invoke( | |
| string $memoryContent, | |
| ?string $memoryType = 'fact', | |
| ?float $importance = 0.5, | |
| ?array $tags = null, | |
| ?string $category = null, | |
| ?string $context = null | |
| ): string { | |
| $embeddingResponse = Prism::embeddings() | |
| ->using(Provider::Ollama, 'granite-embedding:30m') | |
| ->fromInput($memoryContent) | |
| ->asEmbeddings(); | |
| $memory = new Memory; | |
| $memory->content = $memoryContent; | |
| $memory->source = 'agent'; | |
| $memory->memory_type = $memoryType ?? 'fact'; | |
| $memory->importance = max(0, min(1, $importance ?? 0.5)); | |
| $memory->tags = $tags; | |
| $memory->category = $category; | |
| $memory->context = $context; | |
| $memory->metadata = [ | |
| 'stored_at' => now()->toIso8601String(), | |
| ]; | |
| $memory->setEmbedding($embeddingResponse->embeddings[0]->embedding); | |
| $memory->save(); | |
| return view('tools.memory.stored', [ | |
| 'memoryId' => $memory->id, | |
| 'details' => $this->buildDetailsString($memory), | |
| ])->render(); | |
| } | |
| protected function buildDetailsString(Memory $memory): string | |
| { | |
| $details = []; | |
| if ($memory->memory_type !== 'fact') { | |
| $details[] = "type: {$memory->memory_type}"; | |
| } | |
| if ($memory->importance != 0.5) { | |
| $details[] = sprintf('importance: %.1f', $memory->importance); | |
| } | |
| if ($memory->tags) { | |
| $details[] = 'tags: '.implode(', ', $memory->tags); | |
| } | |
| if ($memory->category) { | |
| $details[] = "category: {$memory->category}"; | |
| } | |
| return count($details) > 0 ? ' ('.implode(', ', $details).')' : ''; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace App\Tools\Memory; | |
| use App\Models\Memory; | |
| use Prism\Prism\Enums\Provider; | |
| use Prism\Prism\Facades\Prism; | |
| use Prism\Prism\Tool; | |
| class UpdateMemoryTool extends Tool | |
| { | |
| public function __construct() | |
| { | |
| $this | |
| ->as('update_memory') | |
| ->for('Update existing memory when information changes. Use when user corrects info, preferences change, or details need clarification.') | |
| ->withNumberParameter( | |
| 'memoryId', | |
| 'Memory ID from list_recent_memories or search results. Verify before updating.' | |
| ) | |
| ->withStringParameter( | |
| 'updatedContent', | |
| 'NEW complete content. REPLACES old entirely. GOOD: "User switched to Vue.js in March 2024 for performance." BAD: "Changed to Vue."' | |
| ) | |
| ->using($this); | |
| } | |
| public function __invoke(int $memoryId, string $updatedContent): string | |
| { | |
| $memory = Memory::find($memoryId); | |
| if (! $memory) { | |
| return "Error: Memory ID {$memoryId} not found. Use list_recent_memories or search_memory to find the correct ID."; | |
| } | |
| $oldContent = $memory->content; | |
| $embeddingResponse = Prism::embeddings() | |
| ->using(Provider::Ollama, 'granite-embedding:30m') | |
| ->fromInput($updatedContent) | |
| ->asEmbeddings(); | |
| $memory->content = $updatedContent; | |
| $memory->metadata = array_merge($memory->metadata ?? [], [ | |
| 'updated_at' => now()->toIso8601String(), | |
| 'previous_version' => $oldContent, | |
| ]); | |
| $memory->setEmbedding($embeddingResponse->embeddings[0]->embedding); | |
| $memory->save(); | |
| return view('tools.memory.updated', [ | |
| 'oldContent' => $oldContent, | |
| 'newContent' => $updatedContent, | |
| ])->render(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment