Class: Insika::Commands::BackfillKnowledge

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/commands/backfill_knowledge.rb

Overview

The recovery path (extraction is best-effort — a crash between a turn's terminal and the write loses that turn's concepts, not the conversation nor a previously learned one). Replays one agent's stored sessions through the SAME Knowledge::Extractor the per-turn hook uses.

Synchronous — the CLI's own path (insika knowledge:backfill), same "no app boot" discipline as RunHarvest/RunDistillation: no task, no turn, nothing written but the concepts themselves.

Constant Summary collapse

DEFAULT_MIN_MESSAGES =
2

Instance Method Summary collapse

Constructor Details

#initialize(profiles:, knowledge_store:, session_store:, task_store:, settings_store: nil, event_stream:, extractor_factory: nil, consolidator_factory: nil) ⇒ BackfillKnowledge

Returns a new instance of BackfillKnowledge.



18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/insika/commands/backfill_knowledge.rb', line 18

def initialize(profiles:, knowledge_store:, session_store:, task_store:,
               settings_store: nil, event_stream:, extractor_factory: nil, consolidator_factory: nil)
  @profiles = ProfileSource.coerce(profiles)
  @knowledge_store = knowledge_store
  @session_store = session_store
  @task_store = task_store
  @settings_store = settings_store
  @extractor_factory = extractor_factory ||
                       ->(config) { Knowledge::ExtractorFactory.build(config, utility_model: utility_model) }
  @consolidator_factory = consolidator_factory ||
                          ->(config) { Knowledge::ConsolidatorFactory.build(config, utility_model: utility_model) }
  @event_stream = event_stream
end

Instance Method Details

#call(command) ⇒ Object

payload: { agent:, since?: ISO8601 } -> { backfilled: true, sessions: N, concepts: N, conflicts: N, dropped: ... } | { backfilled: false, skipped: "disabled|no_model|no_sessions" }



35
36
37
38
39
40
41
42
43
44
45
46
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
# File 'lib/insika/commands/backfill_knowledge.rb', line 35

def call(command)
  p = AgentPayload.symbolize(command.payload)
  agent = AgentPayload.presence(p[:agent])
  raise Insika::ValidationError, "agent is required" if agent.nil?

  profile = @profiles[agent] || (raise Insika::NotFoundError, "agent '#{agent}' not configured")
  config = Coercion.deep_stringify(profile.knowledge)
  return skip("disabled") if config.nil? || !Coercion.truthy?(config["extract"])

  extractor = @extractor_factory.call(config)
  return skip("no_model") if extractor.nil?

  # Same resolved model as the extractor. nil (unresolvable) is still
  # meaningful: Knowledge.write_concept's conservative default.
  consolidator = @consolidator_factory.call(config)

  sessions = resolve_sessions(agent, p)
  return skip("no_sessions") if sessions.empty?

  concepts = 0
  conflicts = 0
  dropped = Hash.new(0)
  sessions.each do |s|
    result = extractor.extract(prompt: build_prompt(config, s[:messages]))
    result[:concepts].each do |c|
      case write_concept(profile, s[:id], c, consolidator)[:verdict]
      when :new, :related then concepts += 1
      when :contradicting then conflicts += 1
      end
    end
    result[:dropped].each { |k, v| dropped[k] += v }
  end

  emit(:knowledge_backfilled, agent: agent, sessions: sessions.size, concepts: concepts,
                              conflicts: conflicts, dropped: dropped.to_h)
  { backfilled: true, sessions: sessions.size, concepts: concepts, conflicts: conflicts, dropped: dropped.to_h }
end