Install a second AI coding tool, paste the same rules into another settings panel, and a few weeks later one copy has drifted.
Instead, keep instructions, skills, and optional bot roles in a git-backed ~/dotfiles repo, then symlink every tool home at that single source of truth.

Agent tools look for instructions under a home like ~/.grok/ or ~/.copilot/. Skills usually sit in a skills/ folder next to that. Copy-paste the same markdown into each place and you suddenly own N versions of the truth.
A cleaner approach:
~/dotfiles (tracked, reviewed, and pushable).Canonical files live in the repo; install.sh creates the symlinks each tool expects. Named bots and scheduled routines can sit in the same tree, so a Bot machine and a laptop share one edit path.
Keep agent content under agents/, with the installer beside it:
~/dotfiles/
agents/
AGENTS.md # global always-on instructions
RULES.md # edit gate + memory routing (on demand)
REVIEW.md # topic sibling loaded when needed
hosts/copilot.instructions.md # product-only overlay
skills/watch-prs/SKILL.md # recurring procedures
bots/blogger.md # optional named roles
install.sh # symlinks into each tool home
Always-on rules live in AGENTS.md. Topic siblings, host overlays, skills, and bots load by scope so that file stays thin. Secrets never enter plaintext git. Tool-local state — sessions, auth caches — stays outside the repo.
Note: Put this layout in your git-backed
~/dotfiles, then point each tool at it with an installer like the one below.
Symlinks only help if lessons land in the right file. Packing every correction into the always-on file overloads the agent’s context, so earlier rules get crowded out and forgotten. Scattering copies across tool homes brings drift back.
| Scope | Where it lives | When it applies |
|---|---|---|
| Global instructions | agents/AGENTS.md |
Always, every repo, every tool |
| Edit gate / memory routing | agents/RULES.md |
Before editing any agents/ file or routing a lesson |
| Topic siblings | agents/<TOPIC>.md |
When the catalog trigger matches |
| Per-remote notes | agents/repos/<basename>.md |
When that git remote is the work |
| Host overlays | agents/hosts/<product>.… |
That product only |
| Skills | agents/skills/<name>/SKILL.md |
Recurring procedures |
| Named bots | agents/bots/<role>.md |
That bot’s instruction field + routines |
| Project instructions | <repo>/AGENTS.md |
Only that checkout |
Treat rules as a self-learning loop, not a style guide you rewrite by hand. After a correction, the agent proposes a home and a short lesson, then waits — you approve before anything hits git.
Take a real correction: “this repo uses trunk-based PRs — stop opening long-lived feature branches.” That is a fact about one checkout, so it belongs in that project’s workspace memory (or that repo’s own AGENTS.md), not in the shared always-on file. A cross-tool habit like “always name the git remote and branch” goes in agents/AGENTS.md. A Copilot-only quirk goes under agents/hosts/ and links only into that product. A Monday procedure becomes a skill folder.
Each approval writes one lesson into one scoped file. Over weeks the hub gets sharper without swelling into a wall of text that crowds out what the agent actually needs.
Tools expect slightly different filenames. The installer builds a small hub so one shared file feeds many names:
| Path the tool looks at | Points to |
|---|---|
~/.agents/AGENTS.md |
~/dotfiles/agents/AGENTS.md |
~/.grok/Agents.md |
~/.agents/AGENTS.md |
~/.copilot/copilot-instructions.md |
~/.agents/AGENTS.md |
~/.copilot/instructions/dotfiles.instructions.md |
~/dotfiles/agents/hosts/copilot.instructions.md |
~/.grok/skills |
~/dotfiles/agents/skills |
Take Grok as the worked example. The installer links ~/dotfiles/agents/AGENTS.md into ~/.agents/AGENTS.md, then points ~/.grok/Agents.md at that hub. Skills follow the same pattern: ~/.grok/skills → ~/dotfiles/agents/skills. Other agent homes get the same treatment with their own destination names — still one source file.
When a product needs an overlay, link it only into that product. Copilot’s host file, for example, lands at ~/.copilot/instructions/dotfiles.instructions.md and never touches the shared hub.
Verify the chain:
➜ ls -l ~/.agents/AGENTS.md ~/.grok/Agents.md
➜ ls -l ~/.grok/skills
Every path should be a symlink that eventually lands under ~/dotfiles/agents/.
Not everything durable is a rule. When a lesson is a fact about what is true — not how to work — it belongs in the host’s memory, not in markdown instructions.
| Kind | Home |
|---|---|
| Instruction (how to work) | agents/*.md — global, topic sibling, or repos/<basename>.md |
| Recurring procedure | the skill that should load when it recurs |
| Fact, many repos | host built-in global memory |
| Fact, one repo | host built-in workspace memory |
| Ephemeral | host session scope if it has one; otherwise drop |
Keep one home per lesson. If it already lived elsewhere, delete the copy. Never retain secrets, ephemeral errors, or unchosen ideas in durable memory.
Prefer the host’s built-in memory API when the tool has one. A shell helper like gmem is optional navigation, not a parallel database.
Skills are folders under ~/dotfiles/agents/skills/<name>/ with a SKILL.md. They are the right home when the next session should load a procedure you would otherwise re-explain every Monday.
Take watch-prs as the example. The folder holds a small contract the agent can read: which remotes to scan, what counts as actionable, how to report. Drop it once under agents/skills/watch-prs/, re-run install.sh, and every linked tool sees the same skill.
Keep the skill focused on the procedure. Cross-cutting always-on exceptions still belong in global AGENTS.md. Durable lessons that are not a procedure belong in memory, not in the skill body forever.
If your stack supports named agents with an instruction field and scheduled jobs, the same repo can own those roles.
A minimal shape:
agents/bots/
AGENTS.md # how an agent edits this roster
README.md # apply steps and workers
blogger.md # one role file per bot
skills/ # background workers
Each role file is YAML frontmatter, standing instructions, then a ### History snapshot that is never applied as the live prompt. Host jobs live under ## Routines. Apply is two steps: instruction field = text above History minus Routines; host jobs = each routine copied word-for-word from git.
You do not need named bots to benefit from the symlink hub. When you add them, they become another consumer of the same tree — still one repo, still one installer.
Put the layout in your git-backed ~/dotfiles and run an installer that creates the symlinks:
➜ ~/dotfiles/install.sh
Minimal install.sh pattern:
#!/usr/bin/env bash
set -euo pipefail
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
link_path() {
local src="$1" dest="$2"
mkdir -p "$(dirname "$dest")"
if [[ -L "$dest" || ! -e "$dest" ]]; then
ln -sfn "$src" "$dest"
elif [[ -f "$dest" || -d "$dest" ]]; then
mv "$dest" "${dest}.bak.$(date +%Y%m%d%H%M%S)"
ln -sfn "$src" "$dest"
else
echo "error: $dest exists and is not a file, dir, or symlink" >&2
exit 1
fi
}
AGENTS_SRC="$REPO_DIR/agents/AGENTS.md"
AGENTS_HUB="${HOME}/.agents"
mkdir -p "$AGENTS_HUB" "${HOME}/.grok" "${HOME}/.copilot"
link_path "$AGENTS_SRC" "${AGENTS_HUB}/AGENTS.md"
ln -sfn "${AGENTS_HUB}/AGENTS.md" "${HOME}/.grok/Agents.md"
ln -sfn "${AGENTS_HUB}/AGENTS.md" "${HOME}/.copilot/copilot-instructions.md"
COPILOT_HOST="$REPO_DIR/agents/hosts/copilot.instructions.md"
if [[ -f "$COPILOT_HOST" ]]; then
mkdir -p "${HOME}/.copilot/instructions"
link_path "$COPILOT_HOST" "${HOME}/.copilot/instructions/dotfiles.instructions.md"
fi
SKILLS_SRC="$REPO_DIR/agents/skills"
for home in "${HOME}/.grok/skills" "${HOME}/.claude/skills" \
"${HOME}/.cursor/skills" "${HOME}/.copilot/skills"; do
link_path "$SKILLS_SRC" "$home"
done
The script either creates a symlink, replaces an existing one, or moves a real file aside to a timestamped backup before linking. Host overlays link only when the file exists.
Note: Keep secrets out of plaintext git. Token files and wake URLs belong in a local env file the installer never publishes — never in role files.
Because the destinations are links, you edit the repo and every tool that reads that path sees the change on the next session:
➜ $EDITOR ~/dotfiles/agents/AGENTS.md
➜ cd ~/dotfiles
➜ git add -p
➜ git commit -m "Tighten global agent safety rules"
➜ git push origin main
On another machine:
➜ cd ~/dotfiles && git pull origin main
➜ ~/dotfiles/install.sh
Re-running the installer is cheap. Existing symlinks refresh with ln -sfn; you only get a backup when something real sits where a link should be. Skills and bot roles follow the same push-then-pull path.
~/dotfiles, then run ~/dotfiles/install.sh.PATH.install.sh if a binary was missing earlier.~/Projects — global rules from the hub, per-repo nuance in that project’s own AGENTS.md.~/.env.In short: one repo for the files you maintain, one installer that creates the symlinks, and many tools reading the same content. Next time you add an agent, add one symlink instead of another pasted instructions file.