How to Give AI a Map, Not an Encyclopedia: Mastering Context Engineering

This article explains why AI can only act on information that appears in its context window, outlines the twin problems of missing data and overload, and presents a practical methodology—including AGENTS.md maps and LangChain's LocalContextMiddleware implementation—to engineer concise, searchable context for reliable agent behavior.

Qborfy AI
Qborfy AI
Qborfy AI
How to Give AI a Map, Not an Encyclopedia: Mastering Context Engineering

What "context" means for an AI agent

In the world of AI agents, context is everything the model can "see" while executing a task: the conversation history, system prompts, tool results, injected documents, and explicit task requirements. Anything stored only in Google Docs, a chat log, or a developer's mind is invisible to the model.

Each model has a fixed context window . When the accumulated tokens exceed this limit, the earliest information is truncated or compressed, creating two core issues:

Information shortage : the model lacks crucial data and therefore cannot answer correctly.

Information overload ("context anxiety") : the model is flooded with irrelevant material and cannot locate the key facts.

Problem 1 – Information Shortage

Why it happens

If a required piece of knowledge is missing from the context, the model does not say "I don't know". Instead it hallucinates a plausible answer based on whatever it can infer, which may be outright wrong.

Example: asking an AI to modify an API endpoint without telling it that the endpoint has a special authentication flow will likely produce code that breaks the auth logic because the model never saw that requirement.

OpenAI’s solution: a structured repository as a "record system"

OpenAI engineers discovered that the safest way to guarantee the model sees the right facts is to store all essential information in the code repository, organized in a predictable hierarchy.

AGENTS.md          ← a short "map" that points to other docs
ARCHITECTURE.md    ← high‑level architecture overview
docs/
├── design-docs/   ← design specifications
├── exec-plans/    ← progress & decision logs
├── product-specs/ ← product requirements
├── references/    ← external references
├── DESIGN.md
├── FRONTEND.md
└── QUALITY_SCORE.md

The key idea is that AGENTS.md is a directory map, not an encyclopedia . It contains only ~100 lines that tell the agent where to look for detailed information.

When the team tried to cram everything into a single massive AGENTS.md, three problems emerged:

Context is a scarce resource; a huge file pushes out task‑specific data.

Too many directives become ineffective – when everything is "important", nothing is.

Files decay: rules become outdated and the model cannot tell which are still valid.

Consequently they switched to a "progressive disclosure" approach: a tiny map that points to richer documents.

Problem 2 – Information Overload & Context Anxiety

What "context anxiety" looks like

Anthropic engineers observed that when the context window is near capacity, the model starts to "rush" its output, skipping steps and producing incomplete results – similar to a person cramming a suitcase at the last minute.

They call this phenomenon Context Anxiety . Compression (summarising early dialogue) does not solve it; a full context reset —creating a hand‑off document for a fresh model instance—is required.

Solution: Context Reset & Time‑Budget Alerts

Anthropic’s fix for Claude Sonnet 4.5 is to generate a structured hand‑off document and start a new agent with a clean context. LangChain also injects a time‑budget warning so the model knows how many minutes remain and can prioritise core work.

LangChain’s Direct Approach – LocalContextMiddleware

LangChain injects the environment information automatically when the agent starts. The middleware scans the project directory, discovers available tools, gathers Python and Git metadata, and formats everything into a concise prompt.

"""
LocalContextMiddleware full implementation
Injects project structure, tools, Python env, Git info, key files.
"""
import os, subprocess, json
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field

@dataclass
class ContextInfo:
    """Context information data class"""
    project_root: str
    directory_structure: str
    available_tools: List[Dict[str, str]]
    python_info: Dict[str, Any]
    git_info: Dict[str, Any]
    key_files: List[str]
    estimated_context_size: int = 0

