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().
When to use it
Section titled “When to use it”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?”.
Why this and not the alternative
Section titled “Why this and not the alternative”It does not exclude anything by default. To hide the current conversation, give it a store that filters it out.
Examples
Section titled “Examples”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")2. The realistic case — give a persona its own transcript search
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 moreOptions
Section titled “Options”| 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. |
See also
Section titled “See also”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.