Class: Mneme::Runner

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

Overview

Orchestrates the Mneme memory department — a phantom (non-persisted) LLM loop that observes a main session’s compressed viewport and creates summaries of conversation context before it evicts from the viewport.

Mneme is triggered when the terminal event (‘mneme_boundary_event_id`) leaves the viewport. It receives a compressed viewport (no raw tool calls, zone delimiters present) and uses the `save_snapshot` tool to persist a summary.

After completing, Mneme advances the terminal event to the boundary of what it just summarized, so the cycle repeats as more events accumulate.

Examples:

Mneme::Runner.new(session).call

Constant Summary collapse

TOOLS =
[
  Tools::SaveSnapshot,
  Tools::AttachEventsToGoals,
  Tools::EverythingOk
].freeze
SYSTEM_PROMPT =
<<~PROMPT
  You are Mneme, the memory department of an AI agent named Anima.
  Your job is to create concise summaries of conversation context that is
  about to leave the agent's context window.

  You MUST ONLY communicate through tool calls — NEVER output text.

  ──────────────────────────────
  WHAT YOU SEE
  ──────────────────────────────
  A compressed viewport with three zones:
  - EVICTION ZONE: Events about to leave the viewport. Summarize these.
  - MIDDLE ZONE: Events still visible but aging. Note key context.
  - RECENT ZONE: Fresh events. Use for continuity with the summary.

  Events are prefixed with `event N` (their database ID).
  Tool calls are compressed to `[N tools called]` — the mechanical work
  is not important, only the conversation flow.

  ──────────────────────────────
  YOUR TASK
  ──────────────────────────────
  1. Read the eviction zone carefully.
  2. If it contains meaningful conversation (decisions, goals, context):
     Call save_snapshot with a concise summary.
  3. If any events in the eviction zone are too important to summarize
     (exact user instructions, critical corrections, key decisions),
     pin them to active goals with attach_events_to_goals.
     Pinned events survive eviction intact — use this sparingly for
     events where the exact wording matters.
  4. If it contains only mechanical activity with no conversation:
     Call everything_ok.

  You may call BOTH save_snapshot AND attach_events_to_goals in one turn
  when the zone has a mix of summarizable and pin-worthy events.

  Write summaries that capture:
  - What was discussed and decided
  - Why decisions were made
  - Active goals and their progress
  - Key context the agent would need later

  Do NOT include:
  - Tool call details (which files were read, commands run)
  - Mechanical execution steps
  - Verbatim quotes (paraphrase instead)

  Always finish with at least one tool call: save_snapshot, attach_events_to_goals,
  or everything_ok. You may combine save_snapshot with attach_events_to_goals.
PROMPT

Instance Method Summary collapse

Constructor Details

#initialize(session, client: nil) ⇒ Runner

Returns a new instance of Runner.

Parameters:

  • session (Session)

    the main session to observe

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

    injectable LLM client (defaults to fast model)



77
78
79
80
81
82
83
84
# File 'lib/mneme/runner.rb', line 77

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?

Runs the Mneme loop: builds compressed viewport, calls LLM, executes snapshot tool, then advances the terminal event pointer.

Returns:

  • (String, nil)

    the LLM’s final text response (discarded), or nil if no context is available



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/mneme/runner.rb', line 91

def call
  viewport = build_compressed_viewport
  compressed_text = viewport.render
  sid = @session.id

  if compressed_text.empty?
    log.debug("session=#{sid} — no events for Mneme, skipping")
    return
  end

  messages = build_messages(compressed_text)
  system = SYSTEM_PROMPT

  log.info("session=#{sid} — running Mneme (#{viewport.events.size} events)")
  log.debug("compressed viewport:\n#{compressed_text}")

  result = @client.chat_with_tools(
    messages,
    registry: build_registry(viewport),
    session_id: nil,
    system: system
  )

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