Module: PWN::MemoryIndex
- Defined in:
- lib/pwn/memory_index.rb
Overview
PWN::MemoryIndex is a lightweight local embedding index over PWN::Memory (~/.pwn/memory.json) so PromptBuilder can inject the N MOST-RELEVANT memories for the current request instead of the N newest. Embeddings come from the local Ollama instance (PWN::Env[:ollama][:embed_model], default 'nomic-embed-text') via its native /api/embed endpoint — everything stays on-box.
Index layout (~/.pwn/memory.idx):
{ "<key>": { "sha": "<sha16 of value>", "vec": [Float,…] }, … }
Rebuilds are incremental: only entries whose value-sha changed are (re)embedded, so a warm index costs one embed call (the query).
Constant Summary collapse
- INDEX_FILE =
File.join(Dir.home, '.pwn', 'memory.idx')
- DEFAULT_EMBED_MODEL =
'nomic-embed-text'
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.available? ⇒ Boolean
- Supported Method Parameters
bool = PWN::MemoryIndex.available?.
-
.embed(opts = {}) ⇒ Object
- Supported Method Parameters
vecs = PWN::MemoryIndex.embed(texts: ['a', 'b']).
-
.help ⇒ Object
Display Usage for this Module.
-
.recall_semantic(opts = {}) ⇒ Object
- Supported Method Parameters
hits = PWN::MemoryIndex.recall_semantic( query: 'required - user request / search text', limit: 'optional - top-K by cosine similarity (default 6)' ).
-
.refresh(opts = {}) ⇒ Object
- Supported Method Parameters
idx = PWN::MemoryIndex.refresh(mem: 'optional - preloaded PWN::Memory hash').
-
.reset ⇒ Object
- Supported Method Parameters
PWN::MemoryIndex.reset.
-
.to_context(opts = {}) ⇒ Object
- Supported Method Parameters
ctx = PWN::MemoryIndex.to_context(query:, limit: 6).
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
210 211 212 |
# File 'lib/pwn/memory_index.rb', line 210 public_class_method def self. "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n" end |
.available? ⇒ Boolean
- Supported Method Parameters
bool = PWN::MemoryIndex.available?
True when a local Ollama base_uri is configured. All public methods degrade to substring recall when this is false.
30 31 32 33 34 |
# File 'lib/pwn/memory_index.rb', line 30 public_class_method def self.available? !ollama_base.nil? rescue StandardError false end |
.embed(opts = {}) ⇒ Object
- Supported Method Parameters
vecs = PWN::MemoryIndex.embed(texts: ['a', 'b'])
POST /api/embed on the local Ollama; returns Array<Array
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/pwn/memory_index.rb', line 117 public_class_method def self.(opts = {}) texts = Array(opts[:texts]).map(&:to_s) base = ollama_base return Array.new(texts.length) unless base && !texts.empty? model = (PWN::Env.dig(:ai, :ollama, :embed_model) if defined?(PWN::Env)) || DEFAULT_EMBED_MODEL browser = PWN::Plugins::TransparentBrowser.open(browser_type: :rest) rest = browser[:browser]::Request headers = { content_type: 'application/json; charset=UTF-8' } token = PWN::Env.dig(:ai, :ollama, :key) if defined?(PWN::Env) headers[:authorization] = "Bearer #{token}" if token out = [] texts.each_slice(32) do |batch| resp = rest.execute( method: :post, url: "#{base}/ollama/api/embed", headers: headers, payload: { model: model, input: batch }.to_json, verify_ssl: false, timeout: 120 ) j = JSON.parse(resp, symbolize_names: true) out.concat(Array(j[:embeddings])) end out rescue StandardError Array.new(texts.length) end |
.help ⇒ Object
Display Usage for this Module
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/pwn/memory_index.rb', line 216 public_class_method def self.help puts <<~USAGE USAGE: PWN::MemoryIndex.available? PWN::MemoryIndex.recall_semantic(query: 'nmap sweep', limit: 6) PWN::MemoryIndex.to_context(query: 'nmap sweep', limit: 6) # PromptBuilder drop-in PWN::MemoryIndex.refresh # incremental (re)embed PWN::MemoryIndex.embed(texts: ['a', 'b']) # raw vectors PWN::MemoryIndex.reset Config: PWN::Env[:ai][:ollama][:embed_model] = 'nomic-embed-text' # or bge-m3, mxbai-embed-large #{self}.authors USAGE end |
.recall_semantic(opts = {}) ⇒ Object
- Supported Method Parameters
hits = PWN::MemoryIndex.recall_semantic( query: 'required - user request / search text', limit: 'optional - top-K by cosine similarity (default 6)' )
Returns [{ key:, value:, category:, timestamp:, score: }, …] (newest-first Memory.recall shape + :score) or falls back to PWN::Memory.recall(query:) when embedding is unavailable.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/pwn/memory_index.rb', line 46 public_class_method def self.recall_semantic(opts = {}) query = opts[:query].to_s limit = (opts[:limit] || 6).to_i mem = PWN::Memory.load return [] if mem.empty? || query.strip.empty? qv = (texts: [query]).first return fallback(query: query, limit: limit) unless qv idx = refresh(mem: mem) scored = mem.map do |k, v| vec = idx.dig(k, :vec) next unless vec { key: k, value: v[:value], category: v[:category], timestamp: v[:timestamp], score: cosine(a: qv, b: vec) } end scored.compact.sort_by { |h| -h[:score] }.first(limit) rescue StandardError fallback(query: query, limit: limit) end |
.refresh(opts = {}) ⇒ Object
- Supported Method Parameters
idx = PWN::MemoryIndex.refresh(mem: 'optional - preloaded PWN::Memory hash')
Incrementally (re)embed changed entries and prune deleted keys. Returns the in-memory index Hash keyed by memory key (Symbol).
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/pwn/memory_index.rb', line 91 public_class_method def self.refresh(opts = {}) mem = opts[:mem] || PWN::Memory.load idx = load_index idx.delete_if { |k, _| !mem.key?(k) } todo = mem.reject { |k, v| idx.dig(k, :sha) == sha(text: v[:value].to_s) } unless todo.empty? vecs = (texts: todo.values.map { |v| "#{v[:category]}: #{v[:value]}" }) todo.keys.each_with_index do |k, i| next unless vecs[i] idx[k] = { sha: sha(text: mem[k][:value].to_s), vec: vecs[i] } end save_index(idx: idx) end idx rescue StandardError idx || {} end |
.reset ⇒ Object
- Supported Method Parameters
PWN::MemoryIndex.reset
150 151 152 153 |
# File 'lib/pwn/memory_index.rb', line 150 public_class_method def self.reset FileUtils.rm_f(INDEX_FILE) {} end |
.to_context(opts = {}) ⇒ Object
- Supported Method Parameters
ctx = PWN::MemoryIndex.to_context(query:, limit: 6)
Drop-in replacement for PWN::Memory.to_context that ranks by
relevance to query instead of insertion order. Emitted format is
identical so PromptBuilder needs no special-casing.
74 75 76 77 78 79 80 81 82 83 |
# File 'lib/pwn/memory_index.rb', line 74 public_class_method def self.to_context(opts = {}) hits = recall_semantic(query: opts[:query], limit: opts[:limit] || 6) return PWN::Memory.to_context(limit: opts[:limit] || 6) if hits.empty? ctx = "\n\nPERSISTENT MEMORY (relevance-ranked for this request - use PWN::Memory.remember to store new ones):\n" hits.each do |h| ctx += "- #{h[:key]} [#{h[:category]} @ #{h[:timestamp]}]: #{h[:value].to_s[0, 300]}\n" end ctx end |