Skip to content

Instantly share code, notes, and snippets.

@sixlive
Created November 13, 2025 14:56
Show Gist options
  • Select an option

  • Save sixlive/3eeb52d6a64888ebd0a0f5cfd8e925b7 to your computer and use it in GitHub Desktop.

Select an option

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
<?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();
}
}
<?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}\"";
}
}
<?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();
}
}
<?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();
}
}
<?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();
}
}
<?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).')' : '';
}
}
<?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).')' : '';
}
}
<?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