Agents
Configures shared AI coding agent settings across all tools.
What it does
- Installs the agent-motivated packages (
agents_packages:sd,shfmt,ast-grep,difftastic) - Scans the machine for the CLI tools agents reach for (
agents_tooling) and generates~/.agents/TOOLING.md, an inventory of what is installed - Generates
~/.agents/PROJECTS.md, a map of the machine'sprojectsinventory (the same entries the git role clones, plus per-project metadata), so agents know which repository a change belongs in - Syncs the machine's Agent Skills inventory (
agents_skills) into a tool-neutral local cache (~/.agents/skills/sources), which tool roles like claude materialize into their agent's discovery path - Generates
~/.agents/SKILLS.md, an overview of every skill in the synced sources (name, description, and the cachedSKILL.mdpath), so agents see the whole catalog, not just what their tool has installed - Deploys
~/.agents/worklog/README.md— cross-project worklog instructions - Adds a
worklogcommand that prints today's worklog (falling back to the most recent), orworklog YYYY-MM-DDfor a specific day - Configures
~/AGENTS.md, the universal agent instructions read by Copilot, Cursor, Windsurf, Claude Code, Codex, and others, rendering it on every apply from per-rule overridable data (agents_agentsmd_sections) or your own template
Demo
The payoff of the inventory files is that an agent knows its environment from the first turn, without probing. These prompts exercise that on a freshly applied machine, and each will grow an asciinema cast of the answer.
The agent reads ~/.agents/TOOLING.md and answers from it instead of guessing, naming the file as the source and using the tools it lists from that turn on:
What CLI tools do you have available and where did you get the list?Then it contrasts the inventory against the stock baseline, so the answer is grounded in what this role actually installed rather than a generic list:
What can you do better now than on stock Ubuntu?Agent-motivated packages
The human baseline (ripgrep, fd, tree, shellcheck, jq, yq) ships with the ubuntu / macos roles because people reach for those tools with or without agents. agents_packages is the tier on top: tools that are on the machine because coding agents use them.
| Package | Why agents want it |
|---|---|
ast-grep | structural AST search and rewrite where regex is fragile |
difftastic | syntax-aware diffs (difft), reviewing generated changes |
sd | find and replace with standard regex syntax, a saner sed |
shfmt | shell formatting to pair with shellcheck |
On macOS all four install as Homebrew formulae. On Ubuntu shfmt comes from apt, ast-grep and difftastic install from GitHub releases (apt carries neither), and sd follows the install-source policy — apt where it exists (24.04+), the GitHub release where it does not (22.04). Release installs track their version in versions.d and the upgrade history like the other release-installed tools. Trim the list per host, or set agents_packages: [] to keep the role config-only.
Install source
sd is the one package here with two real sources — apt carries it on Ubuntu 24.04+ but not on 22.04 — so agents_install_source decides which to use, inheriting the collection-wide dotpro_install_source:
dotpro_install_source: package_manager # collection default
agents_install_source: package_manager # this role (inherits the global)
agents_sd_install_source: package_manager # one tool (most specific wins)package_manager (the default) uses apt where it carries sd and falls back to the GitHub release where it does not, so a machine always ends up with a working sd. latest always takes the GitHub release (newer than apt's, and consistent across every distro). The most specific setting wins: tool over role over global. ast-grep and difftastic are GitHub-only and shfmt is apt-only, so they ignore the knob; macOS always uses Homebrew.
Tooling inventory
Agents discover CLI tools by trial and error: a command not found early in a session teaches the agent the tool is absent for the rest of it, and each probe costs a turn. ~/.agents/TOOLING.md removes the guesswork. Every apply checks each entry in agents_tooling with command -v, runs its version probe, and renders the tools that are actually present, each with its version, a title saying what it is, an optional usage summary, and a docs link the agent can fetch for option-level detail:
- `rg` 14.1.0 ripgrep, recursive regex search over directory trees, gitignore-aware and fast; prefer over `grep -r` (docs: https://github.com/BurntSushi/ripgrep)
- `fd` 9.0.0 fast, friendly file finder; prefer over `find` for name lookups (docs: https://github.com/sharkdp/fd)
- `shellcheck` 0.9.0 static analysis for shell scripts, catches quoting, portability, and logic bugs; run it on any shell script you write or edit (docs: https://www.shellcheck.net/wiki/)The example AGENTS.md tells agents to read the file, so they use the good tools from the first turn. A tool that is not installed is left out, so the inventory never advertises something that is missing; rerun with --tags tooling to refresh just this file.
List the agents role after your tool roles in the playbook. The scan inventories whatever is on the machine when it runs, so on a fresh machine a scan that runs before claude or googlecloud misses the CLIs those roles are about to install; the next apply would then regenerate the file (and report a change) instead of the first apply getting it right. dotpro doctor flags an agents role listed before other tool roles. The default list covers every CLI a role in this collection installs, from the system baseline (rg, fd, ast-grep, sd, jq, yq, tree, shellcheck, shfmt, difft, curl, …) through the tool roles (claude, dotpro, docker, kubectl, helm, kubeseal, k9s, terraform, tofu, ansible, ansible-lint, aws, gcloud, doctl, bw, starship, python3, uv, …), plus tools commonly present that no role installs yet (gh, glab, fzf, node, npm, make, go, cargo, scc, hyperfine, watchexec). Override agents_tooling to extend it. Per entry, title says what the tool is, summary, docs, and examples are optional (summary is the usage guidance rendered next to the title; examples renders common usage patterns as {cmd, summary} sub-bullets, for tools whose invocations an agent should copy rather than derive), and version overrides the probe for tools that do not speak --version:
agents_tooling_path: ~/.agents/TOOLING.md # default
agents_tooling:
- name: doctl
version: doctl version # default: `<name> --version`
title: "DigitalOcean CLI"
summary: "manages the droplets behind the staging fleet"
docs: https://docs.digitalocean.com/reference/doctl/
examples:
- cmd: "doctl kubernetes cluster list"
summary: "the clusters this account can reach"Project map
The tooling inventory tells agents what is installed; ~/.agents/PROJECTS.md tells them where work lives. Every apply renders it from the machine's projects inventory, the same entries the git role clones (with git_repositories honored as a fallback), one line per project grouped under a ## heading per first path segment (ungrouped projects list first), so an agent asked to fix something can find the right repository instead of guessing, and the map can never drift from what is actually cloned. Inside a repository, that repo's own AGENTS.md and README.md take over.
Beyond the clone keys (repository, path, clone), an entry's metadata renders after its path: title (a display name), description (what the project is), and website/docs (the deployed site and its documentation, the reverse lookup from a live property to its repository). A project with clone: false — or without a repository at all — is never cloned but stays listed, so agents still know it exists and where it lives. A key with a trailing slash describes the group itself (the directory, not a project in it): its title becomes the group's heading — the display name may carry spaces the directory name avoids — and the rest of its metadata renders as an intro line under it, telling agents what the client or company behind the group is.
projects:
baksteen/:
title: Baksteen Media
description: "personal company, open-source tooling"
baksteen/dotfilespro:
repository: git@gitlab.com:baksteen/dotfilespro.git
title: Dotfiles Pro
description: "Ansible collection managing machines and dotfiles"
website: https://dotfiles.pro## Baksteen Media
personal company, open-source tooling
- `~/projects/baksteen/dotfilespro` **Dotfiles Pro** Ansible collection managing machines and dotfiles website https://dotfiles.proThe example AGENTS.md points agents at the file for tasks that span repositories. Every metadata key is optional (entries without any render bare) and all are ignored by the clone itself. Rerun with --tags projects to refresh just this file, and set agents_projects_enabled: false to skip it:
agents_projects_enabled: true # default; false skips the file
agents_projects_path: ~/.agents/PROJECTS.md # defaultSkills
Agent Skills are directories with a SKILL.md that teach an agent a workflow, a convention, or a runbook; Claude Code, Cursor, Copilot, and other tools discover them from a per-tool path. agents_skills is the machine's skill inventory: each key is a git repository (public or private, any URL git can clone) mapped to all or the list of skill directories to take from it:
agents_skills:
git@github.com:example/skills.git: all
https://github.com/anthropics/skills:
- skill-creator
- mcp-builderEach apply syncs every source into agents_skills_path's sources/ layer (default ~/.agents/skills/sources), one flat host-owner-repo directory per source, pulling new commits on every run so a skill edit lands with the next apply. The cache itself is tool-neutral: tool roles materialize a selection into their agent's discovery path (the claude role's claude_code_skills defaults to the whole inventory and copies into ~/.claude/skills), so one declaration feeds every agent on the machine. Rerun with --tags skills to refresh just the cache, the overview, and the tool roles' copies.
A skill is a directory containing a SKILL.md, at the top level of its source repository or under a skills/ container directory (the anthropics/skills layout). When the container exists it is the repository's skills root and top-level candidates (a stray SKILL.md, a template/ skeleton) are ignored. When two sources carry the same skill name, the later source wins.
Skill overview
Alongside the cache the role generates ~/.agents/SKILLS.md, the skills counterpart of the tooling inventory and the project map: one line per skill found in the synced sources, grouped by source repository, with the skill's name, the first sentence of its SKILL.md description, and the cached SKILL.md path to read for the full instructions:
## https://github.com/anthropics/skills
- `mcp-builder` Guide for creating high-quality MCP (Model Context Protocol) servers. (~/.agents/skills/sources/github.com-anthropics-skills/skills/mcp-builder/SKILL.md)
- `skill-creator` Create new skills, modify and improve existing skills, and measure skill performance. (~/.agents/skills/sources/github.com-anthropics-skills/skills/skill-creator/SKILL.md)The overview lists the whole catalog, not one tool's selection, so an agent also sees the skills that are available but not installed for its tool. The example AGENTS.md makes it a layer on top of native skill invocation, never a replacement: skills the tool has installed keep triggering natively, and a task matching a skill the tool has not invoked gets offered (the agent names the skill, says what it covers, and asks before following its SKILL.md from the cached path):
agents_skills_index_enabled: true # default; false skips the file
agents_skills_index_path: ~/.agents/SKILLS.md # defaultYour AGENTS.md
The role renders ~/AGENTS.md from agents_agentsmd_src (the bundled templates/AGENTS.md.j2 by default) on every apply, so improvements to the template reach the file instead of drifting. It points agents at agents_tooling_path (~/.agents/TOOLING.md) and ~/.agents/worklog/README.md, so a fresh machine has working instructions from the first apply.
The shipped rules are a working baseline, not a skeleton to fill in: defaults distilled from real agent sessions, like flaw-triggered pushback instead of agreeing with everything, feature work on isolated worktrees, hard stops before commits, pushes, and destructive actions, and the pointers into the generated inventories above. The file is useful as-is from the first apply; from there you tune it a rule at a time instead of authoring it from scratch.
The content is data, not a wall of Markdown: the template loops over agents_agentsmd_sections, so you override it a rule at a time instead of forking the whole file. With hash_behaviour = merge your overrides deep-merge onto the shipped defaults, giving three tiers of control (a rule, a section, or the file):
agents_agentsmd_intro: Instructions for agents on the ACME fleet. # the preamble line
agents_agentsmd_sections:
code:
items:
tests_lint: "Run `make check` before finishing." # reword a shipped rule (in place, by key)
no_comments: false # drop a shipped rule (false or "")
no_secrets_log: "Never log secrets to stdout." # add a rule (new key, appended)
security: # add a whole section
title: Security
weight: 15 # orders it (General 10, Code 20)
items:
no_prod: "Never touch production without confirmation."Sections and items order by weight; an item is a plain string, or a text plus weight mapping when you need to slot it between shipped rules. A Jinja variable reference inside a value is still interpolated (that is how the tooling rule reaches agents_tooling_path).
A few shipped rules carry intent worth keeping when you reword them by key. The worktrees rule is deliberately tool-agnostic: an agent's native worktree support satisfies it just as well as the worktree CLI, which is one way to comply, not the requirement. The Ask first section is a separate tier on purpose: hard stops the agent must confirm before acting, not defaults it may apply on its own. That split is also why the commit rule lives in two keys: commit (never commit or push unless asked) is the hard stop, while propose_commit (state the proposed message and file list first) is the workflow guidance that applies once a commit is wanted.
For prose the section/rule structure cannot express, or to bypass the data model entirely, point agents_agentsmd_src at your own template, or set agents_agentsmd_enabled to false to leave ~/AGENTS.md to you:
agents_agentsmd_enabled: true # default; false leaves ~/AGENTS.md untouched
agents_agentsmd_path: ~/AGENTS.md # default
agents_agentsmd_src: AGENTS.md.j2 # default; role-relative or an absolute path to your own templateTo keep the file live and git-tracked in your own dotfiles repo, set agents_agentsmd_enabled: false and let the dotfiles role symlink it:
agents_agentsmd_enabled: false # stop the agents role configuring ~/AGENTS.md
dotfiles_list:
- AGENTS.md # ~/AGENTS.md -> ~/.dotfiles/AGENTS.md