class LocalContextMiddleware:
    """Injects local context at agent start.
    1. Project tree (2‑3 levels)
    2. Available tools/commands
    3. Python environment
    4. Git status
    5. Key config files
    Design principles:
    - Keep < 2000 tokens
    - Provide a "map", not the full content
    """
    SCAN_DEPTH = 3
    IGNORE_DIRS = {'node_modules', '__pycache__', '.git', 'venv', '.venv', 'dist', 'build', '.next', '.cache', 'coverage', '.tox'}
    KEY_FILE_PATTERNS = ['*.md', '*.yaml', '*.yml', '*.toml', 'Dockerfile*', '.env.example', 'Makefile', '*.json', 'requirements*.txt', 'pyproject.toml']

    def __init__(self, working_dir: str = None):
        self.working_dir = Path(working_dir or os.getcwd()).resolve()
        self._context_cache: Optional[ContextInfo] = None

    def collect(self) -> ContextInfo:
        if self._context_cache:
            return self._context_cache
        context = ContextInfo(
            project_root=str(self.working_dir),
            directory_structure=self._scan_directory(),
            available_tools=self._detect_tools(),
            python_info=self._get_python_info(),
            git_info=self._get_git_info(),
            key_files=self._find_key_files()
        )
        context.estimated_context_size = self._estimate_tokens(context)
        self._context_cache = context
        return context

    def _scan_directory(self) -> str:
        lines = [f"📁 {self.working_dir.name}/"]
        def scan(path: Path, prefix: str = "", depth: int = 0):
            if depth >= self.SCAN_DEPTH:
                return
            try:
                entries = sorted([e for e in path.iterdir() if not e.name.startswith('.')],
                                 key=lambda e: (not e.is_dir(), e.name.lower()))
            except PermissionError:
                return
            for i, entry in enumerate(entries):
                is_last = i == len(entries) - 1
                connector = "└── " if is_last else "├── "
                extension = "│   " if not is_last else "    "
                if entry.is_dir():
                    lines.append(f"{prefix}{connector}📂 {entry.name}/")
                    scan(entry, prefix + extension, depth + 1)
                else:
                    size_str = ""
                    if entry.suffix in {'.py', '.js', '.ts', '.tsx', '.jsx'}:
                        size_kb = entry.stat().st_size / 1024
                        size_str = f" ({size_kb:.0f}KB)"
                    lines.append(f"{prefix}{connector}📄 {entry.name}{size_str}")
        scan(self.working_dir)
        return "
