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

Class Method Details

.authorsObject

Author(s)

0day Inc. support@0dayinc.com



144
145
146
# File 'lib/pwn/memory.rb', line 144

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <support@0dayinc.com>\n"
end

.clearObject

Supported Method Parameters

PWN::Memory.clear



122
123
124
125
# File 'lib/pwn/memory.rb', line 122

public_class_method def self.clear
  FileUtils.rm_f(MEMORY_FILE)
  {}
end

.forget(opts = {}) ⇒ Object

rubocop:disable Naming/PredicateMethod



112
113
114
115
116
117
118
# File 'lib/pwn/memory.rb', line 112

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

.helpObject

Display Usage for this Module



149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/pwn/memory.rb', line 149

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

.loadObject

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)' )



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/pwn/memory.rb', line 80

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.


102
103
104
105
106
107
108
# File 'lib/pwn/memory.rb', line 102

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)' )



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/pwn/memory.rb', line 49

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
# File 'lib/pwn/memory.rb', line 32

public_class_method def self.save(opts = {})
  mem = opts[:mem] ||= {}
  FileUtils.mkdir_p(File.dirname(MEMORY_FILE))
  File.write(MEMORY_FILE, JSON.pretty_generate(mem))
  mem
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)



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/pwn/memory.rb', line 130

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