Module: PWN::Memory
- Defined in:
- lib/pwn/memory.rb
Overview
PWN::Memory provides persistent cross-session memory for the pwn-ai agent. Facts, user preferences, environment details, lessons learned, and task state are stored in ~/.pwn/memory.json and survive across REPL restarts / pwn-ai sessions.
The pwn-ai agent (in agent mode) automatically receives relevant memory injected into its system prompt. The agent can also call remember/recall via ruby code blocks during execution loops.
Constant Summary collapse
- MEMORY_FILE =
File.join(Dir.home, '.pwn', 'memory.json')
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.clear ⇒ Object
- Supported Method Parameters
PWN::Memory.clear.
-
.forget(opts = {}) ⇒ Object
rubocop:disable Naming/PredicateMethod.
-
.help ⇒ Object
Display Usage for this Module.
-
.load ⇒ Object
- Supported Method Parameters
memory = PWN::Memory.load.
-
.recall(opts = {}) ⇒ Object
- Supported Method Parameters
results = PWN::Memory.recall( query: 'optional - string to search keys/values/categories (simple match)', category: 'optional - filter by category', limit: 'optional - max results (default 50)' ).
-
.recall_semantic(opts = {}) ⇒ Object
- Supported Method Parameters
hits = PWN::Memory.recall_semantic(query: 'nmap sweep', limit: 6).
-
.remember(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Memory.remember( key: 'required - Symbol or String key for the memory fact', value: 'required - The value (any JSON serializable)', category: 'optional - e.g. :fact, :preference, :lesson, :env (default: :fact)', source: 'optional - :human | :reflect | :heuristic | :resolve | :consolidate (M3 provenance)', confidence: 'optional - 0.0..1.0 how sure the writer was (M3)', importance: 'optional - 0.0..1.0 retrieval/eviction weight (M2/M3)', ttl: 'optional - seconds until stale (M3; consolidate evicts stale low-conf first)' ).
-
.save(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Memory.save(mem: memory_hash).
-
.to_context(opts = {}) ⇒ Object
- Supported Method Parameters
context = PWN::Memory.to_context(limit: 20) (used internally by pwn-ai hook to inject into system prompt).
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
156 157 158 |
# File 'lib/pwn/memory.rb', line 156 public_class_method def self. "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n" end |
.clear ⇒ Object
- Supported Method Parameters
PWN::Memory.clear
134 135 136 137 |
# File 'lib/pwn/memory.rb', line 134 public_class_method def self.clear FileUtils.rm_f(MEMORY_FILE) {} end |
.forget(opts = {}) ⇒ Object
rubocop:disable Naming/PredicateMethod
124 125 126 127 128 129 130 |
# File 'lib/pwn/memory.rb', line 124 public_class_method def self.forget(opts = {}) # rubocop:disable Naming/PredicateMethod key = opts[:key] mem = load mem.delete(key.to_sym) save(mem: mem) true end |
.help ⇒ Object
Display Usage for this Module
161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/pwn/memory.rb', line 161 public_class_method def self.help puts <<~USAGE USAGE: mem = PWN::Memory.load PWN::Memory.remember(key: :user_prefers_ruby, value: 'Always prefer pure Ruby + RestClient patterns', category: :preference) facts = PWN::Memory.recall(query: 'recon', category: :fact, limit: 10) hits = PWN::Memory.recall_semantic(query: 'recon', limit: 6) # embedding-ranked PWN::Memory.forget(key: :some_key) PWN::Memory.clear context_str = PWN::Memory.to_context #{self}.authors USAGE end |
.load ⇒ Object
- Supported Method Parameters
memory = PWN::Memory.load
21 22 23 24 25 26 27 28 |
# File 'lib/pwn/memory.rb', line 21 public_class_method def self.load FileUtils.mkdir_p(File.dirname(MEMORY_FILE)) return {} unless File.exist?(MEMORY_FILE) JSON.parse(File.read(MEMORY_FILE), symbolize_names: true) rescue StandardError {} end |
.recall(opts = {}) ⇒ Object
- Supported Method Parameters
results = PWN::Memory.recall( query: 'optional - string to search keys/values/categories (simple match)', category: 'optional - filter by category', limit: 'optional - max results (default 50)' )
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/pwn/memory.rb', line 92 public_class_method def self.recall(opts = {}) query = opts[:query].to_s.downcase category = opts[:category] limit = opts[:limit] || 50 mem = load results = mem.select do |k, v| match = true match &&= k.to_s.downcase.include?(query) || v[:value].to_s.downcase.include?(query) || v[:category].to_s.downcase.include?(query) if query && !query.empty? match &&= (v[:category] == category.to_sym) if category match end results.to_a.first(limit).to_h end |
.recall_semantic(opts = {}) ⇒ Object
- Supported Method Parameters
hits = PWN::Memory.recall_semantic(query: 'nmap sweep', limit: 6)
Relevance-ranked recall via PWN::MemoryIndex (local Ollama embeddings
- cosine over ~/.pwn/memory.idx). Falls back to substring .recall when no embedding backend is configured.
114 115 116 117 118 119 120 |
# File 'lib/pwn/memory.rb', line 114 public_class_method def self.recall_semantic(opts = {}) return recall(query: opts[:query], limit: opts[:limit]) unless defined?(PWN::MemoryIndex) && PWN::MemoryIndex.available? PWN::MemoryIndex.recall_semantic(query: opts[:query], limit: opts[:limit]) rescue StandardError recall(query: opts[:query], limit: opts[:limit]) end |
.remember(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Memory.remember( key: 'required - Symbol or String key for the memory fact', value: 'required - The value (any JSON serializable)', category: 'optional - e.g. :fact, :preference, :lesson, :env (default: :fact)', source: 'optional - :human | :reflect | :heuristic | :resolve | :consolidate (M3 provenance)', confidence: 'optional - 0.0..1.0 how sure the writer was (M3)', importance: 'optional - 0.0..1.0 retrieval/eviction weight (M2/M3)', ttl: 'optional - seconds until stale (M3; consolidate evicts stale low-conf first)' )
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/pwn/memory.rb', line 61 public_class_method def self.remember(opts = {}) key = opts[:key] value = opts[:value] category = opts[:category] || :fact raise 'ERROR: key and value are required' if key.nil? || value.nil? mem = load entry = { value: value, category: category.to_sym, timestamp: Time.now.utc.iso8601, # M3 — provenance & scoring so Learning.consolidate evicts by # (age/ttl)/(importance×confidence) instead of oldest-first, and # MemoryIndex.recall_semantic ranks by sim × recency × importance. source: (opts[:source] || 'pwn-ai').to_s, confidence: opts[:confidence]&.to_f&.clamp(0.0, 1.0), importance: opts[:importance]&.to_f&.clamp(0.0, 1.0), ttl: opts[:ttl]&.to_i }.compact mem[key.to_sym] = entry save(mem: mem) mem[key.to_sym] end |
.save(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Memory.save(mem: memory_hash)
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/pwn/memory.rb', line 32 public_class_method def self.save(opts = {}) mem = opts[:mem] ||= {} FileUtils.mkdir_p(File.dirname(MEMORY_FILE)) # 4.4 — flock + atomic rename (nightly practice × interactive) path = MEMORY_FILE tmp = File.join(File.dirname(path), ".#{File.basename(path)}.#{Process.pid}.tmp") body = JSON.pretty_generate(mem) File.open(tmp, File::WRONLY | File::CREAT | File::TRUNC, 0o644) do |f| f.flock(File::LOCK_EX) f.write(body) f.flush f.fsync end File.rename(tmp, path) mem ensure FileUtils.rm_f(tmp) if defined?(tmp) && tmp && File.exist?(tmp) end |
.to_context(opts = {}) ⇒ Object
- Supported Method Parameters
context = PWN::Memory.to_context(limit: 20) (used internally by pwn-ai hook to inject into system prompt)
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/pwn/memory.rb', line 142 public_class_method def self.to_context(opts = {}) limit = opts[:limit] || 20 mem = recall(limit: limit) return '' if mem.empty? ctx = "\n\nPERSISTENT MEMORY (cross-session facts, prefs, lessons - use PWN::Memory.remember to store new ones):\n" mem.each do |k, v| ctx += "- #{k} [#{v[:category]} @ #{v[:timestamp]}]: #{v[:value].to_s[0, 300]}\n" end ctx end |