Class: Pikuri::Memory::Recall

Inherits:
Tool
  • Object
show all
Defined in:
lib/pikuri/memory/recall.rb

Overview

The recall Tool: explicit, topic-driven deepening beyond the automatic per-turn prefetch (Extension#on_user_message) — the model calls it when the prefetch slice hints there is more on a topic. The dig half of the same two-step agentic-RAG shape as +pikuri-vectordb+'s +vectordb_search+/+vectordb_read+.

recall(topic:) is a deliberately minimal surface — no top_k, no user_id (retrieval depth is host policy in Extension, the namespace is fixed at construction; the model shouldn't tune retrieval mid-conversation).

mem0 ranks by similarity, not recency, and keeps a stale fact alongside its correction — so the observation carries each memory's created_at and the description tells the model to treat newer-about-the-same-thing as current. Resolution lives in the model's reasoning, not the store (DESIGN.md §"Supersede recall: resolution is the consumer's job").

Sharing: P_stateless in pikuri's own state — the topic is an argument and the namespace is fixed at construction. Two agents sharing a user_id should see each other's memories: durable cross-conversation recall is the gem's whole point. The caveat sits one level down — Mem0Client memoizes a Faraday connection and concurrent requests through one are untested here, so give each agent its own client if you fan out; they still share the store.

Constant Summary collapse

LOGGER =
Pikuri.logger_for('Memory::Recall')
TOP_K =

Returns memories returned per recall. A handful — enough to surface a topic's facts plus any correction, few enough to stay cheap in the turn.

Returns:

  • (Integer)

    memories returned per recall. A handful — enough to surface a topic's facts plus any correction, few enough to stay cheap in the turn.

7
DESCRIPTION =

Returns static description shown to the LLM, opencode-shape (summary + Usage: bullets).

Returns:

  • (String)

    static description shown to the LLM, opencode-shape (summary + Usage: bullets).

<<~DESC
  Search your durable memory of the user for facts relevant to a topic.

  Usage:
  - Use to recall what you know about the user or their work beyond what was automatically surfaced this turn — preferences, ongoing projects, people, decisions.
  - Phrase `topic` as a natural-language statement or question, e.g. "the user's current main project" or "how the user likes test output".
  - Each result carries a timestamp. Memory is append-only: if two results conflict, the more recent one is current truth — the older one is kept as history, not a contradiction to flag.
  - Returns up to #{TOP_K} memories. If nothing relevant comes back, say you don't have a memory of it rather than guessing.
DESC

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client:, user_id:) ⇒ Recall

Parameters:

  • client (Mem0Client)

    the mem0 client recall queries go to.

  • user_id (String)

    the fixed mem0 namespace to search.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/pikuri/memory/recall.rb', line 53

def initialize(client:, user_id:)
  super(
    name: 'recall',
    description: DESCRIPTION,
    parameters: Pikuri::Tool::Parameters.build { |p|
      p.required_string :topic,
                        'Natural-language topic or question to recall about the user, e.g. ' \
                        '"the user\'s dietary preferences" or "what project are we working on?".'
    },
    execute: lambda { |topic:|
      Recall.execute(client: client, user_id: user_id, topic: topic)
    },
    # Private by domain, like a mailbox: durable memories about the user are
    # sensitive by what they are, not by how anything was configured.
    #
    # Hard untrusted too, which is the less obvious half. Memories are captured
    # from earlier turns, so anything an injection got the agent to believe
    # *last week* comes back as trusted-looking recall today — a stored
    # injection with a delay fuse.
    trifecta_legs: Pikuri::Tool::TrifectaLegs.new(private: true, untrusted: :hard, egress_payload_review: :no_egress)
  )
end

Class Method Details

.execute(client:, user_id:, topic:) ⇒ String

Search and format the observation. Catches Mem0Client failures and renders them as an "Error: ..." observation the LLM can react to (a transient mem0 blip shouldn't crash the loop); bugs in pikuri's own code still raise.

Parameters:

  • client (Mem0Client)
  • user_id (String)
  • topic (String)

Returns:

  • (String)

    formatted observation.



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/pikuri/memory/recall.rb', line 85

def self.execute(client:, user_id:, topic:)
  return 'Error: topic is empty' if topic.nil? || topic.strip.empty?

  records = client.search(query: topic, user_id: user_id, top_k: TOP_K)
  return 'No relevant memories found.' if records.empty?

  format_observation(records)
rescue RuntimeError => e
  LOGGER.warn("recall failed: #{e.message}")
  "Error: memory recall failed: #{e.message}"
end