Context Files
Inject markdown context files into your agent's instructions automatically
Context files automatically inject markdown content into your agent’s instructions — drop an AGENTS.md in your project root and every run sees it, no config needed.
from praisonaiagents import Agent
agent = Agent(name="project-agent", instructions="Follow project AGENTS.md guidelines.")
agent.start("Implement the feature using our team conventions.")The user adds AGENTS.md or CLAUDE.md; discovery merges layered markdown into the agent instructions.
Quick Start
- Drop an AGENTS.md in your project root
markdown # AGENTS.md ## Company Guidelines - Always be professional and helpful - Focus on customer satisfaction - Use clear, concise language ## Product Information Our main products include: - AI Assistant Platform - Workflow Automation Tools - Run from any subdirectory — instructions load automatically
bash # From the project root praisonai run "Help a customer" # Or from a nested package — the root AGENTS.md is still found cd packages/frontend praisonai run "Build a login form"Or use the Python API:
python from praisonaiagents import Agent from praisonai.integration import configure_host agent = Agent( name="Customer Support", instructions="Help customers with their questions" ) # No context_paths needed — auto-discovery picks up AGENTS.md configure_host(agents=[agent], style="dashboard")
How It Works
load_context_files() walks up from your current directory to the git root, collecting candidates at each level and concatenating them with the nearest file winning (appended last):
The merged content is prepended to the agent’s instructions:
{original_instructions}
Context:
{layered_context_from_all_levels}On-demand Subtree Attachment
On-demand attachment picks up per-subtree AGENTS.md / CLAUDE.md files the up-front walk-up missed, only when a file inside that subtree is actually touched.
- Zero-config in a monorepo
When your host wires the attacher into the CLI auto-loader, on-demand attachment runs for you — drop a
packages/foo/AGENTS.mdand it appears the first time the agent edits a file insidepackages/foo/. If your host has not wired it in yet, reach for the stateless helper or the session class below. - Attach for a single path (stateless)
python from praisonaiagents import Agent from praisonai.integration.context_files import ( load_context_files, load_context_files_for_path, ) up_front = load_context_files() # existing walk-up from cwd to root agent = Agent( name="Monorepo Refactorer", instructions=up_front + "\n\n" + load_context_files_for_path( "packages/foo/main.py", already_loaded=up_front, ), ) agent.start("Refactor packages/foo/main.py to use the new logger.") - Attach across many touches (session-scoped)
python from praisonaiagents import Agent from praisonai.integration.context_files import ( load_context_files, PathContextAttacher, ) up_front = load_context_files() attacher = PathContextAttacher(already_loaded=up_front, max_chars=8000) for path in ["packages/foo/main.py", "packages/foo/util.py", "packages/bar/api.py"]: extra = attacher.attach_for_path(path) if extra: # feed `extra` into the agent (e.g. via instructions or a system message) ... - Wire it into an agent (auto-attach as tools run)
Register the hook on an agent and the nearest
AGENTS.mdattaches automatically the first time the agent reads or edits a file in a new subtree — no manualattach_for_path()calls.python from praisonaiagents import Agent from praisonaiagents.hooks import HookEvent, HookRegistry from praisonai.integration.context_files import ( load_context_files, build_subtree_context_hook, file_tool_matcher, ) up_front = load_context_files() hooks = HookRegistry() hooks.register_function( HookEvent.AFTER_TOOL, build_subtree_context_hook(already_loaded=up_front), matcher=file_tool_matcher(), ) agent = Agent( name="Monorepo Refactorer", instructions=up_front, hooks=hooks, ) agent.start("Refactor packages/foo/main.py to use the new logger.")The hook fires after each file tool, reads the touched path from
filepath,file_path,path,filename, ortarget_file, and returns the nearest subtree rules asadditional_contextthe first time that subtree is touched. Because it reads any of those keys, the hook fires correctly for both the wrapper file tools and the ACP-shaped tools without configuration.Diagram sequenceDiagram participant User participant Agent participant Tool participant Hook as AFTER_TOOL hook<br/>(build_subtree_context_hook) participant Attacher as PathContextAttacher User->>Agent: start("edit packages/foo/main.py") Agent->>Tool: edit_file(file_path="packages/foo/main.py") Tool-->>Agent: result Agent->>Hook: AFTER_TOOL(tool_input) Hook->>Attacher: attach_for_path("packages/foo/main.py") Attacher-->>Hook: packages/foo/AGENTS.md Hook-->>Agent: additional_context Agent-->>User: reply (now aware of foo's rules) classDef user fill:#6366F1,stroke:#7C90A0,color:#fff classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff classDef tool fill:#189AB4,stroke:#7C90A0,color:#fff classDef hook fill:#F59E0B,stroke:#7C90A0,color:#fff classDef attach fill:#10B981,stroke:#7C90A0,color:#fff class User user class Agent agent class Tool tool class Hook hook class Attacher attach
Behaviour guarantees
| Guarantee | Plain language |
|---|---|
| Subtree file attached only after a file under it is touched | Nothing loads until the agent opens something in that folder. |
| Files already in the up-front load are not re-attached | No duplicate rules from what loaded at startup. |
| A file emitted for one directory is not re-emitted for another | The same rules file never shows up twice. |
| Repeated touches of the same directory hit the cache | Re-opening a folder costs nothing — no disk re-read. |
A sibling directory with no AGENTS.md returns an empty string | Empty folders add nothing. |
Total emitted text is bounded by max_chars | The attached context can never grow past the budget. |
load_context_files_for_path(...) is a stateless one-shot | Use it when you touch a single path. |
Returns "" when nothing new is found | Silent when there is nothing to add. |
When do I need on-demand attachment?
CLI Auto-loading
As of PraisonAI PR #2358, praisonai chat, praisonai run, praisonai code, and praisonai tui automatically discover and inject AGENTS.md-style project context — no flag required. Each command wires two things: the up-front load_context_files() merge that prepends the walk-up context to the system prompt, and the on-demand build_subtree_context_hook attach that fires the first time the agent touches a file under a nested AGENTS.md. Parity for run (including run --agent … and run --profile …) landed in PR #3552 — before that, only chat registered the subtree hook.
sequenceDiagram
participant User
participant CLI as praisonai CLI
participant Loader as load_context_files
participant Hook as build_subtree_context_hook
participant Agent as Agent backstory
User->>CLI: praisonai chat / run / code / tui
CLI->>Loader: walk_up=True, cwd=workspace
Loader-->>CLI: merged context (≤8000 chars)
CLI->>Agent: prepend "# Project Context\n{context}"
CLI->>Hook: register AFTER_TOOL hook (unless --no-rules)
Note over Hook: fires on first touch under a nested AGENTS.md
Agent-->>User: response with project awareness
classDef user fill:#6366F1,stroke:#7C90A0,color:#fff
classDef cli fill:#8B0000,stroke:#7C90A0,color:#fff
classDef loader fill:#189AB4,stroke:#7C90A0,color:#fff
classDef hook fill:#F59E0B,stroke:#7C90A0,color:#fff
classDef agent fill:#10B981,stroke:#7C90A0,color:#fff
class User user
class CLI cli
class Loader loader
class Hook hook
class Agent agentZero-config in a monorepo: from the repo root, praisonai run "refactor packages/foo/bar.py" picks up packages/foo/AGENTS.md the moment it opens a file there — the same behaviour chat has always had.
Beyond auto-discovered AGENTS.md / CLAUDE.md, teams can declare extra sources — files, globs, or URLs — via the top-level instructions: config key or the praisonai run --instructions flag. Declared sources load up front alongside the walk-up and dedup against the same files. See Instruction Sources.
- As of PraisonAI PR #4338,
praisonai run(and the resident TUI’schat/codeinpraisonai-code) register the subtree hook standalone — nopraisonai_bot/praisonaineeded. The CLI falls back to the residentpraisonai_code.integration.context_filescopy when the optional packages are absent. Previously the hook was a silent no-op without those packages.
Opt-out
- CLI flag
Pass
--no-contextto any of the four commands to skip all project-context loading for that invocation:bash praisonai chat --no-context praisonai run --no-context "Quick one-off task" praisonai code --no-context - Environment variable
Set
PRAISON_NO_CONTEXT=truein your shell or CI environment to disable auto-loading globally:bash export PRAISON_NO_CONTEXT=true praisonai run "task" # context skipped - InteractiveConfig field
When using the interactive core programmatically:
python from praisonai.cli.interactive.config import InteractiveConfig config = InteractiveConfig(no_context=True) - configure_host() param
In host integrations, pass
no_context=Truedirectly or viaagent_kwargsfor backward compatibility:python configure_host(no_context=True) # or configure_host(agent_kwargs={"no_context": True})
Opt-out matrix
Two independent switches gate the two mechanisms: --no-context / PRAISON_NO_CONTEXT skips the up-front load_context_files() merge, while --no-rules / PRAISON_NO_RULES skips the on-demand build_subtree_context_hook attach. Both switches work on a standalone pip install praisonai-code — the subtree hook is now registered standalone via the resident praisonai_code.integration.context_files copy (PR #4338), so these opt-outs apply even without praisonai_bot / praisonai.
| Surface | Opt-out for up-front load (load_context_files) | Opt-out for subtree hook (build_subtree_context_hook) |
|---|---|---|
| CLI flag | --no-context | --no-rules |
| Env var | PRAISON_NO_CONTEXT=true | PRAISON_NO_RULES=true (accepts 1 / true / yes, case-insensitive) |
InteractiveConfig field | no_context=True | no_rules=True |
configure_host() param | no_context=True (or via agent_kwargs) | no_rules=True (or via agent_kwargs) |
Token budget and truncation
The default budget is 8000 characters. When discovered context exceeds the budget, it is truncated and \n... [project context truncated] is appended. Override via:
InteractiveConfig(context_token_budget=16000)for interactive sessionsconfigure_host(context_token_budget=16000)for host integrations
PRAISON_CONTEXT_BUDGET — integer character budget for the subtree hook (build_subtree_context_hook(max_chars=…)). Applies to both interactive (chat) and non-interactive (run) CLI. PRAISON_CONTEXT_BUDGET=0 — or a missing / non-integer value — disables the cap on the CLI subtree hook. Set a positive integer to bound the total subtree text emitted across the session.
Configuration Options
Default File Discovery
When no context_paths is given, these filenames are checked at every directory level:
| Path | Description |
|---|---|
CLAUDE.local.md | Local overrides (highest precedence, git-ignored) |
AGENTS.md | Primary agent context file (Codex CLI compatible) |
agents.md | Alternative casing |
.agents/AGENTS.md | Hidden directory structure |
CLAUDE.md | Claude Code memory file |
GEMINI.md | Gemini CLI context file |
Discovery Scope
| Layer | Location | Precedence |
|---|---|---|
| User-global | ~/.praisonai/AGENTS.md | Lowest (loaded first) |
| Git/project root | Root-level candidates | Low |
| Intermediate dirs | Each dir between root and cwd | Medium |
| cwd | Current working directory candidates | Highest (loaded last) |
| Touched-file subtree (on-demand) | Nearest AGENTS.md/CLAUDE.md above the touched file | Appended after all up-front layers |
walk_up Parameter
| Value | Behavior |
|---|---|
True _(default)_ | Walk up to git/project root, include ~/.praisonai/AGENTS.md |
False | Read only from cwd, skip global file |
The walk_up parameter is ignored when you pass an explicit paths list.
Explicit Paths (Backward Compatible)
Pass context_paths to skip auto-discovery entirely — files are read from cwd only:
configure_host(
context_paths=[
"docs/guidelines.md",
"config/style-guide.md",
"knowledge/product-info.md"
]
)Choosing a Mode
Context Loading Behavior
from praisonai.integration.context_files import load_context_files
# Auto-discovery (default): walks up, includes global file
context = load_context_files()
# Restrict to cwd only (no walk-up, no global)
context = load_context_files(walk_up=False)
# Explicit paths: cwd-only, no walk-up (backward compatible)
context = load_context_files(paths=["AGENTS.md", "STYLE.md"])De-duplication is automatic: if cwd equals the git root, the same file is read only once (matched by inode, safe on case-insensitive volumes).
New public helpers
Two helpers wire the attacher into an agent’s hooks so subtree rules attach automatically as file tools run.
| Helper | Signature | Purpose |
|---|---|---|
build_subtree_context_hook | (already_loaded=None, *, max_chars=8000) -> Callable | AFTER_TOOL hook that auto-attaches subtree instruction files as file tools run. Session-scoped. |
file_tool_matcher | () -> str | Regex that limits the hook to file-touching tool names. Matches wrapper defaults (read_file, write_file, edit_file, list_files), ACP tools (acp_create_file, acp_edit_file, acp_delete_file), and interactive-core / Claude-Code-style names (read, edit, write, apply_patch, str_replace, multi_edit, search_replace). Pass as matcher= when registering. |
Options for build_subtree_context_hook:
| Option | Type | Default | Description |
|---|---|---|---|
already_loaded | Optional[str] | None | Up-front instruction text (used only to seed dedup so files already loaded at session start are not re-attached). |
max_chars | int | 8000 | Character budget across the whole session; 0 disables it. |
File Structure Examples
Basic AGENTS.md
# Agent Context
## Role Definition
You are a customer support specialist with deep knowledge of our products.
## Communication Style
- Be friendly and professional
- Use active voice
- Keep responses under 150 words unless detailed explanation needed
## Knowledge Areas
- Product features and pricing
- Technical troubleshooting
- Account management proceduresMonorepo Layering
Place org-wide guidelines at the repo root and package-specific overrides closer to the working directory:
monorepo/
├── AGENTS.md # Org-wide guidelines (lowest project precedence)
├── packages/
│ ├── frontend/
│ │ └── AGENTS.md # Frontend overrides (higher precedence)
│ └── backend/
│ └── AGENTS.md # Backend overrides (higher precedence)Running from inside packages/frontend/ automatically merges both files — root guidelines first, frontend overrides last (winning):
cd packages/frontend
praisonai run "Add a login form"
# Loads: monorepo/AGENTS.md → packages/frontend/AGENTS.mdNo context_paths configuration needed.
User-Global Instructions
~/.praisonai/AGENTS.md applies across all your projects as the lowest-priority layer:
# ~/.praisonai/AGENTS.md
## Personal Preferences
- Always respond in British English
- Prefer async/await over callbacks
- Include type hints in all Python codeEvery project you work in inherits these preferences unless overridden by a project-level file.
Common Patterns
Environment-Specific Context
import os
from praisonaiagents import Agent
from praisonai.integration import configure_host
agent = Agent(name="Assistant", instructions="You are helpful.")
env = os.getenv("ENVIRONMENT", "development")
context_files = ["AGENTS.md"]
if env == "production":
context_files.append("production-guidelines.md")
else:
context_files.append("dev-guidelines.md")
configure_host(agents=[agent], context_paths=context_files)Monorepo with sibling subtrees
Start a session at the repo root, then let the agent open files in different packages — each package’s own AGENTS.md attaches the moment the agent touches a file there.
monorepo/
├── AGENTS.md # Org-wide guidelines
└── packages/
├── foo/
│ ├── AGENTS.md # Foo conventions
│ └── main.py
└── bar/
├── AGENTS.md # Bar conventions
└── api.pyfrom praisonai.integration.context_files import (
load_context_files,
PathContextAttacher,
)
up_front = load_context_files() # session starts at the repo root
attacher = PathContextAttacher(already_loaded=up_front)
attacher.attach_for_path("packages/foo/main.py") # attaches packages/foo/AGENTS.md
attacher.attach_for_path("packages/bar/api.py") # attaches packages/bar/AGENTS.md
attacher.attach_for_path("packages/foo/util.py") # cached — no disk re-read, nothing newThe agent picks up each package’s conventions the moment it opens a file there.
Conditional Context Loading
def get_context_files(agent_type):
base_files = ["AGENTS.md"]
if agent_type == "support":
base_files.append("support-protocols.md")
elif agent_type == "sales":
base_files.append("sales-playbook.md")
return base_files
configure_host(context_paths=get_context_files("support"))CI / Snapshot Testing
When you don’t want ancestor files to influence a test run, disable walk-up:
from praisonai.integration.context_files import load_context_files
# Only reads files in the current test directory
context = load_context_files(walk_up=False)Best Practices
File Organization
Structure your context files for maintainability:
context/
├── base/
│ ├── guidelines.md # Universal principles
│ └── style.md # Communication style
├── roles/
│ ├── support.md # Role-specific context
│ └── sales.md # Role-specific context
└── knowledge/
├── products.md # Product information
└── policies.md # Company policiesContent Structure
Keep context files focused and well-organized:
# Context File Template
## Core Role
Brief description of the agent's primary function
## Guidelines
- Key behavioral guidelines
- Communication principles
- Quality standards
## Knowledge Areas
Relevant domain knowledge the agent should reference
## Examples
Sample interactions or responses when helpfulWhen to use walk_up=False
Disable walk-up for isolated CI jobs, snapshot tests, or any run where you want to guarantee only the files in the current directory are loaded — no ancestor directories, no global user file.
context = load_context_files(walk_up=False)Content Management
Version control your context files:
# Track context changes
git add AGENTS.md STYLE.md
git commit -m "Update agent communication guidelines"
# Review context impact
git diff HEAD~1 context/Use PathContextAttacher in long-running sessions
When the agent will touch many files across a monorepo, create one PathContextAttacher per session and reuse it. The per-directory cache means repeated touches of the same subtree cost nothing, and the char budget stops the attached context from growing unbounded across the run. Pass already_loaded=up_front (the string returned by load_context_files()) so files discovered up front are not re-attached.
from praisonai.integration.context_files import (
load_context_files,
PathContextAttacher,
)
up_front = load_context_files()
attacher = PathContextAttacher(already_loaded=up_front, max_chars=8000)Prefer the hook for interactive/agentic sessions
For interactive sessions where the agent decides which files to touch, register build_subtree_context_hook() on HookEvent.AFTER_TOOL instead of calling attach_for_path() manually. The hook fires exactly when a file is touched, so the agent gets the nearest subtree rules just-in-time without you having to predict its path list up front. Pair with matcher=file_tool_matcher() so the hook skips non-file tools cheaply.
from praisonaiagents.hooks import HookEvent, HookRegistry
from praisonai.integration.context_files import (
load_context_files,
build_subtree_context_hook,
file_tool_matcher,
)
up_front = load_context_files()
hooks = HookRegistry()
hooks.register_function(
HookEvent.AFTER_TOOL,
build_subtree_context_hook(already_loaded=up_front),
matcher=file_tool_matcher(),
)Testing Context Changes
Validate context injection in development:
from praisonai.integration.context_files import load_context_files
context = load_context_files()
print("Loaded context length:", len(context))
print("Preview:", context[:200])