Class: Assistant::BrainCompactor

Inherits:
Object
  • Object
show all
Defined in:
app/services/assistant/brain_compactor.rb

Overview

Audits the active Sunny brain rules and recommends how to shrink the set
without losing meaning: consolidate overlapping rules, tighten verbose ones,
archive redundant / obsolete / contradictory ones.

One bounded LLM call; returns recommendations only — a sunny_admin applies
them (one-click archive for the safe case, manual review for merges/rewrites).

Examples:

result = Assistant::BrainCompactor.call
result.success?         # => true
result.recommendations  # => [#<Recommendation kind="consolidate" ...>, ...]

Defined Under Namespace

Classes: Recommendation, Result

Constant Summary collapse

PREFERRED_MODELS =

Sonnet first for reliable JSON + good rule-rewriting; Haiku as the cheaper
fallback. Ids come from the central registry so they track current snapshots.

[
  AiModelConstants.id(:anthropic_sonnet),
  AiModelConstants.id(:anthropic_haiku)
].freeze
MAX_RULES =

Cap the prompt: analyse at most this many rules (by_category order) so the
request stays bounded even as the brain grows.

120
KINDS =
%w[consolidate tighten archive].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scope = nil) ⇒ BrainCompactor

Returns a new instance of BrainCompactor.



46
47
48
# File 'app/services/assistant/brain_compactor.rb', line 46

def initialize(scope = nil)
  @scope = scope || AssistantBrainEntry.active.global_scope
end

Class Method Details

.call(scope = nil) ⇒ Object

Parameters:

  • scope (ActiveRecord::Relation, nil) (defaults to: nil)

    rules to analyse (default: active global rules)



42
43
44
# File 'app/services/assistant/brain_compactor.rb', line 42

def self.call(scope = nil)
  new(scope).call
end

Instance Method Details

#callResult

Returns:



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'app/services/assistant/brain_compactor.rb', line 51

def call
  entries = @scope.by_category.to_a.first(MAX_RULES)
  return Result.new(error: 'No active rules to analyze yet.') if entries.empty?

  model = select_model
  return Result.new(error: 'No LLM model is available for compaction right now.') unless model

  raw  = RubyLLM.chat(model: model).with_temperature(0).ask(build_prompt(entries)).content.to_s
  recs = parse_recommendations(raw, entries)

  Result.new(recommendations: recs, analyzed_count: entries.size, model_id: model)
rescue RubyLLM::Error => e
  Rails.logger.warn "[BrainCompactor] LLM error: #{e.message}"
  Result.new(error: "The compaction model errored: #{e.message}")
rescue StandardError => e
  Rails.logger.warn "[BrainCompactor] Failed: #{e.message}"
  Result.new(error: "Compaction failed: #{e.message}")
end