Brainby arc-labs/docs
Guide

Building context for prompts

Brain has no build-context verb — you assemble a prompt block yourself from recall output. This shows the pattern, with token budgeting and provenance.

Prerequisites
  • A Brain client with credentials.
  • Memories already in the system for the active scope.
  • A target LLM that accepts a system prompt.
  • Familiarity with Recall.
What you'll build

A buildContext helper that recalls memories for a cue, formats them into a Markdown block sized to a token budget, and returns both the block and its source memory ids for provenance.

There is no context verb

Older drafts referenced a context.build() call and named "recipes". Neither exists. Brain's read surface is exactly one verb — recall — which returns a membership verdict over memories (see Recall). Assembling those memories into a prompt is application work, and it belongs in your code because only you know your prompt layout, model, and token budget.

The pattern is always the same: recall → format → inject.

Step-by-step

import { BrainHttpClient } from "@brain-db/sdk";

const brain = new BrainHttpClient({ apiKey: process.env.BRAIN_API_KEY! });

const res = await brain.recall({ query: userQuery, max_results: 8 });
import os
from brain_db_sdk import BrainHttpClient

brain = BrainHttpClient(os.environ["BRAIN_API_KEY"])

res = brain.recall(query=user_query, max_results=8)
use brain_db_sdk::BrainHttpClient;
use brain_db_sdk::http::RecallInput;

let brain = BrainHttpClient::new("https://api.arc-labs.ai", &std::env::var("BRAIN_API_KEY")?);

let res = brain
    .recall(&RecallInput {
        query: user_query.to_string(),
        max_results: Some(8),
        ..Default::default()
    })
    .await?;

If res.answer_kind is none, there is nothing to inject — skip the context block rather than injecting an empty one.

Render each hit as a bullet. A rough token budget is characters ÷ 4; drop the lowest-confidence hits until you fit. Keep the source ids alongside so you can attribute later.

function buildContext(memories: { text: string; memory_id: string; confidence: number }[], maxTokens = 1500) {
  const sorted = [...memories].sort((a, b) => b.confidence - a.confidence);
  const lines: string[] = [];
  const sources: string[] = [];
  let tokens = 0;

  for (const m of sorted) {
    const cost = Math.ceil(m.text.length / 4);
    if (tokens + cost > maxTokens) break;
    lines.push(`- ${m.text}`);
    sources.push(m.memory_id);
    tokens += cost;
  }

  const block = lines.length ? `## What I remember\n${lines.join("\n")}` : "";
  return { block, sources, tokens };
}
def build_context(memories, max_tokens=1500):
    ordered = sorted(memories, key=lambda m: m.confidence, reverse=True)
    lines, sources, tokens = [], [], 0
    for m in ordered:
        cost = (len(m.text) + 3) // 4
        if tokens + cost > max_tokens:
            break
        lines.append(f"- {m.text}")
        sources.append(m.memory_id)
        tokens += cost
    block = "## What I remember\n" + "\n".join(lines) if lines else ""
    return {"block": block, "sources": sources, "tokens": tokens}
use brain_db_sdk::http::MemoryHit;

struct Context {
    block: String,
    sources: Vec<String>,
    tokens: usize,
}

fn build_context(memories: &[MemoryHit], max_tokens: usize) -> Context {
    let mut ordered: Vec<&MemoryHit> = memories.iter().collect();
    ordered.sort_by(|a, b| {
        b.confidence
            .partial_cmp(&a.confidence)
            .unwrap_or(std::cmp::Ordering::Equal)
    });

    let mut lines: Vec<String> = Vec::new();
    let mut sources: Vec<String> = Vec::new();
    let mut tokens = 0usize;

    for m in ordered {
        let cost = m.text.len().div_ceil(4);
        if tokens + cost > max_tokens {
            break;
        }
        lines.push(format!("- {}", m.text));
        sources.push(m.memory_id.clone());
        tokens += cost;
    }

    let block = if lines.is_empty() {
        String::new()
    } else {
        format!("## What I remember\n{}", lines.join("\n"))
    };
    Context { block, sources, tokens }
}

Prepend the block to your base instructions, or (better for prompt caching) pass it as a separate context message so your system prompt stays stable across turns.

const { block, sources } = buildContext(res.memories);

const completion = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [
    { role: "system", content: SYSTEM_PROMPT },
    ...(block ? [{ role: "user" as const, content: `<context>\n${block}\n</context>` }] : []),
    { role: "user", content: userQuery },
  ],
});

log.info({ contextSources: sources }, "answered with brain context");

Grouping by kind

If you want sections (facts, preferences, events) rather than a flat list, bucket the hits by their kind byte before formatting. Recall returns the kind on every hit; the enumeration is documented under GET /v1/memories. Keep the grouping in your code — it is presentation, and Brain has no opinion about it.

Log the source memory_ids alongside your own request id whenever you build a context block. When a model produces a surprising answer, the source list tells you exactly which memories it saw — and lets you GET /v1/memories/{id}/inspect to see how each was written.

Was this page helpful?

On this page