/ apps / echo_shared / scripts / memory_viewer.exs
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()