ADR 0057 — The Copilot home is GitHub Copilot's store, configuration and hook root
- Status: Accepted
- Date: 2026-10-25
- Amends: ADR 0037 — stores: Copilot homes
- Related: ADR 0034 (ownership rule, unchanged), ADR 0056, ADR 0053, ADR 0026, ADR 0006
- Restructured: 2026-10-25
Context
Section titled “Context”VS Code 1.143 made the Copilot harness the default agent harness. That harness is the Agent Host, built on the Copilot SDK; the Local harness is being retired (VS Code 1.143 release notes, and the agent-harness and agent-host documentation). The Copilot harness, the Copilot CLI and JetBrains share one Copilot home: COPILOT_HOME, else ~/.copilot (%USERPROFILE%\.copilot on Windows). It holds:
- MCP servers in
mcp-config.json; - the always-on instructions in
copilot-instructions.md; - hooks in
hooks/*.json; - one folder per session in
session-state/<id>/, withevents.jsonl,workspace.yaml,vscode.metadata.json,plan.md,checkpoints/andfiles/.
An index database, session-store.db, is not needed.
Imprint’s Copilot adapter predated this:
- MCP. It registered in VS Code’s user
mcp.jsonunderservers. The Copilot harness reads the Copilot home instead. VS Code 1.143 offers to move a user’smcp.jsonservers intomcp-config.jsonand removes them frommcp.jsononce confirmed. - Instructions. It wrote
User/prompts/imprint.instructions.mdwithout front matter, so Copilot never applied it. - Hooks. It said Copilot had no hook system. Copilot reads
hooks/*.json({"version": 1, "hooks": {…}}), and its handlers havecommand(orbash/powershell) andtimeoutSec.- Event names in PascalCase receive Claude Code’s payloads (GitHub’s hooks reference).
Stopcarriessession_id,cwd,transcript_path,stop_reasonandstop_hook_active, which is whathook_entry.py stopreads.SessionStartcarriessession_id,cwdandsource. - For
SessionStartonly a top-leveladditionalContextis consumed. Imprint’s session-start hook prints Claude Code’shookSpecificOutput.additionalContext. - A
commandruns in PowerShell on Windows and in bash elsewhere.
- Event names in PascalCase receive Claude Code’s payloads (GitHub’s hooks reference).
- Learning.
imprint learndid not read Copilot.
A line of events.jsonl is {id, parentId, timestamp, type, data, agentId?, ephemeral?}:
user.messagedata:content(as shown to the user),transformedContent(the model’s XML), attachments,source(userfrom VS Code; skills and agents set others, such asskill-pdforagent-<id>),isAutopilotContinuationanddelivery.assistant.messagedata:content(markdown),toolRequests,reasoningText,encryptedContentandparentToolCallId.- Tool activity is in
tool.execution_*events. - The working directory is in
session.startandsession.resume(data.context.cwd, besidegitRoot,repositoryandbranch) and insession.context_changed(data.cwd). - Session ids can be strings the SDK caller chose, and the transcript’s file name,
events, is the same for every session.
Under Remote-WSL, the agent host and its hooks run inside the distribution, so its Copilot home is the distribution’s ~/.copilot. No Copilot conversations exist on the machine this was built on. Both Copilot homes hold only config.json and logs, and the Copilot CLI is not installed. The fixtures follow the SDK’s event schema (@github/copilot-sdk 1.0.23-preview.5, copilot-chat 0.71.0).
Decision
Section titled “Decision”The Copilot home is the store, the configuration and the hook root.
-
MCP. Setup registers
mcpServers.imprint = {"type": "stdio", "command", "args", "env", "tools": ["*"]}in<Copilot home>/mcp-config.json, keeping other servers and settings. It no longer writes VS Code’s usermcp.json, and disconnect removes Imprint’s old entry from it. In the VS Code 1.143.1 bundle:- the Local harness discovers
<Copilot home>/mcp-config.jsonbecausechat.mcp.discovery.enableddefaults tocopilot: true, and in a remote window it reads the remote host’s file; - the Agent Host resolves the same file as its global configuration;
- the customization migration moves
mcp.jsonservers into that file.
So every supported VS Code reads the Copilot home, and an entry kept in
mcp.jsonwould duplicate it and bring the migration prompt back after each setup. - the Local harness discovers
-
Instructions. Setup writes a managed Imprint block into
<Copilot home>/copilot-instructions.md, keeping the user’s text. Disconnect removes the block, and removes the old prompts file only while it is exactly the generated one. -
Hooks. Imprint owns
<Copilot home>/hooks/imprint.json, which Copilot reads beside the user’s hook files:SessionStartrunshook_entry.py copilot-session-startwith a 10-second timeout. It prints{"additionalContext": …}, the shape Copilot consumes. Thesession-startaction keeps Claude Code’s and Codex’s shape, ascursor-pre-compactalready keeps Cursor’s.Stoprunshook_entry.py stopwith a 120-second timeout and imports the transcript attranscript_path, as Claude Code’s and Codex’s Stop hooks do.- On Windows the commands use PowerShell’s call operator and quoting; elsewhere they use the existing POSIX quoting.
- Disconnect deletes the file. Like the instructions, it is protected while another runtime’s entry is in
mcp-config.json.
-
Detection. Copilot counts as installed when the Copilot home exists,
copilotis onPATH, or VS Code is installed, so CLI-only users are not skipped.
Store. Learn reads <Copilot home>/session-state/*/events.jsonl. The clients table names the store, on this computer and in a registered WSL profile (ADR 0056).
Evidence. A session’s recorded working directories are the cwd of its session.start and session.resume contexts and of its session.context_changed events, subagents’ events included, which is stricter. A session without one belongs nowhere (ADR 0034). workspace_policy.transcript_allowed is unchanged.
Parsing. Only user.message and assistant.message count, and their text is data.content:
- a user message counts when it has no
agentId, itssourceis absent oruser, and it is not an autopilot continuation; - an assistant message counts when it has no
agentIdand noparentToolCallId; - tool events, reasoning, encrypted content,
transformedContentand every other event are skipped.
Identity. The source key is conversation/<session id>, and the id is the session folder’s name. The documented layout names the folder after session_id, so the key needs no read. The project comes from derive_project of the first recorded cwd, as recorded.
WSL target. copilot is a WSL registration target.
- Probe. It reads
copilot=${COPILOT_HOME:-$home/.copilot}without sourcing startup files (ADR 0026) and recordscopilot_homewhen that directory exists. - Setup.
imprint wsl setup --target copilotwrites the bridge launcher<copilot_home>/imprint-wsl, theimprintentry inmcp-config.json, the managed instructions block, andhooks/imprint.json. The hook commands call'<launcher>' hook copilot-session-startand'<launcher>' hook stop. The bridge accepts the new action and translatescwdandtranscript_pathas it does for the other clients. - Learn. Learn reads
<copilot_home>/session-statein the distribution’s path context. - Removal. It removes the entry, the block, the hooks file and the launcher.
Rejected alternatives
Section titled “Rejected alternatives”- Learn the Local harness store (
workspaceStorage/*/chatSessions,emptyWindowChatSessions, mapped to folders throughworkspace.json). Its evidence belongs to a storage container rather than a session. Sessions are copied between containers, and one container mixes path contexts (local, WSL, SSH). That fails ADR 0034’s recorded-evidence rule and ADR 0037’s path contexts. The harness is being retired, and a session handed off to the Copilot harness becomes anevents.jsonlwith its context. - Register in both
mcp-config.jsonand VS Code’smcp.json. Duplicate servers are disabled bychat.mcp.collisionBehavior, and the migration prompt would return after every setup. Supported VS Code versions read the Copilot home anyway. - Hooks inline in
<Copilot home>/settings.json. That file is the user’s, and its handlers would need matching on removal. An Imprint-owned file in the hooks directory is written and removed whole. - One SessionStart output with both shapes. Neither Claude Code’s nor Codex’s tolerance of an unknown top-level key is documented, and a validation failure would drop their context.
- Find a missing
transcript_pathfrom the layout (session-state/<session_id>/events.jsonl). GitHub’s reference liststranscript_pathamongStop’s fields. A second way to locate transcripts would not be needed, because learn reads the store. - The shell-free
exec/argshook form. The reference documents it for the Copilot CLI only. - Key by
session.start’ssessionIdread from the events. It is the same id. The folder name needs no read and keeps the key when the start event is missing.
Consequences
Section titled “Consequences”- Not learned. Local-harness chats are not learned. Neither are Remote-SSH and dev-container sessions, whose Copilot home is on the remote host or in the container and is not registered.
- Supported versions. Supported versions are VS Code 1.143 and later, and Copilot runtime 1.0.x.
- An older VS Code that does not read the Copilot home loses Imprint once disconnect removes the old
mcp.jsonentry. Setup no longer refreshes that entry. - A user who turns off the Local harness’s discovery of the Copilot configuration does not see Imprint there.
- An older VS Code that does not read the Copilot home loses Imprint once disconnect removes the old
- Owned settings. Imprint owns
tools: ["*"]on its entry, so a narrowed tool list is reset by setup. - Stop hook. The Stop hook also runs
cli_extractandcli_summarize, which do not read Copilot events and store nothing for them (ADR 0037 records the same for Codex). - WSL. The WSL listing, the installer’s WSL page and Settings show a GitHub Copilot row.
Invalidation conditions
Section titled “Invalidation conditions”- If live testing shows
Stoppayloads withouttranscript_pathin VS Code’s harness or the CLI, the hook import must locate transcripts another way. - If Copilot stops reading the Copilot home for MCP servers, instructions or hooks, registration must follow.
- If Claude Code and Codex document that they ignore unknown top-level keys in SessionStart output, the separate
copilot-session-startaction could merge into one output with both shapes.
Validation
Section titled “Validation”-
Python.
tests/test_learn_stores.pycovers:- a Copilot home learned with only the user’s typed messages and the main agent’s answers. Skill messages, subagent messages, assistant messages inside a tool call, the autopilot continuation, reasoning, encrypted content, tool events and
transformedContentare left out. - a caller-chosen session id, and a session without a context recording nothing;
session.context_changedinto another workspace’s root, including a subagent’s, as ownership evidence;- Linux and Windows cwds naming their projects;
- a WSL Copilot home resolved through the bridge’s translator;
- the Stop hook’s import of an
events.jsonl, keyed by the session id.
- a Copilot home learned with only the user’s typed messages and the main agent’s answers. Skill messages, subagent messages, assistant messages inside a tool call, the autopilot continuation, reasoning, encrypted content, tool events and
tests/test_hooks.pychecks each client’s SessionStart shape, and that a PascalCaseStoppayload imports itstranscript_pathunlessstop_hook_activeis set.tests/test_wsl_bridge.pychecks the new action through the bridge.
The full suite passed on Linux.
-
Go.
setup_copilot_test.gochecks setup in a disposable home: nothing is written while Copilot is absent. The golden MCP entry, instructions block and hooks file are written beside unrelated servers, user text and user hooks, and a second run changes nothing. VS Code’smcp.jsonis not written, and the client reports connected. Disconnect removes the entry, block and hooks file, the old VS Code entry and the generated prompts file, and keeps an edited prompts file and another runtime’s hooks.internal/hooksruns the Copilot commands in bash and checks the top-leveladditionalContextandStop’s{}.wsl_test.gochecks the probe withCOPILOT_HOME, and registration, idempotence and removal of the Copilot WSL layout.clients_test.goandlearn_test.gocheck the target and its stores.
-
Native Windows, from WSL through interop, with temporary directories and the installed interpreter used read-only:
- The Go client, WSL, learn and cleanup tests passed in a cross-compiled Windows test binary.
- The hooks test ran the PowerShell commands, with spaces and apostrophes in the paths, in Windows PowerShell with the installed interpreter.
- The cross-compiled CLI’s probe of the real Ubuntu profile reported
copilot_home. imprint wsl setup --target copilotandremoveran against a disposable--homein the real distribution. The registered hook commands, run in bash as Copilot runs them, answeredSessionStartwithadditionalContextthrough the bridge. For a transcript without a context,Stopskipped the import after the bridge’s ownership check.- The installed interpreter, in the Ubuntu path context, found, keyed, named, owned and parsed a fixture session in that home.
-
Needs a live, logged-in Copilot:
- that Copilot applies the SessionStart context and the instructions file;
- that
Stoppayloads carrytranscript_pathin VS Code’s harness and the CLI; - that the WSL Agent Host runs the distribution’s hooks and MCP server;
- real sessions;
- VS Code, the CLI and JetBrains loading
mcp-config.json; - macOS.