Class: Pikuri::Memory::Extension
- Inherits:
-
Object
- Object
- Pikuri::Memory::Extension
- Includes:
- Agent::Extension
- Defined in:
- lib/pikuri/memory/extension.rb
Overview
The host-facing API: wire durable cross-conversation memory onto
a Agent via c.add_extension inside the Agent.new
block — same opt-in shape as pikuri-tasks / pikuri-vectordb.
Usage
client = Pikuri::Memory::Mem0Client.new(endpoint: 'http://localhost:8888')
Pikuri::Agent.new(transport: ..., system_prompt: ...) do |c|
c.add_extension Pikuri::Memory::Extension.new(
client: client, user_id: 'martin'
)
end
What it wires (the three retrieval tiers)
- configure registers the
recalltool (Recall). - system_prompt_snippets contributes the always-in-prompt
persona summary (tier 1) when
resident_persona:is on — re-pulled (refreshed from the store) on every Agent#clear_conversation. - on_user_message does tier 2 (automatic prefetch) and the
asynchronous capture: enqueue the turn for off-path extraction,
then return a
<memory-context>slice the Agent injects as a:systemmessage. - bind starts the capture worker and arms its bounded flush on agent close.
Read sync, write async: prefetch is on-path (a vector search is milliseconds), capture is off-path through Recorder (extraction is a ~3s LLM call). Recalled context is +:system+-role and only the user's own words are captured — the two halves of the feedback-loop defense (DESIGN.md §"Retrieval").
Automatic capture + recall are safe only on a no-untrusted-ingest,
no-egress agent (the @private configuration; Pikuri::Memory
namespace header). Sub-agents don't inherit extensions, so a
delegated persona's turns are never prefetched or captured.
Constant Summary collapse
- LOGGER =
Pikuri.logger_for('Memory::Extension')
- DEFAULT_PREFETCH_K =
Returns default prefetch slice size — small and high-precision, since junk recall degrades behavior and the
recalltool means a small slice is a pointer, not a loss (DESIGN.md §"Automatic ≠ always-inject"). 5- DEFAULT_RESIDENT_LIMIT =
Returns default cap on the resident-persona summary — a few facts, not the whole store (the first Mem0Client#get_all rows).
20
Instance Attribute Summary collapse
-
#recorder ⇒ Recorder
readonly
The capture queue, exposed for tests and for a host that wants to flush it explicitly.
Instance Method Summary collapse
-
#bind(ctx) ⇒ void
Start the capture worker and arm its bounded flush on agent close.
-
#configure(c) ⇒ void
Register the
recalltool and (optionally) append the resident persona summary. - #initialize(client:, user_id:, prefetch_k: DEFAULT_PREFETCH_K, threshold: nil, infer: true, extraction_prompt: nil, resident_persona: true, flush_timeout: Recorder::DEFAULT_FLUSH_TIMEOUT, resident_limit: DEFAULT_RESIDENT_LIMIT) ⇒ Extension constructor
-
#on_user_message(_ctx, content) ⇒ String?
Per-turn hook.
-
#system_prompt_snippets ⇒ Array<String>
The resident-persona summary, freshly fetched from the store.
Constructor Details
#initialize(client:, user_id:, prefetch_k: DEFAULT_PREFETCH_K, threshold: nil, infer: true, extraction_prompt: nil, resident_persona: true, flush_timeout: Recorder::DEFAULT_FLUSH_TIMEOUT, resident_limit: DEFAULT_RESIDENT_LIMIT) ⇒ Extension
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/pikuri/memory/extension.rb', line 85 def initialize(client:, user_id:, prefetch_k: DEFAULT_PREFETCH_K, threshold: nil, infer: true, extraction_prompt: nil, resident_persona: true, flush_timeout: Recorder::DEFAULT_FLUSH_TIMEOUT, resident_limit: DEFAULT_RESIDENT_LIMIT) raise ArgumentError, 'user_id must be non-empty' if user_id.nil? || user_id.to_s.empty? @client = client @user_id = user_id @prefetch_k = prefetch_k @threshold = threshold @resident_persona = resident_persona @resident_limit = resident_limit @extraction_prompt = extraction_prompt @recorder = Recorder.new( client: client, user_id: user_id, infer: infer, prompt: @extraction_prompt, flush_timeout: flush_timeout ) end |
Instance Attribute Details
#recorder ⇒ Recorder (readonly)
Returns the capture queue, exposed for tests and for a host that wants to flush it explicitly.
107 108 109 |
# File 'lib/pikuri/memory/extension.rb', line 107 def recorder @recorder end |
Instance Method Details
#bind(ctx) ⇒ void
This method returns an undefined value.
Start the capture worker and arm its bounded flush on agent close. Keyed to this specific agent via Agent::ExtensionContext#on_close (not Configurator#on_close) so the lifetime tracks the live agent.
150 151 152 153 154 |
# File 'lib/pikuri/memory/extension.rb', line 150 def bind(ctx) @recorder.start ctx.on_close { @recorder.close } nil end |
#configure(c) ⇒ void
This method returns an undefined value.
Register the recall tool and (optionally) append the resident
persona summary. Raises if recall was pre-registered — the
extension is its sole owner and a duplicate would bind to a
different client / namespace.
116 117 118 119 120 121 122 123 124 125 |
# File 'lib/pikuri/memory/extension.rb', line 116 def configure(c) if c.tools.any? { |t| t.name == 'recall' } raise 'recall cannot be pre-registered (in tools: or via c.add_tool) when adding ' \ 'Pikuri::Memory::Extension — the extension owns the recall tool so it shares ' \ 'the same mem0 client / user_id.' end c.add_tool Recall.new(client: @client, user_id: @user_id) nil end |
#on_user_message(_ctx, content) ⇒ String?
Per-turn hook. Enqueues the user's words for asynchronous
capture, then returns the automatic prefetch slice (or nil to
inject nothing this turn). Capture happens regardless of whether
prefetch finds anything.
166 167 168 169 |
# File 'lib/pikuri/memory/extension.rb', line 166 def (_ctx, content) @recorder.enqueue(content) prefetch(content) end |
#system_prompt_snippets ⇒ Array<String>
The resident-persona summary, freshly fetched from the store.
Because the Agent re-pulls this on every clear, the persona
refreshes across a "/clear" — facts captured during the
cleared conversation show up, instead of staying frozen at
construction. Returns [] when the resident persona is off, the
store is empty, or (via #resident_persona_snippet's own rescue)
the store is unreachable this pass.
136 137 138 139 140 141 |
# File 'lib/pikuri/memory/extension.rb', line 136 def system_prompt_snippets return [] unless @resident_persona snippet = resident_persona_snippet snippet ? [snippet] : [] end |