Skip to content

Brief Copilot through COPILOT_CUSTOM_INSTRUCTIONS_DIRS instead of a prompt preamble - #360

Merged
scgopi merged 2 commits into
mainfrom
feat/copilot-instructions-dirs
Sep 15, 2026
Merged

scgopi merged 2 commits into
mainfrom
feat/copilot-instructions-dirs

Conversation

@scgopi

@scgopi scgopi commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Summary

Copilot loops now receive the graph briefing as a system-level custom instruction through COPILOT_CUSTOM_INSTRUCTIONS_DIRS, instead of a "Before anything else, read the briefing file at …" preamble on every opening prompt. The source recorded that variable as ignored. It is not: Copilot searches the listed directories for *.instructions.md files, and the earlier measurement only tried AGENTS.md and .github/copilot-instructions.md.

Why the old finding was recorded, and why it was wrong

  • The July measurement was real. Copilot's own session logs from 2026-07-28 (~/.copilot/session-state, copilotVersion: 1.0.75) show a working-directory AGENTS.md control that loaded, and the same file in a directory named by the variable that did not.
  • Both files it tried are repository instruction names. copilot help environment only says "custom instructions files", so the negative result looked like the variable being ignored.
  • Re-measured with a sentinel per file, reading the recorded system.message for each session rather than trusting the model's answer (140 pinned runs, 0 tool calls, 0 misattributed sessions):
Layout in the variable's directory 1.0.75 1.0.82 1.0.83 1.0.84-1 1.0.84-3 1.0.84-5 1.0.84-8
Control: AGENTS.md in cwd
AGENTS.md
.github/copilot-instructions.md
copilot-instructions.md, CLAUDE.md
instructions/x.instructions.md (with or without applyTo)
.github/instructions/x.instructions.md, x.instructions.md
Comma-joined after another directory (both load)
Alongside a cwd AGENTS.md (both load)
Through graphcode's zsh -i -l -c 'exec env …' launch shape
Singular COPILOT_CUSTOM_INSTRUCTION_DIRS
  • --resume rebuilds the system message from the resuming process's environment (measured on 1.0.84-5 and 1.0.84-8): without the variable a resumed session loses the instructions, with it they return. So resume paths carry it too.
  • In an interactive -i session on 1.0.84-5, /instructions lists graphcode.instructions.md [external] with its path.

Changes

  • SessionBriefing.write also writes instructions/graphcode.instructions.md beside AGENTS.md (same text; the AGENTS.md beside it is not loaded by Copilot, so nothing doubles).
  • Every Copilot launch prefixes env COPILOT_CUSTOM_INSTRUCTIONS_DIRS="${COPILOT_CUSTOM_INSTRUCTIONS_DIRS:+$COPILOT_CUSTOM_INSTRUCTIONS_DIRS,}<briefing dir>": appended after whatever the user's login shell already set, and $HOME/… on a remote host. Covers the daemon's fresh, shed and resume launches and the app's attach and resume, local and remote.
  • Remote delivery adds the .instructions.md copy for Copilot sessions only.
  • Copilot's opening prompt no longer carries the briefing pointer. --add-dir for the briefing directory stays.
  • The SessionBriefing doc comment now records what is true and why the earlier finding was recorded.

Not changed

  • Claude Code, Codex, OpenCode and pi launch argv are byte-identical to main (below).
  • The per-node wake-digest pointer ("Read your loop memory at …") still rides the prompt for every backend. It is per-node, and the briefing directory is shared per project.
  • Context cost is about the same (the ~10.6 KB briefing is in context either way); this is about reliability, not tokens.

Test plan

  • Gate on 45cd9477 (= pushed head), private DerivedData: make test's xcodebuild -scheme graphcode test exit 0 — 1832 tests / 195 suites / 0 failures. The literal make test target cannot run in a worktree (generate depends on build-ghostty, which needs the submodules), so its recipe was run directly.
  • make check: exit 0 (swiftlint 0 serious, swift-format clean).
  • SwiftPM build of GraphcodeKit, cli and daemon: exit 0; scripts/cli-smoke.sh against it: exit 0.
  • Argv parity against main, dumped from both trees across every command builder (launchArguments, ZmxSessionLauncher.arguments/resumeArguments local and remote, remoteEnsureInvocation, the app's launchPrefix/agentCommand/resumeCommand/sessionEnvironment/remoteCommand, summary and title invocations) × goal/time/turn × three briefing settings: 186 shapes per backend, 0 differences for Claude Code, Codex, OpenCode and pi. Every Copilot difference is the inserted env prefix, the removed pointer, or the delivery manifest's added file.
  • Mutation check: dropping the Copilot guard in briefingEnvironment and delivering the copy to every backend makes xcodebuild test exit 65 (onlyCopilotIsGivenTheVariable, aRemoteCopilotLoopIsDeliveredTheCopyItSearchesFor, aResumedCopilotSurfaceIsBriefedAgain, codexRidesTheSameWayCopilotDoes) and the parity diff flag 102–114 shapes on each other backend; restored before the gate.
  • New CopilotInstructionsDeliveryTests expand the variable in a real zsh: unset → <dir>, /mine,/theirs/mine,/theirs,<dir>.

Checklist

  • I have read the Contributing Guidelines
  • I have signed off my commits (git commit -s) per the DCO
  • Tests pass locally (make test)
  • Code follows the existing style (make check)

…reamble

Copilot searches the directories that variable lists for *.instructions.md
files, not for AGENTS.md. The July measurement that concluded the variable was
ignored ran on 1.0.75 with a working cwd control, but only tried AGENTS.md and
.github/copilot-instructions.md. Re-measured on 1.0.75 through 1.0.84-5, an
instructions/*.instructions.md under a listed directory lands in the system
message every time.

The briefing directory now carries that copy, every Copilot launch (fresh,
resumed, local, remote, daemon and app) appends the directory to the user's own
value, and the opening prompt no longer carries the read-this-file pointer.
--add-dir stays.
@scgopi
scgopi merged commit 3c764cf into main Sep 15, 2026
1 check passed
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.

1 participant