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 prefer a direct Ollama /api/embed endpoint (PWN::Env[:ollama][:embed_model], default 'nomic-embed-text'). When only Open WebUI is configured, embeddings go through its /ollama/api/embed proxy with the openwebui key/base_uri.
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
277 278 279 |
# File 'lib/pwn/memory_index.rb', line 277 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 or Open WebUI base_uri is configured. All public methods degrade to substring recall when this is false.
31 32 33 34 35 |
# File 'lib/pwn/memory_index.rb', line 31 public_class_method def self.available? !.nil? rescue StandardError false end |
.embed(opts = {}) ⇒ Object
- Supported Method Parameters
vecs = PWN::MemoryIndex.embed(texts: ['a', 'b'])
POST /api/embed on direct Ollama, or /ollama/api/embed via Open WebUI.
Returns Array<Array
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/pwn/memory_index.rb', line 145 public_class_method def self.(opts = {}) texts = Array(opts[:texts]).map(&:to_s) ep = return Array.new(texts.length) unless ep && !texts.empty? model = ep[:model] browser = PWN::Plugins::TransparentBrowser.open(browser_type: :rest) rest = browser[:browser]::Request headers = { content_type: 'application/json; charset=UTF-8' } token = ep[:token] headers[:authorization] = "Bearer #{token}" if token && !token.to_s.empty? out = [] texts.each_slice(32) do |batch| resp = rest.execute( method: :post, url: ep[:url], 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
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/pwn/memory_index.rb', line 283 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 (prefer direct Ollama; Open WebUI is the fallback proxy): PWN::Env[:ai][:ollama][:base_uri] = 'http://127.0.0.1:11434' PWN::Env[:ai][:ollama][:embed_model] = '<embed-model-tag>' # or: PWN::Env[:ai][:openwebui][:base_uri] = 'https://openwebui.local' PWN::Env[:ai][:openwebui][:key] = '<jwt>' PWN::Env[:ai][:openwebui][:embed_model] = '<embed-model-tag>' #{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.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/pwn/memory_index.rb', line 47 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) now = Time.now.utc # M2 — Generative-Agents retrieval: score = α·sim + β·recency + γ·importance # (Park '23). pwn had only sim; recency and importance now let a # 3-day-old human :fact outrank a 1-hour-old heuristic :lesson. scored = mem.map do |k, v| vec = idx.dig(k, :vec) next unless vec sim = cosine(a: qv, b: vec) age_h = (now - Time.parse(v[:timestamp].to_s)) / 3_600.0 rec = Math.exp(-age_h / (24.0 * 7.0)) imp = (v[:importance] || 0.5).to_f score = (0.6 * sim) + (0.25 * rec) + (0.15 * imp) { key: k, value: v[:value], category: v[:category], timestamp: v[:timestamp], score: score, sim: sim.round(3), importance: imp } rescue StandardError nil 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).
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/pwn/memory_index.rb', line 118 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
178 179 180 181 |
# File 'lib/pwn/memory_index.rb', line 178 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.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/pwn/memory_index.rb', line 86 public_class_method def self.to_context(opts = {}) limit = opts[:limit] || 6 # Fetch extra when filtering hygiene SOPs so we still fill the budget. fetch_n = opts[:drop_hygiene_sops] ? [limit * 3, 18].max : limit hits = recall_semantic(query: opts[:query], limit: fetch_n) return PWN::Memory.to_context(limit: limit) if hits.empty? if opts[:drop_hygiene_sops] hits = hits.reject do |h| k = h[:key].to_s v = h[:value].to_s k.match?(/process_sop_.*(?:rubocop|rake|docs_after|code_hygiene)|operator_pref_docs_after_rubocop/i) || v.match?(/\b(?:bundle exec )?rubocop\b.*\brake\b|docs_after_rubocop_rake|Documentation after code changes/i) end.first(limit) return PWN::Memory.to_context(limit: limit) if hits.empty? else hits = hits.first(limit) end 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 |