Skip to content

feat(coding-agent/context): added nested AGENTS.md context injection on file reads#2668

Open
metaphorics wants to merge 1 commit into
can1357:mainfrom
metaphorics:ultra/nested-agents
Open

feat(coding-agent/context): added nested AGENTS.md context injection on file reads#2668
metaphorics wants to merge 1 commit into
can1357:mainfrom
metaphorics:ultra/nested-agents

Conversation

@metaphorics

Copy link
Copy Markdown
Contributor

Summary

Adds a pure ancestor-AGENTS.md injection module (root-to-leaf walk, realpath strict-containment, per-session dedup cache, byte-budgeted truncation, directory-context formatting) wired into the read tool: a successful local file read appends the content of AGENTS.md files in directories strictly between the project root and the file, once per directory per session. The repo-root AGENTS.md is never injected and raw reads are skipped. Gated by nestedAgents.enabled.

Tests

Pure up-walk / containment / dedup / truncate / format regression test; bun check green.

Note: touches config/settings-schema.ts and docs/settings.md alongside #2655 and #2661; rebase if flagged.

Closes #2660

…on file reads

- Added a pure context/nested-agents-md module (root-to-leaf ancestor walk, realpath strict-containment, per-session injection cache, byte-budgeted UTF-8 truncation, directory-context formatting) ported and omp-adapted from senpi.
- Wired it into the read tool: a successful local file read appends the content of AGENTS.md files in directories strictly between the project root and the file, once per directory per session. The repo-root AGENTS.md is never injected (already in the system prompt) and raw reads are skipped.
- Added nestedAgents.enabled (UI toggle, default on) plus config-only nestedAgents.maxBytesPerFile/maxBytesPerRead budgets; documented in the read tool description and settings docs.
Copilot AI review requested due to automatic review settings June 15, 2026 13:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for injecting nested AGENTS.md directory context into successful read results (once per directory per session), with settings + documentation and a new context-injection module.

Changes:

  • Inject ancestor AGENTS.md content on non-raw file reads with per-session caching and byte budgets.
  • Introduce nested-agents-md context module (containment, discovery, truncation, formatting, injection cache) plus tests.
  • Add settings (nestedAgents.*) and document the new behavior in prompts/docs/changelog.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/coding-agent/src/tools/read.ts Hooks nested AGENTS.md injection into file read results with per-session cache
packages/coding-agent/src/prompts/tools/read.md Documents nested context injection behavior for the read tool
packages/coding-agent/src/context/nested-agents-md/* Implements discovery/injection/truncation/cache + tests for nested AGENTS.md
packages/coding-agent/src/config/settings-schema.ts Adds nestedAgents.enabled and byte-budget settings
packages/coding-agent/CHANGELOG.md Notes the new nested context injection feature
docs/settings.md Mentions the new nestedAgents.* settings group

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2437 to +2456
if (!isRawSelector(parsed) && this.session.settings.get("nestedAgents.enabled") !== false) {
const injection = await injectDirectoryContext({
filePath: absolutePath,
rootDir: this.session.cwd,
cache: getNestedAgentsCache(this.session),
sessionKey: NESTED_AGENTS_SESSION_KEY,
config: {
maxBytesPerFile: this.session.settings.get("nestedAgents.maxBytesPerFile"),
maxBytesPerRead: this.session.settings.get("nestedAgents.maxBytesPerRead"),
},
});
if (injection.injectedText) {
const firstText = content.find((c): c is TextContent => c.type === "text");
if (firstText) {
firstText.text += injection.injectedText;
} else {
content.push({ type: "text", text: injection.injectedText });
}
}
}
Comment on lines +42 to +63
let injectedText = "";
let bytesBudget = config.maxBytesPerRead;

for (const agentsPath of candidates) {
const agentsDir = path.dirname(agentsPath);
if (input.cache.hasInjected(input.sessionKey, agentsDir)) continue;
if (bytesBudget <= 0) break;

let content: string;
try {
content = await Bun.file(agentsPath).text();
} catch (error) {
errors.push({
path: agentsPath,
error: new InjectionFileReadError(agentsPath, error),
});
continue;
}

const perFileCap = Math.min(config.maxBytesPerFile, bytesBudget);
const truncated = truncateBytes(content, perFileCap);

Comment on lines +72 to +79
injectedText += formatDirectoryContext({
absolutePath: agentsPath,
content: truncated.result,
truncated: truncated.truncated,
});
input.cache.markInjected(input.sessionKey, agentsDir);
bytesBudget -= truncated.resultBytes;
}
Comment on lines +22 to +26
const decoder = new TextDecoder("utf-8", { fatal: false });
let decoded = decoder.decode(bytes.subarray(0, maxBytes));
while (decoded.endsWith(REPLACEMENT_CHAR)) {
decoded = decoded.slice(0, -1);
}
Comment on lines +50 to +53
let content: string;
try {
content = await Bun.file(agentsPath).text();
} catch (error) {
Comment on lines +4 to +7
constructor(path: string, cause: unknown) {
const message = cause instanceof Error ? cause.message : String(cause);
super(`Failed to read ${path}: ${message}`);
this.name = "InjectionFileReadError";

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7796873474

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// and the file) once per session. Skips raw reads — verbatim output must stay
// clean — and the repo-root AGENTS.md, which findAgentsMdUp excludes (the
// system prompt already carries it).
if (!isRawSelector(parsed) && this.session.settings.get("nestedAgents.enabled") !== false) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move nested-context injection before bridge returns

When an ACP client advertises fs.readTextFile, plain and multi-range local file reads return the in-memory bridge result before reaching this new injection block, so nestedAgents.enabled has no effect in those sessions. That skips scoped AGENTS.md instructions for the ACP path that is explicitly treated as the source of truth for open editor buffers; apply the injection before returning bridge-backed local file results or centralize it for all local sourcePath results.

Useful? React with 👍 / 👎.

@@ -0,0 +1,34 @@
import { realpath } from "node:fs/promises";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a namespace import for fs/promises

AGENTS.md for this repo requires namespace imports for Node filesystem modules, including node:fs/promises, but this new file imports realpath as a named binding. Please switch to import * as fs from "node:fs/promises" and call fs.realpath(...) to keep the package within the documented convention.

Useful? React with 👍 / 👎.

Comment on lines +45 to +48
for (const agentsPath of candidates) {
const agentsDir = path.dirname(agentsPath);
if (input.cache.hasInjected(input.sessionKey, agentsDir)) continue;
if (bytesBudget <= 0) break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve deepest AGENTS.md under budget

Because candidates are processed root-to-leaf and the loop stops once the per-read byte budget is exhausted, a large or merely numerous set of higher-level AGENTS.md files can prevent the most specific child directory's instructions from being injected on the first read of a file in that child. Since nested instructions are the ones that actually govern that subtree, reserve budget for deeper files or prioritize the leaf contexts so local rules are not omitted.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

context: nested AGENTS.md injection on file reads

2 participants