Class: AnalyticalBrain::Runner

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

Overview

Orchestrates the analytical brain — a phantom (non-persisted) LLM loop that observes a main session and performs background maintenance via tools.

The analytical brain is a “subconscious” process: it operates ON the main session without the main agent knowing it exists. Tools mutate the main session directly (e.g. renaming it, activating skills), but no trace of the analytical brain’s reasoning is persisted.

Examples:

AnalyticalBrain::Runner.new(session).call

Constant Summary collapse

TOOLS =

Tools available to the analytical brain.

Returns:

[
  Tools::RenameSession,
  Tools::ActivateSkill,
  Tools::DeactivateSkill,
  Tools::ReadWorkflow,
  Tools::DeactivateWorkflow,
  Tools::SetGoal,
  Tools::UpdateGoal,
  Tools::FinishGoal,
  Tools::EverythingIsReady
].freeze
SYSTEM_PROMPT =
<<~PROMPT
  You are a background automation that manages session metadata.
  You MUST ONLY communicate through tool calls — NEVER output text.
  Always finish by calling everything_is_ready.

  ──────────────────────────────
  SESSION NAMING
  ──────────────────────────────
  Call rename_session when the topic becomes clear or shifts.
  Format: one emoji + 1-3 descriptive words.

  ──────────────────────────────
  SKILL MANAGEMENT
  ──────────────────────────────
  Call activate_skill when the conversation matches a skill's description.
  Call deactivate_skill when the agent moves to a different domain.
  Multiple skills can be active at once.

  ──────────────────────────────
  WORKFLOW MANAGEMENT
  ──────────────────────────────
  Call read_workflow when the user starts a multi-step task matching a workflow description.
  Read the returned content and use judgment to create appropriate goals — not a mechanical 1:1 mapping.
  Adapt to context: skip irrelevant steps, add extra steps for unfamiliar areas.
  Call deactivate_workflow when the workflow completes or the user shifts focus.
  Only one workflow can be active at a time — activating a new one replaces the previous.

  ──────────────────────────────
  GOAL TRACKING
  ──────────────────────────────
  Call set_goal to create a root goal when the user starts a multi-step task.
  Call set_goal with parent_goal_id to add sub-goals (TODO items) under it.
  Call update_goal to refine a goal's description as understanding evolves.
  Call finish_goal when the main agent completes work a goal describes.
  Finishing a root goal cascades — all active sub-goals are completed too.
  Never duplicate an existing goal — check the active goals list first.

  ──────────────────────────────
  COMPLETION
  ──────────────────────────────
  Call everything_is_ready as your LAST tool call, every time.
  If nothing needs changing, call it immediately as your only tool call.
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 and maintain

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

    injectable LLM client (defaults to fast model)



75
76
77
78
79
80
81
82
# File 'lib/analytical_brain/runner.rb', line 75

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

Instance Method Details

#callString?

Runs the analytical brain loop. Builds context from the main session’s recent events, calls the LLM with the analytical brain’s tool set, and executes any tool calls against the main session.

Events emitted during tool execution are not persisted — the phantom session_id (nil) causes the global Persister to skip them.

Returns:

  • (String, nil)

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



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

def call
  messages = build_messages
  sid = @session.id
  if messages.empty?
    log.debug("session=#{sid} — no events, skipping")
    return
  end

  system = build_system_prompt
  log.info("session=#{sid} — running (#{recent_events.size} events)")
  log.debug("system prompt:\n#{system}")
  log.debug("user message:\n#{messages.first[:content]}")

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

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