Context Engineering: Why Your AI Agent Needs a Database, Not a Prompt VolcEngine's open-source context database OpenViking became the #1 trending Python repository on GitHub, highlighting a shift in AI agent development toward treating agent memory as a database problem rather than a prompting issue. The emerging discipline of context engineering designs an agent's information environment as a living, structured, tiered data system, which can improve long-horizon task accuracy from 24% to 82%. Published: August 22, 2026 | Focus Keyword: context engineering for AI agents | Est. read time: 14 minutes You've built the agent. It passes every eval. Then you deploy it. On day one, it's brilliant. By week three, it's recommending a customer return a product they've already returned twice before, referencing a policy that changed six weeks ago, and confidently calling an API endpoint that was deprecated in the last sprint. You've tuned the prompt a hundred times. You've tried longer system prompts, few-shot examples, chain-of-thought. The agent is still stuck at 24% accuracy on long-horizon tasks. Here's the uncomfortable truth: the model isn't the problem. The context is. This is the inflection point the ML engineering community hit in mid-2026. When OpenViking https://github.com/volcengine/OpenViking — VolcEngine's open-source context database for AI agents — became the 1 trending Python repository on GitHub , it wasn't because engineers were excited about another RAG wrapper. It was because they recognised something more profound: the problem of agent memory had outgrown the vocabulary of prompting. It had become a database problem . Context engineering for AI agents is the emerging discipline of designing and managing the information environment in which your agent operates — not as a static prompt, but as a living, structured, tiered data system. Done right, it transforms that 24% agent into one running at 82%. This post is the technical deep-dive you need to understand why, and how to build it. Before we talk about the solution, let's precisely name the problem. Every AI agent draws on some combination of six context primitives: The text passed directly in the prompt. Fast, zero-latency, but brutally limited. A 1M token window sounds like infinite space until you're running a multi-day coding agent across a 500K-line codebase. And crucially, not all tokens in a long context are attended to equally — the "lost in the middle" problem means your critical instructions buried at position 300K may as well not exist. The standard fix — embed your knowledge base, retrieve the top-k chunks at query time. RAG is essential, but it fails in two ways: precision collapses on multi-hop queries asking about a relationship between two entities that each live in separate chunks , and it has no memory of what it already retrieved . Every turn is stateless. Real-time grounding via search or APIs. Excellent for current events, terrible for internal knowledge. And as the August 2026 UK AISI incident report https://www.aisi.gov.uk/blog/incident-report-unsanctioned-agent-behaviour-during-cyber-testing showed, agents with live web access in improperly sandboxed environments can cause real damage. Structured, typed callable functions. The Model Context Protocol MCP has standardised this. But skills are stateless by design — they do one thing, return a result, and forget. They don't accumulate knowledge across invocations. The chat history buffer. This is the scratchpad that every agent has, but it's ephemeral — it dies with the session. It also grows unboundedly until it hits your context limit, at which point you truncate it and lose the beginning of your reasoning chain. The piece almost everyone gets wrong. Most teams implement this as "save embeddings of conversation turns to a vector database." This is better than nothing, but it's a poor approximation of what agents actually need. ❌ The naive pattern most teams ship today Problems: lossy, stateless across sessions, no structure, no tiering, no self-updating, no provenance class NaiveAgentMemory: def init self, vector db : self.db = vector db def save self, turn: str : embedding = embed turn self.db.upsert embedding, metadata={"text": turn} def recall self, query: str, top k: int = 5 - list str : results = self.db.query embed query , top k=top k return r.metadata "text" for r in results No hierarchy. No tiering. No graph relations. No self-evolution. No provenance. No governance. This is not a memory system. This is a search index. The problem is structural: you're using a search engine to solve a database problem . A search index answers "what text is similar to this query?" A database answers "what is the state of this entity, what changed, when, and why?" The four storage forms that together constitute a complete agent context database. Each serves a distinct access pattern — no single form is sufficient alone. The OpenViking framework, whose VikingMem paper was accepted to VLDB 2026 the top database systems conference , defines context engineering for AI agents around four complementary organization forms. Think of them as the four tables in your agent's relational schema: What it's good at: fuzzy recall, concept-level retrieval, semantic search across unstructured text. What it's bad at: precise lookups, relational joins, structured queries. When to use it: retrieving relevant past episodes, similar code patterns, analogous situations. What it's good at: navigating large knowledge bases with known structure, progressive disclosure, lazy loading. What it's bad at: fuzzy search, ad-hoc queries. When to use it: project documentation, codebase knowledge, anything with a natural tree structure. OpenViking's viking:// protocol is the most elegant implementation of this pattern — it gives your agent a virtual filesystem address space for all its knowledge, with path-based access that mirrors how humans and IDE tools naturally navigate information. OpenViking filesystem protocol example Agent can navigate context like a filesystem viking://project/architecture/decisions/adr-042-database-choice.md L2: Full ADR viking://project/architecture/decisions/ L1: ADR index viking://project/architecture/ L0: "project uses PostgreSQL, event sourcing" What it's good at: precise lookups, aggregations, current state of structured entities. What it's bad at: unstructured text, semantic search. When to use it: user profiles, task state, tool call history, API response caches. -- Agent context as structured state -- This is what you actually want for entity tracking CREATE TABLE agent context entities entity id TEXT PRIMARY KEY, entity type TEXT NOT NULL, -- 'user', 'task', 'codebase', 'decision' state JSONB, last updated TIMESTAMPTZ, session count INT DEFAULT 0, confidence FLOAT -- agent's confidence in this knowledge ; CREATE TABLE agent context relations from entity TEXT REFERENCES agent context entities entity id , relation type TEXT, to entity TEXT REFERENCES agent context entities entity id , evidence TEXT, strength FLOAT ; What it's good at: multi-hop reasoning, relationship traversal, inferring implicit connections. What it's bad at: fuzzy lookup, scale can get expensive for large graphs . When to use it: reasoning about how concepts, people, decisions, and code artifacts relate to each other. The combination of all four forms is what transforms a "memory-augmented chatbot" into an agent that genuinely knows things — with structure, provenance, and the ability to update its knowledge as the world changes. L0 gives the agent orientation 100 tokens . L1 gives structure 2K tokens . L2 provides full detail only when needed — dramatically reducing token consumption and latency. Understanding what to store is only half the battle. The other half is understanding how much of it to put in the context window at any given moment. The naive approach: stuff everything into the prompt. Result: slow, expensive, attention-diluted. The smarter approach: tier your context loading. OpenViking's three-tier system is the most rigorous implementation of this pattern: A compressed, always-present header for each knowledge unit. Think of it as the card in a card catalogue — just enough to know whether this document is relevant without loading the document itself. L0 example for a microservice's context entry: "payment-service: Stripe-based payment processing. Owns /payments/ endpoints. Last updated 2026-08-15. 3 known issues. 2 pending breaking changes." The agent loads ALL L0 summaries for a project at start — total cost: perhaps 5K tokens for a 100-module codebase. The table of contents plus key facts — loaded when the L0 signals relevance. For a service, this might include its API contract, key dependencies, recent change history, and known issues. The agent loads L1 only for services that are likely relevant to the current task — cutting irrelevant loading entirely. The complete knowledge artifact: full source code, full documentation, full conversation history. Loaded only when the agent needs to reason about specifics. ✅ The tiered context loading pattern Dramatically reduces token usage while preserving recall accuracy class TieredContextDB: def init self, viking client : self.db = viking client async def load context for task self, task: str, budget tokens: int = 8000 : """Smart tiered loading — load only what's needed.""" Step 1: Always load ALL L0 summaries cheap — ~100 tokens each l0 summaries = await self.db.load tier level=0, scope="all" Step 2: Score L0 summaries against the task relevant = self.rank by relevance l0 summaries, task, top k=10 Step 3: Load L1 for top candidates 2K tokens each, load ~3-5 l1 details = remaining budget = budget tokens - sum s.token count for s in l0 summaries for candidate in relevant :5 : if remaining budget < 2000: break l1 = await self.db.load tier level=1, entity id=candidate.id l1 details.append l1 remaining budget -= l1.token count Step 4: L2 loaded lazily during reasoning — only if agent requests it context = ContextBundle always present=l0 summaries, structured detail=l1 details, lazy loader=lambda entity id: self.db.load tier level=2, entity id=entity id return context async def evolve self, task: str, result: str, agent trace: list : """Self-evolution: update the DB based on what the agent learned.""" new knowledge = await self.extract knowledge agent trace await self.db.merge new knowledge Viking's conflict-resolution merge await self.db.regenerate summaries affected entities=new knowledge.entities The tiering principle maps directly to how experienced engineers actually work: you scan filenames first, read READMEs second, and read source code only when necessary. The difference is your agent now does this systematically , cheaply , and automatically . The performance gap between naive retrieval and structured context engineering is not incremental — it is categorical. These numbers are from published evaluations on production-grade benchmarks. Let's be precise about what the numbers actually measure and mean. LoCoMo is a benchmark specifically designed to test agents on long-running conversational scenarios — the kind where a customer support agent needs to remember a user's history across dozens of sessions, or a coding agent needs to track decisions made three weeks ago. | System | Accuracy | Token Cost | Latency | |---|---|---|---| | Baseline naive RAG | 24.20% | 1× baseline | 1× baseline | | OpenViking Claude Code backend | 80.32% | −34% | −59% | | OpenViking OpenClaw native | 82.08% | −91% | −66% | | OpenViking Hermes | 82.86% | ~−85% | ~−62% | The 3.39× accuracy improvement is striking. The 91% token reduction is arguably more important for production systems — it's the difference between a context-enriched agent that costs $0.003/query and one that costs $0.033/query. At scale, that's an order of magnitude difference in operational cost. HotpotQA tests the ability to answer questions that require chaining multiple facts — the bread-and-butter of any non-trivial agent task. | System | Accuracy | Index Cost | Latency | |---|---|---|---| | LightRAG | 89.00% | 62.7M tokens | 75.0 seconds | OpenViking | 91.00% | 8.67M tokens | 0.23 seconds | The 326× latency improvement 75s → 0.23s is not a typo. The structural tiering means OpenViking can answer multi-hop questions by navigating its filesystem-shaped knowledge index rather than running expensive graph traversals or sequential LLM calls. The indexing cost savings 62.7M → 8.67M tokens, an 86% reduction also dramatically cut the cost of onboarding new knowledge. tau2-bench tests agents on real-world task completion scenarios in retail and airline customer service — domains with high entity complexity, policy lookups, and state management requirements. | Agent | Baseline | With Context DB | Δ | |---|---|---|---| | Retail agent | 70.94% | 77.81% | +6.87pp | | Airline agent | 54.38% | 66.25% | +11.87pp | A +11.87 percentage point improvement in a production task completion benchmark is the kind of result that changes quarterly metrics for AI product teams. These are not toy improvements. Enough theory. Let's build something. The following walkthrough takes you from zero to a context-engineered agent in under 30 minutes. Install OpenViking pip install openviking Initialise a context database for your project viking init my-agent-context cd my-agent-context The init creates a .viking/ directory with: .viking/ config.yaml storage backends, tiering config entities/ L0/L1/L2 knowledge artifacts relations/ graph edges sessions/ conversation history with self-evolution logs provenance/ audit trail W3C PROV-O ingest.py — One-time setup: populate your context DB from existing sources import asyncio from openviking import Viking, Ingester async def ingest codebase : viking = Viking db path=".viking" ingester = Ingester viking Ingest a code repository — Viking auto-generates L0/L1/L2 for each module await ingester.ingest repository path="./src", entity type="codebase", chunk strategy="by module", or "by file", "by function" generate summaries=True, LLM-generated L0 and L1 summaries extract relations=True, Build the knowledge graph Ingest documentation await ingester.ingest docs path="./docs", entity type="documentation", Ingest past decision records await ingester.ingest files pattern="./decisions/adr- .md", entity type="architecture decision", print f"Ingested {len await viking.list entities } entities" print f"Built {len await viking.list relations } relations" asyncio.run ingest codebase python agent.py — A context-engineered agent using OpenAI or Anthropic import asyncio from openviking import Viking from openai import AsyncOpenAI works identically with anthropic.AsyncAnthropic class ContextEngineeredAgent: def init self : self.viking = Viking db path=".viking" self.llm = AsyncOpenAI self.session id = None async def start session self, session id: str : """Begin a new agent session — loads L0 context automatically.""" self.session id = session id Viking loads all L0 summaries ~100 tokens each as the base orientation self.base context = await self.viking.session start session id=session id, load tier=0, Always-present L0 summaries scope="all", Across all knowledge entities return self.base context async def run self, user message: str - str: """Process a message with full context engineering.""" Step 1: Viking scores L0 summaries and fetches relevant L1 detail enriched context = await self.viking.get context for query query=user message, session id=self.session id, l1 top k=5, Load L1 for top 5 relevant entities token budget=12000, Hard cap on context tokens include relations=True, Add graph edges for multi-hop reasoning Step 2: Build the system prompt dynamically from structured context system prompt = f"""You are a helpful engineering assistant. Project Context Auto-loaded by Viking Context DB Always-Present Knowledge L0 — All Entities {enriched context.l0 overview} Relevant Detail L1 — Top Matches for This Query {enriched context.l1 details} Active Relations Knowledge Graph Edges {enriched context.relations} Session Memory What We've Established This Session {enriched context.session memory} If you need deeper detail on any entity, call the load context tool with the entity ID. """ Step 3: Run the LLM with L2 lazy-loading tool response = await self.llm.chat.completions.create model="gpt-5.6-terra", messages= {"role": "system", "content": system prompt}, {"role": "user", "content": user message}, , tools= { "type": "function", "function": { "name": "load context", "description": "Load full L2 detail for a specific knowledge entity", "parameters": { "type": "object", "properties": { "entity id": {"type": "string", "description": "The entity ID from L0/L1 summaries"} }, "required": "entity id" } } } Step 4: Handle L2 lazy loading if the agent requests it if response.choices 0 .finish reason == "tool calls": tool call = response.choices 0 .message.tool calls 0 entity id = eval tool call.function.arguments "entity id" Load L2 detail on demand — only when the agent actually needs it l2 content = await self.viking.load tier level=2, entity id=entity id Continue the conversation with L2 content injected ... standard tool response handling agent response = response.choices 0 .message.content Step 5: Self-evolution — Viking extracts new knowledge from this turn await self.viking.evolve from turn session id=self.session id, user message=user message, agent response=agent response, auto merge=True, Automatically merge new facts into the DB confidence threshold=0.85, Only merge high-confidence extractions return agent response Usage async def main : agent = ContextEngineeredAgent await agent.start session "engineering-session-001" response = await agent.run "Why did we choose PostgreSQL over MongoDB for the payments service?" print response Agent correctly cites ADR-042, the decision context, and the relation to the payments-service entity — without hallucinating. asyncio.run main After running several sessions, inspect how the context DB has evolved: Check what the agent has learned viking status Output: Entities: 247 was 180 at ingest — agent added 67 from sessions Relations: 1,843 was 1,200 — 643 new edges discovered L0 freshness: 98.7% current auto-regenerated when entities changed Sessions: 14 sessions, 89 turns indexed Evolution: 43 knowledge merges, 12 conflicts resolved, 0 contradictions pending Inspect a specific entity's evolution history viking history --entity payment-service View the provenance of a specific fact viking provenance "payment-service uses Stripe" → Extracted from session-003, turn 7, with 0.94 confidence Confirmed in session-008, turn 2 Source: human engineer statement + codebase scan match Production AI deployments in 2026 face a compliance requirement that most context engineering discussions skip entirely: auditability . If your agent makes a decision — recommends a refund, blocks an account, generates a contract clause — you need to be able to reconstruct exactly what context it had when it made that decision. Semantica https://github.com/semantica-agi/semantica — another trending GitHub project this week — addresses this with a graph-native governance layer built on: python governance.py — Adding auditability to your context DB from semantica import SemanticaGraph, ProvenanceTrace, SHACLValidator class AuditableContextDB: def init self, viking client, semantica graph : self.viking = viking client self.graph = semantica graph self.validator = SHACLValidator schema path="schemas/agent-context.shacl.ttl" async def merge with provenance self, new knowledge: dict, session id: str : """Merge new knowledge with full PROV-O provenance tracking.""" Validate against SHACL schema before merging validation result = self.validator.validate new knowledge if not validation result.conforms: raise ContextValidationError f"Knowledge rejected: {validation result.violations}" Create provenance record W3C PROV-O provenance = ProvenanceTrace activity id=f"merge-{session id}-{timestamp }", agent id="context-engineering-agent-v2", used= session id , Which session generated this generated at=datetime.utcnow , confidence=new knowledge.get "confidence", 0.0 , Merge into knowledge graph with provenance await self.graph.merge triples=new knowledge "triples" , provenance=provenance, Synchronise with Viking's tiered storage await self.viking.sync from graph self.graph, affected entities=new knowledge "entities" async def explain decision self, decision id: str - str: """Full audit trail for a specific agent decision — SPARQL query.""" query = f""" PREFIX prov: