Skip to main content

Context Builder

memory/context_builder.py assembles the system prompt for every LLM call. The output is a single string that goes into the model's system slot.

Composition

[Top of system prompt]

1. prime.md ─ operator override block
2. WASP identity ─ name, role, current model
3. Active Flow Lock ─ if domain-locked from prior turn
4. Knowledge Graph (per-chat compact)
5. Self-Model ─ strengths, known failures, prefs
6. Epistemic State ─ high/medium/low confidence domains
7. Temporal Observations ─ recent entity changes (prices, etc.)
8. Procedural Memory ─ matching procedures as few-shots
9. Behavioral Rules ─ all active learned rules
10. Episodic History ─ last N exchanges
11. Vector Memory neighbors ─ semantic similarity to current msg
12. World Model ─ EntityState snapshots
13. Skill catalog ─ available skills + signatures

[End of system prompt]

Each block is optional and gated by feature flags. If a flag is off (e.g., vector_memory_enabled=false), the corresponding block is skipped.

Injection budget

To keep prompts manageable, each layer is capped:

LayerCap
Episodicadaptive (last N exchanges, sized to remaining budget)
Semantic / vector5 entries
Procedural3 procedures
Goal-scoped5 observations
Knowledge graph(compact format, varies)
Behavioral rulesall active rules
Self-modelstrengths + known failures + active preferences
Epistemic statehigh/medium/low summary
Temporalrecent observations relevant to current text

Total memory block typically 2–8 KB.

Adaptive truncation

When the assembled prompt approaches the model's context window, ModelManager.generate() retries progressively:

full history → keep 4 exchanges → keep 2 → keep 1

System prompt is always preserved. Logged as model_manager.overflow_recovered on success.

Per-chat locality

The Context Builder filters memory by chat_id so different operators (or different threads) don't see each other's context. The Knowledge Graph is global by design (operator-shared facts), but the episodic, vector, and goal-scoped layers are chat-scoped.

Goal-scoped context

When a Goal is executing, build_context(goal_id=...) adds:

  • GoalMemory.get_observations(goal_id) — observations recorded during this goal
  • reflection_engine.get_recent(goal_id) — recent reflection insights

This gives the executor a working memory that doesn't leak into unrelated chats.

Active Flow Lock

flow:{chat_id} Redis key (TTL 15 min). When a chat turn establishes a domain (e.g., crypto price lookup), the next message inherits that domain unless the user explicitly switches.

is_explicit_domain_switch() and is_crypto_recovery_followup() decide whether to clear the lock. The lock survives failed LLM rounds.

The lock is injected into the system prompt as [ACTIVE FLOW — CONTEXT LOCK] so the model knows what conversation it is continuing.

What the Context Builder does NOT do

  • It does not reorder or rerank inside layers — that is memory/ranking.py.
  • It does not call the LLM for extraction — KG, behavioral, and procedural extraction are separate pipelines.
  • It does not run the policy guards — those run on the candidate response.

Observability

Each invocation logs event=context_builder.assembled with: tokens_estimated, episodic_count, vector_count, procedural_count, kg_size, behavioral_rule_count. Visible at /live and in structured logs.

See also