You install a second AI coding tool, paste the same rules into yet another settings panel, and three weeks later one copy has drifted. Keep instructions and skills in your git-backed dotfiles, then symlink every tool home at that single source of truth.
The layout below is a worked example from my private ~/dotfiles. Treat the paths and install.sh mapping as a portable pattern to copy into your own repo — private or public — not something to clone from mine.
install.shMost agent tools look for instructions under their own home directory — something like ~/.grok/, ~/.copilot/, ~/.cursor/, or ~/.claude/. Skills usually live in a skills/ folder next to that. If you copy-paste the same markdown into each place, you own N versions of the truth.
A cleaner pattern:
~/dotfiles (tracked, reviewed, pushable — your repo).The repo holds the canonical files; install.sh creates the symlinks each tool expects.
A practical agents-focused tree looks like this (concrete names from my setup — yours can differ):
~/dotfiles/
agents/AGENTS.md # global agent instructions (always-on)
agents/PYTHON.md # on-demand topic sibling
agents/STREAMING.md
agents/DOCS.md
agents/skills/ # shared skills (each folder has SKILL.md)
watch-prs/
watch-slack/
bash/bash_aliases # shell aliases
bash/bash_functions # helpers (e.g. gmem)
configs/ # app configs (ghostty, grok, …)
install.sh # symlinks into each tool home
Everything AI tools need lives under agents/. Shell helpers and app configs are included so a new machine is not only “agent rules” but a usable shell. Secrets stay out of plaintext git (encrypted or local env files the installer never publishes). Tool-local state — sessions, auth caches — stays outside the repo.
Note: The private clone URL of someone else’s dotfiles is not your bootstrap step. Put the same layout in your git-backed
~/dotfiles, then point each tool at it with an installer like the one below.
install.shEach tool expects a slightly different filename or skills path. The installer builds a small hub so one 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 |
~/.grok/skills |
~/dotfiles/agents/skills |
~/.claude/skills |
same skills folder |
~/.cursor/skills |
same skills folder |
~/.copilot/skills |
same skills folder |
~/.agents/skills/<name> |
~/dotfiles/agents/skills/<name> (per skill) |
How that plays out per tool:
~/.grok/Agents.md → hub; skills via ~/.grok/skills → the shared tree. Optional: symlink ~/dotfiles/configs/grok → ~/.grok/config.toml so /settings writes land in git.~/.copilot/copilot-instructions.md → hub; skills via ~/.copilot/skills.~/.cursor/skills → the same tree (Cursor also picks up in-repo AGENTS.md when you open a project).~/.claude/skills → the same tree.So there is one hub file (~/.agents/AGENTS.md) and several tool-specific names that all resolve to it. Skills are shared the same way: most agent homes get a directory symlink at the whole agents/skills tree. The ~/.agents/skills home is special — it can also hold extras that are not in the repo — so the installer creates a real directory there and links each skill by name instead of replacing the whole folder.
Non-AI pieces that usually ship with the same installer:
| Path | Points to |
|---|---|
~/.bash_functions |
~/dotfiles/bash/bash_functions |
~/.bash_aliases |
~/dotfiles/bash/bash_aliases |
~/.config/ghostty/config.ghostty |
~/dotfiles/configs/ghostty |
~/.grok/config.toml |
~/dotfiles/configs/grok |
Verify the instruction chain if you like:
➜ ls -l ~/.agents/AGENTS.md ~/.grok/Agents.md ~/.copilot/copilot-instructions.md
➜ ls -l ~/.grok/skills ~/.cursor/skills ~/.claude/skills ~/.copilot/skills
You want every one of those to be a symlink that eventually lands under ~/dotfiles/agents/.
Put the layout in your git-backed ~/dotfiles (private is fine; public works too if you keep secrets out). Then an installer that symlinks — illustrate with example paths, not someone else’s clone URL:
➜ # your repo already at ~/dotfiles (clone/pull however you normally do)
➜ ~/dotfiles/install.sh
A minimal install.sh pattern (no secrets, same idea as the full script):
#!/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"
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 stays deliberately simple: for each destination it either creates a symlink, replaces an existing symlink, or moves a real file/dir out of the way to a timestamped .bak.… backup before linking. Missing sources should fail loudly instead of half-installing.
After it finishes you should see a short report of what was linked (instructions hub, skills homes, shell helpers, a couple of app configs).
Note: Keep secrets out of plaintext git. Token files belong under something like an encrypted
secrets/tree or a local env file the installer never symlinks into the world.
Because the destinations are links, you edit the repo (or any symlink path into it) 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 are refreshed with ln -sfn; you only get a backup when something real is sitting where a link should be.
Skills follow the same rule. Drop a folder under ~/dotfiles/agents/skills/<name>/ with a SKILL.md (examples in my tree: watch-prs, watch-slack), re-run install.sh, and Grok / Claude / Cursor / Copilot all pick up the same skill tree without a per-tool copy step.
A practical bootstrap order:
~/dotfiles, then run ~/dotfiles/install.sh.~/.zshrc (or equivalent) sources the linked aliases/functions and puts any CLI you care about on PATH.install.sh if a Dock or launcher step was skipped because a binary was not on PATH yet.~/Projects. Global rules come from the dotfiles hub; per-repo nuance lives in that project’s own AGENTS.md.In short: one repo for the files you maintain, one installer that creates the symlinks, many tools reading the same content. Next time you add an agent, check whether it already has a home in install.sh — and if not, add one symlink instead of another pasted instructions file.