Class: Llmemory::SkillMining::Miner

Inherits:
Object
  • Object
show all
Defined in:
lib/llmemory/skill_mining/miner.rb

Overview

Skill mining scans an agent’s recent episodes (episodic memory) for repeated, successful trajectories and distills them into reusable skills (procedural memory). This is Voyager’s actual contribution: rather than a passive, hand-written skill library, procedural memory grows from lived experience.

Mining is human-in-the-loop by default: ‘mine` returns skill proposals and writes nothing. Pass `auto_register: true` to register them directly. Each registered skill carries provenance { method: “skill_mining”, sources: [{ type: “episode”, id: … }] } so it stays traceable to the experiences it was distilled from.

‘procedural` must respond to:

register_skill(name:, body:, description:, kind:, provenance:)

Constant Summary collapse

DEFAULT_WINDOW =
20
DEFAULT_CONFIDENCE =
0.5
VALID_KINDS =
%w[prompt template code].freeze

Instance Method Summary collapse

Constructor Details

#initialize(episodic:, procedural:, llm: nil) ⇒ Miner

Returns a new instance of Miner.



26
27
28
29
30
# File 'lib/llmemory/skill_mining/miner.rb', line 26

def initialize(episodic:, procedural:, llm: nil)
  @episodic = episodic
  @procedural = procedural
  @llm = llm || Llmemory::LLM.client
end

Instance Method Details

#mine(window: DEFAULT_WINDOW, outcomes: nil, auto_register: false) ⇒ Object

Mines the most recent ‘window` episodes for reusable skills. When `outcomes` (an allowlist of outcome labels) is given, only episodes whose outcome is in the set are considered — a deterministic pre-filter.

Returns an array of proposal hashes ({ name:, kind:, body:, description:, confidence: }). When ‘auto_register: true`, registers each proposal and returns the new skill ids instead.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/llmemory/skill_mining/miner.rb', line 40

def mine(window: DEFAULT_WINDOW, outcomes: nil, auto_register: false)
  result = []
  Llmemory::Instrumentation.instrument(:mine_skills, window: window, auto_register: auto_register) do
    episodes = @episodic.recent_episodes(limit: window)
    episodes = filter_by_outcome(episodes, outcomes) if outcomes
    next if episodes.empty?

    proposals = distill(episodes)
    next if proposals.empty?

    result = auto_register ? register(proposals, episodes) : proposals
  end
  result
end