Support directory-scoped AGENTS.md instructions - #1976
Conversation
Merge conflict needs a human
|
a230cdb to
f631a03
Compare
|
Review outcome: directory-scoped AGENTS.md is meaningful, but this draft changes instruction discovery, trust, symlink and prompt-budget behavior across 16 files and 1,130 lines, is conflicting, and still had full-suite timing failures. Keeping it as a draft for focused security/instruction-precedence review; not approved for merge in this sweep. |
jonathanKingston
left a comment
There was a problem hiding this comment.
Review: directory-scoped AGENTS.md instructions
Verdict: changes needed. CI has never run on this head, so I ran the gate locally on f631a03 and it passes. The blockers are a runtime-cost problem in the discovery cache and a rebase (191 commits behind, one mechanical conflict), not broken code.
Validation on the head
| Check | Result |
|---|---|
pnpm run typecheck |
pass |
pnpm run lint |
pass |
prettier --check on the 15 changed files (this branch predates oxfmt) |
pass |
pnpm run check:dead-code |
pass |
pnpm run check:oracle |
pass (229 specs live, 15 invariants hold) |
pnpm run check:e2e-syntax |
pass |
pnpm test -- project-instructions agent-service agent-system-prompt settings-sources context-estimate |
93/93 pass (after rebuilding node-pty for Linux; the initial load failures were environmental) |
pnpm run check:screenshots |
228 stale, an artefact of the 191-commit gap |
| WebdriverIO spec | not run, no Electron binary in this sandbox |
Dry merge with origin/main |
one conflict: src/main/services/agent-service.ts at the buildSystemPrompt call, where main inserted the invokedAgent block from #1860. Nothing this PR touches was moved into a package, and main has had no nested-instruction work in this area since the merge base. |
Blocking
-
A tree walk in front of nearly every file tool call.
NESTED_DISCOVERY_CACHE_MS = 1_000(project-instructions.ts:34) meansactivateNestedInstructionSources(:536) re-walks up to 10,000 directories with sequentialreaddirawaits on everyread_file,list_dir,search_code,write_fileand the other mapped tools (agent-service.ts:377-388, wrapper at:1672-1701) whenever more than a second has passed since the previous call. The same walk runs on every composer context estimate (context-estimate.ts:60now passes the draft) and on every Settings open (refreshNestedDiscovery: trueinregister-handlers.ts). On a large monorepo an agent reading 30 files in a turn pays 30 multi-thousand-readdir walks. Discover once per turn, or key the cache on something that changes (mtime, a watcher), rather than a 1s TTL. -
Rebase and get CI on the head before this leaves draft. The conflict resolution is mechanical.
Security
- Nested files inherit workspace trust with no per-file gate. Once the root is trusted, any directory without a
.git(vendored trees, examples, fixtures) can contribute system-prompt text. The agent can alsowrite_filea nestedAGENTS.mdand have it injected on the next read under that directory within the same turn. Thetrust="untrusted"envelope applies, so this extends the existing surface rather than adding a new class, but there is no transcript signal whenagent-service.ts:1688appends to the leading system message mid-turn; the only visibility is Settings → Sources, for the latest turn, in process memory. Please emit a transcript notice when a block is injected. - Symlink, traversal, NUL and out-of-root handling look correct and are tested: symlinked directories are never walked, a symlinked
AGENTS.mdmust realpath inside the canonical root (:124-153), context paths are realpath-checked via an existing ancestor (:232-257). Bounds (depth 16, 10k dirs, 200 files, 32KB per file, 8 files / 64KB active) are sensible, but truncation is silent: alphabetical DFS stops at the caps, so anAGENTS.mdin a later-sorted tree is never applied and nothing reports it. Log it and mark it in Sources..gitignoreis not honoured; the skip list is hardcoded.
Non-blocking
- Mutating the leading system message mid-turn invalidates provider prompt-cache prefixes on every activation and bypasses the budget computed by
prepareAgentHistory(up to +64KB). - The deferred edit (
agent-service.ts:1695-1699) returns a success-shaped string; if the model does not retry, ormaxStepsis hit, the edit silently never happens. docs/user/project-instructions.mdoverclaims: activation only happens for the ten built-in tools inINSTRUCTION_CONTEXT_PATH_FIELDS;run_shellwrites, ACP agents andexploreor todo-worker subagent reads do not trigger it. Narrow the wording.deduplicateSources(:301-316) drops a nested file whose content equals the root file from the Sources list entirely.lastNestedActivationByProjectRootis keyed per project root, so concurrent threads in one project overwrite each other's "active this turn" state.
No as casts beyond as const in tests, no suppressions, no exported type predicates, no object-literal casts. The e2e spec and its reference PNG are present and the oracle sees the spec.
Generated by Claude Code
|
Pushed four commits. e0933bb merges Generated by Claude Code |
🖥️ PR preview
|
|
Readiness review: per-turn memoization avoids repeated discovery on every tool, but the first look of every turn still performs a fresh serial tree walk (up to 10,000 directories), bypassing the 30-second discovery cache. I am holding this draft for a large-repository turn-start latency measurement and an agreed invalidation/indexing strategy; the existing focused correctness tests do not establish that cost. |
|
Current audit: the latest precheck failure is an npm-registry socket timeout during Keep draft and avoid spending CI on a rebase until that design/evidence exists. #1354 already tracks the feature, so no new issue is needed; the next useful work is the bounded large-repository benchmark/investigation recorded here. |
4be167d to
1afad97
Compare
The nested AGENTS.md walk was cached for one second, so a turn re-walked up to 10,000 directories before nearly every file tool call. A turn now creates a discovery memo (`createNestedInstructionTurn`) that the system prompt build seeds with one fresh walk; every `activateNestedInstructionSources` call of that turn reuses it. Callers outside a turn (the composer estimate) share a 30 s cache, and Settings still forces a reload. An edit tool whose path is an AGENTS.md invalidates both the memo and the shared cache after it runs, so the next file tool call sees the file the agent wrote, moved, or removed. `run_shell` writes are not observed. Each mid-turn activation now adds a one-line transcript notice naming the file, emitted as a text chunk at the next step boundary rather than between a tool call and its result (a text chunk there would start a new assistant bubble and strand the tool card). Discovery records when it stopped at a directory, file, or depth cap: project sources carry `discoveryTruncated` and the walk logs a warning. A nested file that repeats an already-listed source stays listed and is marked `duplicateOf` instead of being dropped; its content is still injected once. The latest-activation record for Settings is now keyed per thread under the project root, so concurrent threads no longer overwrite each other; Settings reads the most recent thread. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011jaQbed96h8aSZLijfiHNB
…urces `instructions:list` passes `duplicateOf` and `discoveryTruncated` through to the renderer. Sources → Instruction files marks a nested file whose text repeats a listed one with a quiet "duplicate" badge and says which file carries it, and appends a single note under the list when nested discovery stopped at its cap, so a missing deep file no longer looks unwritten. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011jaQbed96h8aSZLijfiHNB
Name the built-in file tools that bring a path into context and say that `run_shell` writes, ACP agents, and subagent reads do not activate a nested AGENTS.md. Describe the once-per-turn discovery, the re-walk after the agent writes an AGENTS.md, the transcript notice, the duplicate badge, and the truncation note in Sources. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011jaQbed96h8aSZLijfiHNB
1afad97 to
cc4252e
Compare
…s shape `ProjectInstructionSummary` gains a required `trusted: boolean` alongside the optional `scopePath` / `duplicateOf` / `discoveryTruncated`. A new *optional* result field would have been additive, but a required one is not, so `gen-api-protocol.mts --compare-ref` classifies `instructions:list` and `client.instructions.list` as breaking and fails the gate at version 2. The gate did not exist when this branch forked; it arrived on `main` with the protocol freeze, so this is the first run that has had to answer for the shape change. Bumping is the right side of the trade: `trusted` is a real property of every instruction source, and weakening it to optional purely to stay additive would put a lie in the type. The manifest is regenerated with `gen:api-protocol`, not edited: only its `version` moves, because the manifest records channel arity and binding names, which this change leaves alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BCcGaKPRL3fmRpLR3rK5WX
cfd2d06 to
d61678f
Compare
Reference screenshots ready for reviewReview GitHub’s image diffs in screenshot PR #2621. Rendered for If this source branch moves, a later successful render closes the stale review PR and replaces this link. |
Directory-scoped AGENTS.md files activate the root-to-nearest instruction chain when prompts or tools reference a path. First edits into a new scope are deferred once so the agent sees its guidance before writing. Trust, canonical path boundaries, nested checkout/generated-directory exclusions, file-size and prompt budgets remain enforced. Settings shows active, scoped and duplicate sources and reports incomplete inventories.
Turn-time discovery reads only referenced ancestor scopes, memoized within the turn. It caches missing files, invalidates discovery after file-tool changes to AGENTS.md, and starts fresh next turn. Already-injected instructions remain fixed for that turn. Settings retains its bounded full inventory. A repeatable real-activation benchmark over 10,000 directories measured 596–773 ms before and 5–14 ms after on this macOS host; these are local measurements, not platform-wide guarantees. The strategy and benchmark are committed.
This branch incorporates #2597 to order API changes: PR activity uses v4 and nested-instruction metadata uses v5. Merge #2597 first.
Validation:
Closes #1354
Latest integration validation: merged main through
92d2330cb, fixed same-millisecond activation ordering with a monotonic sequence, and verified the frozen-clock regression fails before the fix and passes afterward. The completepnpm run checkpassed: 8,845 tests, 7 skipped, zero failures. Build and the focused nested-instruction Electron eval passed again; screenshot inspected.