Class: SwarmSDK::V3::Memory::ContextBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/swarm_sdk/v3/memory/context_builder.rb

Overview

Assembles working context from memory tiers

Combines retrieved memory cards, recent turns (STM buffer), cluster summaries, active constraints, and an exploration sample into a coherent working context for the LLM.

Includes an “exploration sprinkle” — 1-2 low-exposure cards that are loosely relevant to the query. This prevents permanent forgetting of rarely-accessed memories by giving them occasional exposure.

## Associative Memory

When ‘associative_memory: true`, exploration cards are labeled under a distinct “YOU ALSO REMEMBER:” section, and a brief guidance is appended after the memory context encouraging the LLM to naturally bring up these tangential memories when the conversation allows — like a person who says “btw, how was Porto?” after discussing Portugal.

When ‘associative_memory: false` (default), exploration cards are formatted identically to retrieved cards. They still serve their anti-forgetting purpose (exposure bumping via `record_access!`), but the LLM is not encouraged to surface them conversationally.

Examples:

builder = ContextBuilder.new(retriever: retriever, adapter: adapter)
context = builder.build(query: "How does auth work?", recent_turns: messages)

Instance Method Summary collapse

Constructor Details

#initialize(retriever:, adapter:, retrieval_top_k: 15, embedder: nil, associative_memory: false) ⇒ ContextBuilder

Returns a new instance of ContextBuilder.

Parameters:

  • retriever (Retriever)

    Hybrid search retriever

  • adapter (Adapters::Base)

    Storage adapter

  • retrieval_top_k (Integer) (defaults to: 15)

    Cards to retrieve per query

  • embedder (Embedder, nil) (defaults to: nil)

    Embedder for exploration similarity

  • associative_memory (Boolean) (defaults to: false)

    Whether to label exploration cards distinctly



38
39
40
41
42
43
44
45
# File 'lib/swarm_sdk/v3/memory/context_builder.rb', line 38

def initialize(retriever:, adapter:, retrieval_top_k: 15, embedder: nil, associative_memory: false)
  @retriever = retriever
  @adapter = adapter
  @retrieval_top_k = retrieval_top_k
  @embedder = embedder
  @associative_memory = associative_memory
  @config = Configuration.instance
end

Instance Method Details

#build(query:, recent_turns: [], system_prompt: nil, read_only: false) ⇒ Array<Hash>

Build working context for a query

Examples:

messages = builder.build(
  query: "What auth system are we using?",
  recent_turns: last_8_turns,
  system_prompt: "You are a backend developer.",
)

Read-only mode (for subtasks)

messages = builder.build(
  query: "Check auth approach",
  read_only: true,
)

Parameters:

  • query (String)

    Current user query

  • recent_turns (Array<Hash>) (defaults to: [])

    Recent conversation turns (STM)

  • system_prompt (String, nil) (defaults to: nil)

    Agent’s system prompt

  • read_only (Boolean) (defaults to: false)

    When true, skips recording access on retrieved cards

Returns:

  • (Array<Hash>)

    Messages array ready for LLM



67
68
69
70
71
72
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/swarm_sdk/v3/memory/context_builder.rb', line 67

def build(query:, recent_turns: [], system_prompt: nil, read_only: false)
  DebugLog.log("context_builder", "build: query=#{query[0..60].inspect}")

  retrieved_cards = DebugLog.time("context_builder", "retriever.search(top_k=#{@retrieval_top_k})") do
    @retriever.search(query, top_k: @retrieval_top_k)
  end

  exploration_cards = DebugLog.time("context_builder", "find_exploration_cards") do
    find_exploration_cards(query, retrieved_cards)
  end
  all_cards = retrieved_cards + exploration_cards

  relevant_clusters = find_relevant_clusters(all_cards)
  active_constraints = find_active_constraints(all_cards)

  all_cards = DebugLog.time("context_builder", "deduplicate_cards(#{all_cards.size})") do
    deduplicate_cards(all_cards)
  end

  DebugLog.log("context_builder", "retrieved=#{retrieved_cards.size} exploration=#{exploration_cards.size} deduped=#{all_cards.size} clusters=#{relevant_clusters.size} constraints=#{active_constraints.size}")

  # Record access on all cards included in context (skip in read-only mode)
  unless read_only
    all_cards.each do |card|
      card.record_access!
      @adapter.write_card(card)
    end
  end

  messages = []

  # Build memory context from all tiers
  memory_context = format_memory_context(
    retrieved_cards, exploration_cards, relevant_clusters, active_constraints
  )

  # System prompt with memory context
  if system_prompt
    system_content = system_prompt
    system_content = "#{system_content}\n\n#{memory_context}" unless memory_context.empty?
    messages << { role: "system", content: system_content }
  elsif !memory_context.empty?
    messages << { role: "system", content: memory_context }
  end

  # Recent turns (STM buffer)
  messages.concat(recent_turns)

  messages
end