Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content
Context engineering

Context assembly

Instead of concatenating everything that might be relevant into the prompt, ask memory for one budgeted, prioritized block and inject that. get_context() retrieves, ranks, groups, and trims in a single call, so the model sees the most trustworthy knowledge first and the budget is spent deliberately. Use this pattern whenever you build a system prompt per LLM call — which for most agents is every call.

How it works

get_context() runs retrieval for your query, groups the evidence into named sections, and fills them in a fixed priority order: distilled, validated knowledge (mental models, rules, lessons) first, raw history last. The full order is documented in the memory model. Within a section, items sort by descending score.

When max_token_budget is greater than 0, sections are filled in that priority order until the budget is exhausted — low-priority sections are the first to be dropped. The response accounts for every token:

{
  "context_block": "## Mental Models\n- Acme Corp support profile: ...",
  "token_estimate": 1421,
  "budget_used": 1421,
  "budget_remaining": 79,
  "evidence_candidates_considered": 23,
  "evidence_dropped_by_budget": 6,
  "exact_references_surfaced": 1,
  "section_summaries": [
    { "section_name": "lessons", "item_count": 3, "top_item_preview": "When an export times out...", "estimated_tokens": 210 }
  ],
  "empty_reason": ""
}

empty_reason tells you when the block is empty or degraded rather than making you guess: "no_evidence" (nothing matched), "recency_fallback" (no confident match, most-recent entries returned instead), or "budget_exhausted" (the budget was too small to fit anything). Treat a non-empty empty_reason as a signal, not an error — a brand-new agent legitimately has no evidence yet.

Progressive disclosure

You don't have to fetch the full block up front. mode="summary" returns only section_summaries — item counts, top-item previews, and token estimates per section — with an empty context_block. That's a cheap overview: the model (or your routing code) can look at what memory has before paying for it. Then fetch only the sections that matter with mode="sections":

overview = client.get_context(
    session_id=run_id,
    query="Acme CSV export timeout",
    mode="summary",
)
# section_summaries says lessons and archive_blocks are where the value is:
detail = client.get_context(
    session_id=run_id,
    query="Acme CSV export timeout",
    mode="sections",
    sections=["lessons", "archive_blocks"],
    max_token_budget=800,
)

Valid section keys: mental_models, active_rules, lessons, archive_blocks, handoffs, feedback, facts, observations, working_memory, traces, goals, checkpoints, logs, other. See Context modes for the shape each mode returns.

Walkthrough

At Nimbus, Ari (the support agent) picks up a new Acme Corp ticket and assembles a 1,500-token block for the first LLM call — small enough to leave room for the ticket itself, large enough for the profile, rules, and lessons that make the reply competent:

context = client.get_context(
    session_id="nimbus:ari:ticket-1043",
    query="New Acme Corp ticket: CSV export times out on large reports",
    max_token_budget=1500,
    entry_types=["mental_model", "rule", "lesson", "fact"],
    include_working_memory=True,   # default — Current State rides along
    format="structured",
)
 
if context.get("empty_reason") == "no_evidence":
    context_block = ""             # first ticket ever — proceed without memory
else:
    context_block = context["context_block"]
 
messages = [
    {"role": "system", "content": BASE_PROMPT + "\n\n" + context_block},
    {"role": "user", "content": ticket_body},
]
ℹ️Note

The wire request also accepts a lane filter (lane, with lane_filter as an accepted alias) that restricts assembly to entries tagged with one lane — useful in multi-agent setups. Rust exposes it as GetContextOptions.lane; the Python and JS get_context helpers do not forward it, so use the raw passthrough — client.advanced.context({..., "lane": "billing"}) in both — when you need it. See Lane-scoped memory.

This is exactly where the pattern plugs in: the assembled block goes into the system message, ahead of the task input. It is also what the learn module automates — mubit.learn.init(..., max_token_budget=2048) runs a budgeted get_context() before every LLM call and injects the result, with entry_types and context_sections knobs for the same shaping shown above (Python; module surfaces vary by language — see the learn module).

When not to use it

For a single-fact lookup — "what plan is Acme on?" — plain recall() is cheaper: no block assembly, no section formatting, and evidence_only=True skips answer synthesis entirely. Reach for get_context() when you're briefing a model; reach for recall() when you're answering one question or need the raw scored evidence objects.

Related