Article
Build context-aware AI agents with IBM BeeAI
Learn context engineering pipelines for personalized, stateful AI agents using the IBM BeeAI FrameworkThe AI landscape has quickly evolved from chatbots based on natural language processing (NLP) and machine learning to multi-step autonomous agents based on large language models (LLMs). Prompt engineering helped with what to say to the model. Context engineering is what is needed to help with what information to give the models, when, and how to keep that information fresh and relevant across sessions.
Context engineering is an emerging discipline. It can best be defined as the systematic practice of designing, managing, and optimizing the flow of contextual information throughout an AI agent's lifecycle, from initial collection and storage, through retrieval and transformation, to validation and injection into the model. Where prompt engineering is stateless, context engineering is stateful. Context engineering gives agents memory across time, channels, and users.
To define high-quality context and reduce noise, content engineering addresses these properties:
- Relevance. Irrelevant information degrades response quality, causes hallucinations, and burns tokens.
- Consistency. The same facts presented in different formats must produce the same response. Users lose trust fast when rephrasing a question gives a different answer.
- Security. Context must be isolated per user and encrypted in transit and at rest, which is a legal and compliance requirement in regulated industries, not a nice-to-have.
In this article, you'll learn about a context engineering pipeline architecture and its important components and then how to build a context management engine using the IBM BeeAI Framework.
Understanding context: A banking analogy
Consider this scenario: Imagine you walk into a bank and ask "I want to invest some money."
- At the Help Desk, you get a brochure. The agent knows you are a customer—nothing more. That is environmental context, and it is what a stateless chatbot offers.
- At the Teller, the experience improves. They see your balance, recent transactions, and account type—explicit context. But responses are still standardized. This maps to an agent with basic database access and no memory management.
- With your Relationship Manager, the interaction is different in kind, not just degree. They remember five years of conservative investments (long-term context). They caught your offhand mention last year of your daughter starting engineering college (episodic context). When you hesitate at the words "lock-in period" today (implicit context), they pivot immediately to an Education Savings Plan with Section 80C tax benefits, which is an option you had not even asked about.
This scenario demonstrates how context is built to provide personalized, effective solutions by:
- Retrieving relevant history from years of interactions (long-term memory)
- Identifying significant life events mentioned in passing (episodic memory)
- Detecting subtle cues in the current conversation (implicit signals)
- Incorporating information from multiple sources (account data, past conversations, product knowledge)
- Prioritizing what matters (college timeline over retirement planning, flexible options given hesitation about lock-in)
The following table maps this scenario directly to the components you need to build in a multi-agent system:
| Banking persona | Context type | Implemented as |
|---|---|---|
| Help Desk brochure | Environmental (stateless) | Baseline system prompt only |
| Teller account view | Explicit, Profile | SESSION_STORE > system prompt |
| Relationship Manager memory | Long-term + Episodic | VECTOR_DB > system prompt |
| Relationship Manager intuition | Semantic, Dynamic | ProductKnowledgeTool (agent-invoked) |
It is not just about remembering everything. It is about knowing what to remember, when to retrieve it, how to weigh its importance, and how to synthesize it into actionable insights. Context engineering performs the same role in an agentic system.
Context engineering pipeline architecture
Context engineering is typically implemented as a multi-stage pipeline with each stage having a distinct responsibility. The following figure shows the context engineering pipeline stages, showing the flow from collection through monitoring.