".join(lines)

    def _detect_tools(self) -> List[Dict[str, str]]:
        tools_to_check = [
            ('python', 'python3 --version'), ('node', 'node --version'), ('npm', 'npm --version'),
            ('pip', 'pip3 --version'), ('docker', 'docker --version'), ('git', 'git --version'),
            ('make', 'make --version'), ('pytest', 'pytest --version'), ('jest', 'jest --version')
        ]
        available = []
        for name, cmd in tools_to_check:
            try:
                result = subprocess.run(cmd.split(), capture_output=True, text=True, timeout=5)
                if result.returncode == 0:
                    version = result.stdout.strip().split('
')[0]
                    available.append({"name": name, "version": version, "command": cmd.split()[0]})
            except (FileNotFoundError, subprocess.TimeoutExpired):
                continue
        return available

    def _get_python_info(self) -> Dict[str, Any]:
        info = {}
        try:
            result = subprocess.run(['python3', '--version'], capture_output=True, text=True, timeout=5)
            info['version'] = result.stdout.strip()
            result = subprocess.run(['pip3', 'list', '--format=json'], capture_output=True, text=True, timeout=10)
            if result.returncode == 0:
                packages = json.loads(result.stdout)
                important = [p['name'] for p in packages if p['name'] in {
                    'langchain','openai','anthropic','llama-index','fastapi','flask','django','pytest','pandas','numpy','torch','transformers','requests'}]
                info['key_packages'] = important
        except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError):
            info['error'] = 'Unable to fetch Python info'
        return info

    def _get_git_info(self) -> Dict[str, Any]:
        info = {}
        try:
            result = subprocess.run(['git', 'branch', '--show-current'], cwd=self.working_dir, capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                info['branch'] = result.stdout.strip()
            result = subprocess.run(['git', 'log', '-1', '--pretty=%h %s (%an, %ar)'], cwd=self.working_dir, capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                info['last_commit'] = result.stdout.strip()
            result = subprocess.run(['git', 'status', '--porcelain'], cwd=self.working_dir, capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                changes = [c for c in result.stdout.strip().split('
') if c]
                info['dirty_files'] = len(changes)
        except (FileNotFoundError, subprocess.TimeoutExpired):
            info['error'] = 'Not a Git repo or Git unavailable'
        return info

    def _find_key_files(self) -> List[str]:
        key_files = []
        for pattern in self.KEY_FILE_PATTERNS:
            matches = list(self.working_dir.rglob(pattern))
            matches = [m for m in matches if not any(ignored in m.parts for ignored in self.IGNORE_DIRS)]
            for m in matches[:5]:
                key_files.append(str(m.relative_to(self.working_dir)))
        return sorted(key_files)[:20]

    def _estimate_tokens(self, ctx: ContextInfo) -> int:
        total_text = ctx.directory_structure + str(ctx.available_tools) + str(ctx.python_info) + str(ctx.git_info) + str(ctx.key_files)
        return len(total_text) // 4  # rough 4 chars per token

    def format_for_injection(self, ctx: ContextInfo = None) -> str:
        ctx = ctx or self.collect()
        sections = []
        sections.append("## 📍 工作环境")
        sections.append(f"- **项目根目录**: `{ctx.project_root}`")
        sections.append(f"- **Git 分支**: {ctx.git_info.get('branch', 'N/A')}")
        sections.append(f"- **最近提交**: {ctx.git_info.get('last_commit', 'N/A')}")
        if ctx.git_info.get('dirty_files', 0) > 0:
            sections.append(f"- ⚠️ 有 {ctx.git_info['dirty_files']} 个未提交文件")
        sections.append("
## 📂 目录结构(前3层)")
        sections.append("```")
        sections.append(ctx.directory_structure)
        sections.append("```")
        if ctx.available_tools:
            sections.append("
## 🔧 可用工具/命令")
            for tool in ctx.available_tools:
                sections.append(f"- **{tool['name']}**: {tool['version']}")
        if ctx.python_info and 'error' not in ctx.python_info:
            sections.append("
## 🐍 Python 环境")
            sections.append(f"- **版本**: {ctx.python_info.get('version', 'N/A')}")
            if ctx.python_info.get('key_packages'):
                pkgs = ', '.join(ctx.python_info['key_packages'])
                sections.append(f"- **关键依赖**: {pkgs}")
        if ctx.key_files:
            sections.append("
## 📋 关键文件索引")
            sections.append("> 以下文件可能包含你需要的重要信息:")
            for f in ctx.key_files[:15]:
                sections.append(f"- `{f}`")
        sections.append("
---
> 💡 **如何使用以上信息**:
> 1. 先看目录结构了解项目布局
> 2. 需要细节时打开对应文件
> 3. 配置文件通常包含环境与构建信息
> 4. 使用 `read_file` 或 `search` 工具读取具体内容")
        result = "
".join(sections)
        if ctx.estimated_context_size > 3000:
            result += f"

⚠️ 上下文信息较大(~{ctx.estimated_context_size} tokens),如遇空间不足可省略目录结构部分。"
        return result

    def reset(self):
        """Clear cached context"""
        self._context_cache = None

def setup_local_context_middleware(agent_config: dict):
    """Example of wiring the middleware into an agent configuration"""
    middleware = LocalContextMiddleware(working_dir=agent_config.get('working_dir'))
    def on_agent_start(agent_state: dict):
        context = middleware.collect()
        context_text = middleware.format_for_injection(context)
        agent_state['local_context'] = context_text
        agent_state['context_info'] = {
            'project_root': context.project_root,
            'available_tools': [t['name'] for t in context.available_tools],
            'key_files': context.key_files
        }
        print(f"[LocalContext] 已注入上下文 (~{context.estimated_context_size} tokens)")
        print(f"[LocalContext] 项目: {context.project_root}")
        print(f"[LocalContext] 可用工具: {[t['name'] for t in context.available_tools]}")
        return {'middleware': middleware, 'hooks': {'on_start': on_agent_start}}

Three Core Principles of Context Engineering

Visible = existent, invisible = nonexistent – put every piece of knowledge the model needs into a place it can read (repo, docs, tool output).

Map over encyclopedia – keep the prompt short; give the model a navigation guide instead of dumping all data.

Monitor context health – track token usage, ensure key facts stay within the window, and watch for signs of context anxiety.

Practical Recommendations

Store all essential information in a structured code repository; avoid relying on external chats or undocumented knowledge.

Create a concise AGENTS.md map (≈100 lines) that points to deeper docs, API specs, design files, and deployment guides.

Inject the environment at task start with LocalContextMiddleware (see implementation above).

Set up context monitoring: warn when token usage approaches the model limit and trigger a reset or compression.

If a task has a deadline, inject periodic time‑budget alerts so the model can prioritise core work.

Ready‑to‑Use Templates

AGENTS.md template (excerpt) :

# AGENTS.md — 项目导航地图
> 本文件是 AI Agent 的"项目地图",不是百科全书。
> 它的作用是告诉你去哪里找信息,而不是塞给你所有信息。
> 预计阅读时间:2 分钟。
---
## 📌 一句话概述
[你的项目名称] 是一个 [一句话描述项目做什么]。
技术栈:[主要语言/框架],部署在 [环境]。
## 🎯 当前开发阶段
- **当前 Sprint**:Sprint 名称/编号(截止日期:YYYY-MM-DD)
- **进行中的工作**:简要描述正在开发的功能
- **下一步计划**:接下来要做的事
## 📂 关键位置速查
### 代码结构
src/
├── core/   ← 核心业务逻辑(最先看这里)
│   ├── models/   ← 数据模型定义
│   └── services/  ← 业务服务层
├── api/   ← API 路由和控制器
├── utils/ ← 工具函数
└── tests/ ← 测试代码
### 文档索引
| 你想了解... | 去这里 |
|------------|--------|
| 整体架构设计 | `docs/architecture/ARCHITECTURE.md` |
| API 接口规范 | `docs/api/API_SPEC.md` |
| 数据模型设计 | `docs/design/DATA_MODELS.md` |
| 当前执行计划 | `docs/plans/CURRENT_SPRINT.md` |
| 编码规范 | `docs/standards/CODING_STANDARDS.md` |
| 部署指南 | `docs/ops/DEPLOYMENT.md` |
| 产品需求规格 | `docs/product/PRD.md` |
### 配置文件
| 文件 | 用途 |
|------|------|
| `.env.example` | 环境变量模板(复制为 .env 使用) |
| `pyproject.toml` | Python 项目配置和依赖 |
| `config/settings.py` | 运行时配置(分 dev/stage/prod) |
## ⚠️ 重要约束和规则
### 必须遵守
1. 所有 API 必须有认证中间件
2. 数据库变更必须写迁移脚本
3. 测试覆盖率不得低于 80%
### 绝对不能做的事
1. ❌ 不要直接修改生产数据库
2. ❌ 不要在代码中硬编码密钥
3. ❌ 不要跳过测试直接提交
## 🔧 开发工具链
# 安装依赖
pip install -e ".[dev]"
# 运行测试
pytest tests/ -v --cov=src
# 代码格式化
ruff format src/ tests/
ruff check src/ tests/
# 启动本地开发服务器
python -m src.api.main
# 数据库迁移
alembic upgrade head
# Git 工作流
- 主分支:`main`
- 功能分支:`feature/描述`
- 提交格式:`type(scope): description`
- 需要 PR review 才能合并到 main
## 💡 AI 特定提示
1. **先读后改**:修改任何文件前,先读相关文档了解上下文
2. **小步提交**:每个逻辑单元完成后就提交
3. **测试先行**:写代码前先想好怎么测
4. **遇到不确定的事**:查看 `docs/` 目录下的相关文档,或者询问团队成员

Context‑handoff template (excerpt) :

# 上下文交接文档
> 由 AI 实例 #N 生成于 YYYY-MM-DD HH:MM
> 供 AI 实例 #(N+1) 接续工作时参考
---
## ✅ 已完成的工作
### 功能实现
- [x] 已完成的功能1(文件:path/to/file1.py)
- [x] 已完成的功能2(文件:path/to/file2.py)
- [ ] 正在进行的功能3(完成度:70%)
### 已修复的问题
- Fixed: 问题描述 → 解决方案摘要
## 📍 当前状态
### 正在进行的工作
**任务**:当前任务的描述
**所在文件**:`path/to/current_file.py`
**进度**:75%
**下一步具体动作**:精确描述接下来该干什么
## 📝 关键决策记录
| 决策 | 为什么这么做 | 替代方案及排除原因 |
|------|-------------|-------------------|
| 决策1 | 原因 | 排除了XX因为... |
| 决策2 | 原因 | 排除了XX因为... |
## ⚠️ 注意事项 / 已知的坑
1. 注意事项1:例如 - auth middleware 的顺序很重要
2. 注意事项2:例如 - 数据库表 X 和 Y 有外键关联
## 🔗 相关文件索引
| 文件 | 用途 | 最后修改时间 |
|------|------|-------------|
| `path/to/file1.py` | 用途 | HH:MM |
| `path/to/file2.py` | 用途 | HH:MM |
| `docs/xxx.md` | 用途 | - |
## 📅 下一步计划(按优先级排序)
1. **P0 - 必须**:最紧急的下一步
2. **P1 - 应该**:紧接着要做的
3. **P2 - 可以**:有余力时做的
4. **后续**:更长期的计划项
---
*请接手的 AI 实例先通读此文档,确认理解后再继续工作。*

Conclusion

The essence of context engineering is to solve the fundamental problem that "what the AI cannot see does not exist" by (1) placing all required knowledge inside a searchable repository, (2) giving the model a concise navigation map instead of an information dump, and (3) continuously monitoring context health to avoid overload and anxiety.

Next up, the series will explore multi‑agent collaboration architectures – how to let several AIs work together when a single agent is insufficient.

software architectureAILangChainOpenAIPrompt DesignContext Engineering
Qborfy AI
Written by

Qborfy AI

A knowledge base that logs daily experiences and learning journeys, sharing them with you to grow together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.