memory_viewer.exs
1 #!/usr/bin/env elixir 2 3 # Agent Memory Viewer - View and manage ECHO organizational memory 4 # Usage: mix run shared/scripts/memory_viewer.exs [command] [args] 5 6 alias EchoShared.Repo 7 alias EchoShared.Schemas.Memory 8 import Ecto.Query 9 10 defmodule MemoryViewer do 11 @moduledoc """ 12 Utility for viewing and managing agent memories in the ECHO system. 13 Shows how agents running in parallel access shared organizational memory. 14 """ 15 16 def run(args \\ []) do 17 command = Enum.at(args, 0, "list") 18 19 case command do 20 "list" -> list_all_memories() 21 "count" -> count_memories() 22 "by-agent" -> list_by_agent() 23 "by-tag" -> list_by_tag(Enum.at(args, 1)) 24 "search" -> search_memories(Enum.at(args, 1)) 25 "add" -> add_memory(args) 26 "help" -> show_help() 27 _ -> 28 IO.puts("Unknown command: #{command}") 29 show_help() 30 end 31 end 32 33 def list_all_memories do 34 IO.puts("\n" <> IO.ANSI.blue() <> "═════════════════════════════════════════" <> IO.ANSI.reset()) 35 IO.puts(IO.ANSI.blue() <> " ECHO Organizational Memory" <> IO.ANSI.reset()) 36 IO.puts(IO.ANSI.blue() <> "═════════════════════════════════════════" <> IO.ANSI.reset() <> "\n") 37 38 memories = Repo.all(from m in Memory, order_by: [desc: m.inserted_at]) 39 40 if Enum.empty?(memories) do 41 IO.puts(IO.ANSI.yellow() <> "No memories found in the database." <> IO.ANSI.reset()) 42 IO.puts("\nTip: Add a memory with: mix run shared/scripts/memory_viewer.exs add") 43 else 44 Enum.each(memories, &display_memory/1) 45 IO.puts("\nTotal: #{length(memories)} memories") 46 end 47 end 48 49 def count_memories do 50 total = Repo.aggregate(Memory, :count) 51 52 by_agent = Repo.all( 53 from m in Memory, 54 group_by: m.created_by_role, 55 select: {m.created_by_role, count(m.id)} 56 ) 57 58 IO.puts("\n" <> IO.ANSI.blue() <> "Memory Statistics" <> IO.ANSI.reset()) 59 IO.puts("─────────────────────────────────────────") 60 IO.puts("Total Memories: #{total}") 61 IO.puts("\nBy Agent:") 62 63 Enum.each(by_agent, fn {role, count} -> 64 IO.puts(" #{role || "unknown"}: #{count}") 65 end) 66 end 67 68 def list_by_agent do 69 agents = Repo.all( 70 from m in Memory, 71 distinct: m.created_by_role, 72 select: m.created_by_role, 73 order_by: m.created_by_role 74 ) 75 76 IO.puts("\n" <> IO.ANSI.blue() <> "Memories by Agent" <> IO.ANSI.reset()) 77 IO.puts("─────────────────────────────────────────\n") 78 79 Enum.each(agents, fn agent -> 80 memories = Repo.all( 81 from m in Memory, 82 where: m.created_by_role == ^agent, 83 order_by: [desc: m.inserted_at] 84 ) 85 86 IO.puts(IO.ANSI.green() <> "#{agent || "Unknown"}" <> IO.ANSI.reset() <> " (#{length(memories)} memories)") 87 Enum.each(memories, fn memory -> 88 IO.puts(" • #{memory.key}") 89 end) 90 IO.puts("") 91 end) 92 end 93 94 def list_by_tag(tag) when is_binary(tag) do 95 memories = Repo.all( 96 from m in Memory, 97 where: ^tag in m.tags, 98 order_by: [desc: m.inserted_at] 99 ) 100 101 IO.puts("\n" <> IO.ANSI.blue() <> "Memories tagged: #{tag}" <> IO.ANSI.reset()) 102 IO.puts("─────────────────────────────────────────\n") 103 104 if Enum.empty?(memories) do 105 IO.puts(IO.ANSI.yellow() <> "No memories found with tag: #{tag}" <> IO.ANSI.reset()) 106 else 107 Enum.each(memories, &display_memory/1) 108 IO.puts("\nTotal: #{length(memories)} memories") 109 end 110 end 111 112 def list_by_tag(nil) do 113 IO.puts(IO.ANSI.red() <> "Error: Please provide a tag name" <> IO.ANSI.reset()) 114 IO.puts("Usage: mix run shared/scripts/memory_viewer.exs by-tag <tag_name>") 115 end 116 117 def search_memories(query) when is_binary(query) do 118 pattern = "%#{query}%" 119 120 memories = Repo.all( 121 from m in Memory, 122 where: ilike(m.content, ^pattern) or ilike(m.key, ^pattern), 123 order_by: [desc: m.inserted_at] 124 ) 125 126 IO.puts("\n" <> IO.ANSI.blue() <> "Search Results for: #{query}" <> IO.ANSI.reset()) 127 IO.puts("─────────────────────────────────────────\n") 128 129 if Enum.empty?(memories) do 130 IO.puts(IO.ANSI.yellow() <> "No memories found matching: #{query}" <> IO.ANSI.reset()) 131 else 132 Enum.each(memories, &display_memory/1) 133 IO.puts("\nTotal: #{length(memories)} results") 134 end 135 end 136 137 def search_memories(nil) do 138 IO.puts(IO.ANSI.red() <> "Error: Please provide a search query" <> IO.ANSI.reset()) 139 IO.puts("Usage: mix run shared/scripts/memory_viewer.exs search <query>") 140 end 141 142 def add_memory(args) do 143 IO.puts("\n" <> IO.ANSI.blue() <> "Add New Memory" <> IO.ANSI.reset()) 144 IO.puts("─────────────────────────────────────────\n") 145 146 key = get_input("Memory key (unique identifier): ", Enum.at(args, 1)) 147 content = get_input("Content: ", Enum.at(args, 2)) 148 tags_input = get_input("Tags (comma-separated): ", Enum.at(args, 3)) 149 agent = get_input("Created by agent role: ", Enum.at(args, 4)) 150 151 tags = String.split(tags_input, ",") |> Enum.map(&String.trim/1) 152 153 attrs = %{ 154 key: key, 155 content: content, 156 tags: tags, 157 created_by_role: agent, 158 metadata: %{} 159 } 160 161 case %Memory{} 162 |> Memory.changeset(attrs) 163 |> Repo.insert() do 164 {:ok, memory} -> 165 IO.puts("\n" <> IO.ANSI.green() <> "✓ Memory created successfully!" <> IO.ANSI.reset()) 166 display_memory(memory) 167 168 {:error, changeset} -> 169 IO.puts("\n" <> IO.ANSI.red() <> "✗ Failed to create memory:" <> IO.ANSI.reset()) 170 IO.inspect(changeset.errors) 171 end 172 end 173 174 defp display_memory(memory) do 175 IO.puts(IO.ANSI.cyan() <> "┌─ #{memory.key}" <> IO.ANSI.reset()) 176 IO.puts("│ Content: #{truncate(memory.content, 100)}") 177 IO.puts("│ Tags: #{inspect(memory.tags)}") 178 IO.puts("│ Created by: #{memory.created_by_role || "unknown"}") 179 IO.puts("│ Created at: #{memory.inserted_at}") 180 if memory.metadata && map_size(memory.metadata) > 0 do 181 IO.puts("│ Metadata: #{inspect(memory.metadata)}") 182 end 183 IO.puts("└" <> String.duplicate("─", 50) <> "\n") 184 end 185 186 defp truncate(text, length) do 187 if String.length(text) > length do 188 String.slice(text, 0, length) <> "..." 189 else 190 text 191 end 192 end 193 194 defp get_input(prompt, default \\ nil) do 195 if default do 196 default 197 else 198 IO.write(prompt) 199 IO.gets("") |> String.trim() 200 end 201 end 202 203 def show_help do 204 IO.puts("\n" <> IO.ANSI.blue() <> "ECHO Memory Viewer" <> IO.ANSI.reset()) 205 IO.puts("═════════════════════════════════════════\n") 206 IO.puts("View and manage shared organizational memory accessed by") 207 IO.puts("all agents running in parallel.\n") 208 IO.puts(IO.ANSI.green() <> "Commands:" <> IO.ANSI.reset()) 209 IO.puts(" list - List all memories") 210 IO.puts(" count - Show memory statistics") 211 IO.puts(" by-agent - List memories grouped by agent") 212 IO.puts(" by-tag <tag> - List memories with specific tag") 213 IO.puts(" search <query> - Search memories by content or key") 214 IO.puts(" add - Add a new memory interactively") 215 IO.puts(" help - Show this help message") 216 IO.puts("\n" <> IO.ANSI.green() <> "Examples:" <> IO.ANSI.reset()) 217 IO.puts(" mix run shared/scripts/memory_viewer.exs list") 218 IO.puts(" mix run shared/scripts/memory_viewer.exs by-tag strategy") 219 IO.puts(" mix run shared/scripts/memory_viewer.exs search 'product launch'") 220 IO.puts("") 221 end 222 end 223 224 # Run the viewer 225 System.argv() |> MemoryViewer.run()