The pipeline architecture presented here synthesizes patterns from production RAG systems, hierarchical memory architectures like MemGPT, and established data engineering practices:
| Stage | Name | What Happens |
|---|---|---|
| 1 | Collection | Gather from user input, system state, external feeds |
| 2 | Storage | Hot (in-memory), Warm (cache), Cold (DB), Vector (embeddings) |
| 3 | Retrieval | Direct lookup, semantic search, hybrid search, graph traversal |
| 4 | Transformation | Summarization, enrichment, filtering, prioritization |
| 5 | Validation | Quality scoring, privacy enforcement, token budget check |
| 6 | Assembly | Layer ordering, template application, final optimization |
| 7 | Injection | System/user message split, direct prompt, native memory addition |
| 8 | Monitoring | Latency, quality, resource, and business metrics tracking |
Context composition model
Context arrives from multiple sources. The seven-layer model presented here adapts hierarchical memory concepts from MemGPT and tiered caching strategies from distributed systems design. Knowing which layer maps to which storage tier and time-to-live (TTL) is what separates a working prototype from a maintainable system:
| Layer | Purpose | Primary Storage | Cache TTL |
|---|---|---|---|
| Foundation | System message, role, capabilities, constraints | Config / App memory | Permanent |
| Personalization | User profile, risk tolerance, communication style | PostgreSQL / Redis | 1 hour |
| Continuity | Conversation history, session state | Redis / In-memory | 30–60 min |
| Knowledge | RAG results from domain documents | Vector DB (Pinecone) / Redis | 5–15 min |
| Workflow | Active multi-step task state | Redis / In-memory | 1 hour |
| Action | Recent tool or function call results | Ephemeral / Redis | 1–5 min |
| Trigger | Current user query — injected last | Request object | Ephemeral |
The Trigger layer (the user's current query) is placed closest to the generation boundary to leverage LLM recency weighting, ensuring that the most immediate context receives appropriate attention during response generation.
An implementation of agentic context assembly with the BeeAI Framework
Now that you understand the stages and layers of context, it is time to put them into action. Rather than statically pasting strings together, modern architectures integrate context retrieval directly into the agent's native workflow.
The following implementation uses the IBM BeeAI framework to demonstrate a context pipeline. It maps the theoretical stages to actual code. The complete app.py is available in the companion GitHub repository.
This code requires Python 3.13+ and a set of watsonx.ai credentials set in an .env file. The code uses ibm/granite-3-8b-instruct. Always verify the current model ID in the IBM watsonx.ai model catalog before running, as identifiers are updated with new releases.
Note: The code included in this article is for educational purposes only. Production deployment requires appropriate error handling, security controls, secrets management, and infrastructure configuration.
Required Python frameworks and imports
All imports required to run this code.
import asyncio
import os
from pydantic import BaseModel, Field
from typing import Any
from beeai_framework.adapters.watsonx.backend.chat import WatsonxChatModel
from beeai_framework.backend.chat import ChatModelParameters
from beeai_framework.backend.message import SystemMessage
from beeai_framework.memory import UnconstrainedMemory
from beeai_framework.agents.requirement import RequirementAgent
from beeai_framework.tools.tool import Tool, StringToolOutput
from beeai_framework.emitter import Emitter
from dotenv import load_dotenv
load_dotenv()
1. Simulated context stores
In production, context layers live in databases and vector stores. In this case, they are in-memory dictionaries that illustrate the exact data shapes that each stage expects. Comments show the intended production mapping.
# Personalization Layer — production target: PostgreSQL or Redis
SESSION_STORE = {
"cust_001": "Age: 45, Profession: Salaried, Risk: Conservative",
"cust_002": "Age: 28, Profession: Tech Entrepreneur, Risk: Aggressive"
}
# Continuity Layer — production target: Vector DB (e.g., Pinecone) with embeddings
VECTOR_DB = {
"cust_001": "Past interactions: mutual funds, tax-saving, daughter's college admission.",
"cust_002": "Past interactions: API startups, crypto, high-yield tech ETFs."
}
# Knowledge Layer — production target: RAG pipeline over document corpus
# In this tutorial, keyword lookup substitutes for vector similarity search.
# Part 2 of this series replaces this dict with a real embedding-based retrieval step.
PRODUCT_KNOWLEDGE = {
"education": "Education Savings Plan details, Tax benefits under 80C, Secure Bonds.",
"growth": "High-risk digital asset framework, aggressive tech mutual funds.",
"retirement":"Low-risk annuities, government-backed pension schemes.",
"tax_saving":"ELSS mutual funds, tax-free infrastructure bonds."
}
2. Dynamic context tool
Instead of forcing a knowledge lookup to execute before the prompt, we give the agent a ProductKnowledgeTool. This tool allows the agent to reason about the user's query and actively discover the right domain knowledge at runtime.
The _run method currently uses keyword matching over a dictionary. This implementation is a deliberate simplification to keep infrastructure requirements minimal. In a production system, keyword matching should be replaced by vector similarity search over an embedded document corpus.
class ProductSearchInput(BaseModel):
query: str = Field(
description="Keywords to look up relevant financial products (e.g., 'education', 'crypto', 'tax')."
)
class ProductKnowledgeTool(Tool[ProductSearchInput, Any, StringToolOutput]):
name = "ProductKnowledgeSearch"
description = (
"Looks up the internal financial product knowledge base using keywords. "
"Use this to find product details, tax rules, or asset class information."
)
input_schema = ProductSearchInput
def __init__(self, options: dict[str, Any] | None = None) -> None:
super().__init__(options)
def _create_emitter(self) -> Emitter:
# BeeAI requires every Tool to declare an event emitter scoped to its
# namespace. This enables the framework's built-in observability hooks
# (logging, tracing, streaming) without additional setup.
return Emitter.root().child(
namespace=["tool", "product_knowledge"],
creator=self,
)
async def _run(
self, input: ProductSearchInput, options: Any, context: Any
) -> StringToolOutput:
search_terms = input.query.lower().split()
matched_results = []
for category, details in PRODUCT_KNOWLEDGE.items():
if any(term in category for term in search_terms) or \
any(term in details.lower() for term in search_terms):
matched_results.append(f"[{category.upper()}]: {details}")
if matched_results:
return StringToolOutput(
result="Matching product details:\n" + "\n".join(matched_results)
)
return StringToolOutput(
result="No matching product details found. Try broader keywords."
)
3. Context pipeline
This build_context_pipeline function maps Stages 3 through 7 of our context pipeline architecture. It retrieves static historical data, transforms it into readable text, validates it against edge cases, assembles the layered system prompt, and injects it into the agent's UnconstrainedMemory.
async def build_context_pipeline(customer_id: str) -> UnconstrainedMemory:
"""Executes Context Engineering Pipeline stages 3–7."""
memory = UnconstrainedMemory()
# STAGE 3: RETRIEVAL
raw_profile = SESSION_STORE.get(customer_id)
raw_history = VECTOR_DB.get(customer_id)
# STAGE 4: TRANSFORMATION
profile_text = raw_profile or "Status: New Customer (No profile data)"
history_text = raw_history or "Status: No prior interactions"
# STAGE 5: VALIDATION
if "Aggressive" in profile_text and not raw_history:
history_text += " | [SYSTEM NOTE: Verify risk tolerance; history missing.]"
# STAGE 6: ASSEMBLY — Foundation layer first, Trigger layer injected last by the agent
system_prompt = f"""You are an expert financial advisor specializing in personalized investment recommendations.
Use the ProductKnowledgeSearch tool to discover specific product details before making recommendations.
ALWAYS tailor your advice to the customer's background below.
=== CUSTOMER BACKGROUND ===
Profile: {profile_text}
Past Interactions: {history_text}
"""
# STAGE 7: INJECTION
await memory.add(SystemMessage(content=system_prompt))
return memory
4. Agent invocation
The invoke_beeai_agent function orchestrates the entire context pipeline by initializing the LLM, building the context-aware memory, creating the agent with tools, and executing the user query with appropriate retry logic.
async def invoke_beeai_agent(customer_id: str, user_query: str) -> str:
print(f"\n--- [INITIALIZING PIPELINE FOR {customer_id}] ---")
try:
llm = WatsonxChatModel(
model_id="ibm/granite-3-8b-instruct",
project_id=os.getenv("WATSONX_PROJECT_ID"),
parameters=ChatModelParameters(temperature=0)
)
memory = await build_context_pipeline(customer_id)
agent = RequirementAgent(llm=llm, tools=[ProductKnowledgeTool()], memory=memory)
print(f"User Query: {user_query}")
# Trigger Layer: user query is always the final input — injected closest
# to the generation boundary to leverage LLM recency weighting.
response = await agent.run(
user_query,
max_retries_per_step=3,
total_max_retries=10,
max_iterations=5
)
# STAGE 8: MONITORING (stub — full observability pipeline in Part 3)
print(f"[Pipeline Log] Interaction for {customer_id} complete.")
return response.last_message.text
except Exception as e:
print(f"[ERROR] Pipeline failed for {customer_id}: {e}")
raise
Full execution and expected output
The main function demonstrates the context engine in action by processing two different customer queries. Each customer receives personalized recommendations based on their unique profile, risk tolerance, and interaction history, showcasing how the same codebase produces contextually appropriate responses.
async def main():
print("=====================================================")
print(" IBM BEEAI AGENTIC CONTEXT ENGINE")
print("=====================================================")
# Note: Currency amounts use Indian rupees (₹). 1 lakh = 100,000 rupees.
response_1 = await invoke_beeai_agent(
"cust_001", "Where should I invest this ₹5 lakhs for my daughter?"
)
print(f"\n🤖 AGENT RESPONSE (cust_001):\n{response_1}\n")
response_2 = await invoke_beeai_agent(
"cust_002", "What are the best options for ₹2 lakhs cash right now?"
)
print(f"\n🤖 AGENT RESPONSE (cust_002):\n{response_2}\n")
if __name__ == "__main__":
asyncio.run(main())
Sample output (abridged):
=====================================================
IBM BEEAI AGENTIC CONTEXT ENGINE
=====================================================
--- [INITIALIZING PIPELINE FOR cust_001] ---
User Query: Where should I invest this ₹5 lakhs for my daughter?
[Pipeline Log] Interaction for cust_001 complete.
🤖 AGENT RESPONSE (cust_001):
Given your conservative risk profile and your daughter's upcoming college admission,
I recommend the Education Savings Plan with Section 80C tax benefits, complemented
by Secure Bonds for capital preservation. These align with your investment history
and provide liquidity around the college timeline.
--- [INITIALIZING PIPELINE FOR cust_002] ---
User Query: What are the best options for ₹2 lakhs cash right now?
[Pipeline Log] Interaction for cust_002 complete.
🤖 AGENT RESPONSE (cust_002):
Given your aggressive risk tolerance and history with crypto and high-yield tech
assets, I would look at the high-risk digital asset framework and aggressive tech
mutual funds from our growth portfolio. These offer high upside potential consistent
with your prior investment style.
Two entirely distinct recommendations emerge from the same application logic. That difference is driven entirely by the context pipeline, not by different prompts or different models.
Summary
Prompt engineering tells the model what to do. Context engineering gives it the infrastructure to do it well, with memory, personalization, and dynamic domain knowledge.
The context engineering pipeline demonstrated here, which separates retrieval from assembly, injects memory into native agent state, and offloads domain discovery to agent-callable tools, is a repeating pattern you will encounter in every production agentic system.
Building high-quality, practical context engineering pipelines requires adhering to certain best practices. The following table synthesizes guidance from production RAG implementations, distributed caching strategies, and security best practices for multi-tenant AI systems:
| Concept | The failure mode | What to do |
|---|---|---|
| Scope and boundaries | Context bloat from irrelevant data | Filter aggressively; feed only signals relevant to the current task |
| Freshness and decay | Stale data producing outdated recommendations | Time-weight context; define a "shelf-life" for older records |
| Granularity | Storing every interaction verbatim | Keep recent events verbatim; compress older ones into summaries |
| Conflict resolution | Old profile contradicting live session | Explicit session corrections always override historical data |
| Privacy and security | Cross-user data leakage | RBAC on retrieval; strict tenant isolation in vector stores |
| Portability | Users repeating themselves across channels | Standardize context payloads across web, mobile, and voice |