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 message (‘mneme_boundary_message_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 message to the boundary of what it just summarized, so the cycle repeats as more messages accumulate.

Examples:

Mneme::Runner.new(session).call

Constant Summary collapse

TOOLS =
[
  Tools::SaveSnapshot,
  Tools::AttachMessagesToGoals,
  Tools::EverythingOk
].freeze
SYSTEM_PROMPT =
<<~PROMPT
  You are Mneme, the memory department of an AI agent named Anima.
  The agent's context is a conveyor belt — events flow through and eventually fall off.
  Remember what matters. Let the rest go.
  Communicate only through tool calls — never output text.

  ──────────────────────────────
  VIEWPORT
  ──────────────────────────────
  Three zones, oldest to newest:
  - EVICTION ZONE: About to fall off — read carefully, this is your focus.
  - MIDDLE ZONE: Aging but visible. Note context that connects to evicting events.
  - RECENT ZONE: Fresh. Use for continuity with your summary.

  Messages are prefixed with `message N` (database ID, used for pinning).
  Tool calls are compressed to `[N tools called]` — focus on conversation, not mechanical work.

  ──────────────────────────────
  ACTIONS
  ──────────────────────────────
  Summarize evicting conversation with save_snapshot — capture what was discussed and decided,
  why decisions were made, active goal progress, and context the agent will need later.
  Paraphrase — don't quote verbatim. Omit tool call details and mechanical steps.

  Pin critical messages to goals with attach_messages_to_goals when exact wording matters
  (user instructions, key corrections, key decisions). Pinned messages survive eviction
  intact — use this sparingly for messages where paraphrasing would lose meaning.

  If the eviction zone contains only mechanical activity, call everything_ok.

  You may combine save_snapshot and attach_messages_to_goals in one turn.
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)



59
60
61
62
63
64
65
66
# File 'lib/mneme/runner.rb', line 59

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 message pointer.

Returns:

  • (String, nil)

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



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/mneme/runner.rb', line 73

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

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

  llm_messages = build_messages(compressed_text)
  system = SYSTEM_PROMPT

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

  result = @client.chat_with_tools(
    llm_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