Brainby arc-labs/docs
Recipe

Support bot with memory

A minimal chatbot loop that encodes the user turn, recalls prior context before responding, and encodes the assistant reply back.

The pattern is three Brain calls per turn:

  1. encode — store the new user message
  2. recall — get the memories that answer the current cue
  3. encode again — store the assistant reply for next turn's retrieval

Brain has no server-side "context builder" — recall returns a membership verdict (Single / Many / None) with the supporting memories, and you assemble the prompt from their text. Absence is explicit: on None there is nothing to inject, so you fall back to a plain prompt.

import { BrainHttpClient } from '@brain-db/sdk';
import OpenAI from 'openai';

const brain = new BrainHttpClient({
  apiKey: process.env.BRAIN_API_KEY!,
  baseUrl: process.env.BRAIN_BASE_URL, // defaults to http://127.0.0.1:8080
});

const openai = new OpenAI();

async function handleTurn(userMessage: string): Promise<string> {
  // 1. Persist the user turn. `encode` is idempotent and the SDK retries it;
  //    a fresh memory is available to the very next recall.
  await brain.encode({ text: userMessage });

  // 2. Recall the memories that answer this cue.
  const answer = await brain.recall({ query: userMessage, max_results: 6 });

  const context =
    answer.answer_kind === 'none'
      ? ''
      : answer.memories.map((m) => `- ${m.text}`).join('\n');

  // 3. Call the LLM with the recalled memories as the system prompt prefix.
  const completion = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {
        role: 'system',
        content: context
          ? `You are a support agent. Relevant memory about this user:\n\n${context}`
          : 'You are a support agent.',
      },
      { role: 'user', content: userMessage },
    ],
  });

  const reply = completion.choices[0]?.message?.content ?? '';

  // 4. Persist the assistant reply so the next turn can recall it.
  await brain.encode({ text: `assistant replied: ${reply}` });

  return reply;
}
import os
from openai import OpenAI
from brain_db_sdk import BrainHttpClient

brain = BrainHttpClient(
    os.environ['BRAIN_API_KEY'],
    base_url=os.environ.get('BRAIN_BASE_URL', 'http://127.0.0.1:8080'),
)
openai = OpenAI()


def handle_turn(user_message: str) -> str:
    # 1. Persist the user turn.
    brain.encode(user_message)

    # 2. Recall memories that answer this cue.
    answer = brain.recall(user_message, max_results=6)
    context = (
        ''
        if answer.answer_kind == 'none'
        else '\n'.join(f'- {m.text}' for m in answer.memories)
    )

    # 3. Ask the LLM.
    system = (
        f'You are a support agent. Relevant memory about this user:\n\n{context}'
        if context
        else 'You are a support agent.'
    )
    completion = openai.chat.completions.create(
        model='gpt-4o-mini',
        messages=[
            {'role': 'system', 'content': system},
            {'role': 'user', 'content': user_message},
        ],
    )
    reply = completion.choices[0].message.content or ''

    # 4. Persist the assistant reply.
    brain.encode(f'assistant replied: {reply}')

    return reply

recall reranks and fuses three retrievers (semantic, lexical, entity-graph) behind the scenes — you don't pick a strategy. Raise max_results for broader recall on exploratory turns, lower it when you want only the sharpest hits in the prompt.

Don't treat a none verdict as an error. It means Brain genuinely has nothing relevant yet — early in a conversation that's expected. Fall back to a plain prompt rather than blocking the turn.

Was this page helpful?

On this page

No Headings