Class: Mneme::L2Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/mneme/l2_runner.rb

Overview

Compresses multiple Level 1 snapshots into a single Level 2 snapshot. L2 snapshots capture days/weeks-scale context from hourly L1 summaries, preventing unbounded snapshot growth via recursive compression.

Triggered from MnemeJob after an L1 snapshot is created, when enough uncovered L1 snapshots have accumulated (configurable via mneme.l2_snapshot_threshold in config.toml).

Examples:

Mneme::L2Runner.new(session).call

Constant Summary collapse

TOOLS =
[
  Tools::SaveSnapshot,
  Tools::EverythingOk
].freeze
SYSTEM_PROMPT =
<<~PROMPT
  You are Mneme, the muse of memory. When enough of your own Level 1 snapshots accumulate, you fold them into a single Level 2 summary — a memory of memories — so the long arc of Aoide's work stays within reach without carrying every detail.

  Act only through tool calls. Never output text — your contribution is the summary you leave behind.

  ──────────────────────────────
  WHAT YOU SEE
  ──────────────────────────────
  Several Level 1 snapshots in chronological order. Each captures the decisions, goal progress, and context from a slice of Aoide's history.

  ──────────────────────────────
  HOW TO REMEMBER
  ──────────────────────────────
  Compress the slice into ONE Level 2 summary that captures the arc across all of them. Call save_snapshot when there's meaningful content; call everything_ok when the slice is purely mechanical.

  A Level 2 summary is carried for longer than a Level 1, so the tax on Aoide's viewport is higher still. Every redundant detail you preserve costs her a word she can't spend on the present.

  Keep:
  - Key decisions and the reasoning behind them
  - Goal progress across the time span
  - Important context shifts or pivots
  - Relationships and patterns that span multiple snapshots

  Drop:
  - Details repeated across snapshots
  - Mechanical execution steps
  - Interim decisions that were superseded later

  Finish with exactly one tool call: save_snapshot or everything_ok.
PROMPT

Instance Method Summary collapse

Constructor Details

#initialize(session, client: nil) ⇒ L2Runner

Returns a new instance of L2Runner.

Parameters:

  • session (Session)

    the main session whose L1 snapshots to compress

  • client (LLM::Client, nil) (defaults to: nil)

    injectable LLM client (defaults to fast model)



53
54
55
56
57
58
59
60
# File 'lib/mneme/l2_runner.rb', line 53

def initialize(session, client: nil)
  @session = session
  @client = client || LLM::Client.new(
    model: Anima::Settings.fast_model,
    max_tokens: Anima::Settings.mneme_max_tokens,
    logger: Mneme.logger
  )
end

Instance Method Details

#callString?

Compresses uncovered L1 snapshots into a single L2 snapshot. Returns early if not enough L1 snapshots have accumulated.

Returns:

  • (String, nil)

    LLM response text, or nil when skipped



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/mneme/l2_runner.rb', line 66

def call
  l1_snapshots = eligible_snapshots
  threshold = Anima::Settings.mneme_l2_snapshot_threshold
  sid = @session.id
  snapshot_count = l1_snapshots.size

  if snapshot_count < threshold
    log.debug("session=#{sid} — only #{snapshot_count}/#{threshold} L1 snapshots, skipping L2")
    return
  end

  messages = build_messages(l1_snapshots)
  registry = build_registry(l1_snapshots)

  log.info("session=#{sid} — running L2 compression (#{snapshot_count} L1 snapshots)")

  result = @client.chat_with_tools(
    messages,
    registry: registry,
    system: SYSTEM_PROMPT
  )

  log.info("session=#{sid} — L2 compression done: #{result.to_s.truncate(200)}")
  result
end