Skip to content

Toolnexus.TranscriptSearch.tool

Elixir · package toolnexus · SPEC §7E · elixir/lib/toolnexus/transcript_search.ex

@spec tool(term(), keyword() | map()) :: Toolnexus.Tool.t()
def tool(store, opts \\ []) # opts: :max_results (20), :snippet_chars (200), :name ("transcript_search")

Toolnexus.TranscriptSearch.tool turns a conversation store into a plain, read-only tool named transcript_search. The model calls it with {query, conversationId?, limit?}. It does a case-insensitive substring match over the text of every message: plain content, text parts, tool-call arguments (as JSON) and tool-result text. Conversations come in the order the store’s list() returns them (newest first), and messages in index order.

The output is one line per hit, <conversationId>#<index> <role>: <snippet>. The snippet is centred on the first match, snippetChars characters long (characters, not bytes), with … marking cut text. No hits gives no matches. Hits past the limit add a final (+<k> more). An empty query is an error. The tool never writes. Construction fails if the store has no list().

Use it wherever an agent needs to check what actually happened rather than what it remembered: the dreamer uses it to verify memory, and a persona can use it to answer “what did I tell you about X last month?”.

It does not exclude anything by default. To hide the current conversation, give it a store that filters it out.

1. The smallest useful call — search everything

Section titled “1. The smallest useful call — search everything”
alias Toolnexus.{Context, TranscriptSearch}
alias Toolnexus.Client.FileConversationStore
store = FileConversationStore.new("./data/conversations")
search = TranscriptSearch.tool(store)
res = search.execute.(%{"query" => "mt. moon"}, %Context{})
IO.puts(res.output)
# conv-42#3 user: …we finally got through Mt. Moon after the Zubat swarm…
# (or "no matches")
Section titled “2. The realistic case — give a persona its own transcript search”
store = FileConversationStore.new("./data/conversations")
# Let the persona itself look up what was actually said, not just what it remembered.
ava = Home.from_dir("./personas/ava", tools: [TranscriptSearch.tool(store)])

3. The full surface — options and the narrower inputs

Section titled “3. The full surface — options and the narrower inputs”
search =
TranscriptSearch.tool(store,
max_results: 5, # ceiling for the "limit" input
snippet_chars: 80, # code points, not bytes
name: "past_chats"
)
res = search.execute.(%{"query" => "invoice", "conversationId" => "conv-42", "limit" => 3}, %Context{})
IO.puts(res.output) # at most 3 lines, then "(+<k> more)" if there were more
Option Default What it does
maxResults 20 Most hits per call, and the ceiling for the limit input.
snippetChars 200 Snippet length in characters (code points), not bytes.
name transcript_search The tool name the model sees.

Input fields the model fills in:

Field Type What it does
query string Required, non-empty. Case-insensitive substring.
conversationId string Search only this conversation.
limit integer Fewer hits than maxResults.
  • dreamer — the built-in agent that verifies memory with this tool.
  • Dreaming guide — why transcripts are the ground truth for memory.
  • memoryTool — what the transcripts are checked against.