agentleFS
hams-ollohams-ollo/zen-agent-skills/AGENTS.md

zen-agent-skills

AGENTS.md2 starsChanged 25 days agoMITView on GitHub

Canonical rules for every AI agent working in this repository (Claude Code, Cursor, Codex, OpenCode, and any tool that reads AGENTS.md). This is the single source of truth. CLAUDE.md imports this file with @AGENTS.md, so Claude Code loads it at launch; any .cursor/rules/*.mdc or .github/copilot-instructions.md here is a thin pointer back to it. The reasons and incidents behind these rules are kept in docs/rationale/agents-md.md, so this file can stay short. This repo is Zen Agent Skills: a portable, cross-harness library…

The file

We split this AGENTS.md into the rules it gives an agent

4 rules. About 0 are common, 0 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Do not scan the whole .tasks/ directory, read other agents' task files, or read ROADMAP.md/CHANGELOG.md unless your task cites them.

    line 17
  2. Very specific:

    Run python scripts/validate-skills.py to lint every skill against these rules.

    line 66
  3. Very specific:

    Follow .agents/rules/house-style.md for writing and formatting: no em-dashes, sentence-case headings, named sources, relative markdown …

    line 118
  4. Very specific:

    Fetch with urllib and digest with hashlib: Never use an agent's web-fetching tool.

    line 186

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

AGENTS.md: rules for the Zen Agent Skills repository

Canonical rules for every AI agent working in this repository (Claude Code, Cursor, Codex, OpenCode, and any tool that reads AGENTS.md). This is the single source of truth. CLAUDE.md imports this file with @AGENTS.md, so Claude Code loads it at launch; any .cursor/rules/*.mdc or .github/copilot-instructions.md here is a thin pointer back to it. The reasons and incidents behind these rules are kept in docs/rationale/agents-md.md, so this file can stay short.

This repo is Zen Agent Skills: a portable, cross-harness library of agent skills, plus the tooling to install them into any project and any AI coding tool. It dogfoods its own system: this repository is tracked with the same init-worktracking skill it ships. Strategy lives in ROADMAP.md; atomic work lives in .tasks/; completed work is logged in CHANGELOG.md.


0. Agent reading protocol (read this first)

To keep context windows small and relevant, an agent assigned a task reads exactly:

  1. This file (AGENTS.md) in full.
  2. Its one assigned task file in .tasks/<id>.md.
  3. Only the files that task names in its touched_files frontmatter, plus any file the task body explicitly points to.

Do not scan the whole .tasks/ directory, read other agents' task files, or read ROADMAP.md/CHANGELOG.md unless your task cites them. If your task's depends_on lists another task not yet in .tasks/done/, stop and report the blocker.

1. What this repository is

A skills library, not an application. The deliverables are the skills under .agents/skills/ and the tooling under scripts/ that distributes them. Success is measured by whether a skill is portable (works across harnesses), self-contained (needs no hidden context), and genuinely used and iterated on.

Not every script distributes a skill. Some tooling under scripts/ serves the maintainer of this repository and is not a deliverable: install.py never places it, no adopter receives it, and the portability contract in the portability-contract section therefore does not reach it. It is still bound by the stdlib rule in the conventions section, so it runs on a bare Python 3 with no install step like everything else here.

2. Layout

PathHolds
.agents/skills/The skills. One directory per skill, each with a SKILL.md harness-agnostic body.
.agents/rules/The swappable lenses skills compose: house-style.md for writing and formatting, review-quality.md for the review rubric and severities, autonomy.md for what an agent may do when nobody is watching, and, in A10, for what it may do with material it did not author on any run at all. Each lens carries paths frontmatter so Claude Code loads it only when a skill reads it; core.md is the one always-loaded file, one line per rule, held under a line budget by validate-skills.py. Adopters may replace any of them. Shipped alongside the skills by install.py, which places them in ~/.claude/rules/, because a skill that references a lens is not self-contained without it.
.agents/hooks/Optional runtime guardrails (Python on stdin), each a reminder (injects context, never blocks) or a gate (blocks, only when the condition is decidable from the payload). The only thing the kit ships that runs inside an adopter's session, so installation is opt-in and activation is theirs: install.py --with-hooks places the files and prints the registration rather than editing anyone's settings. Contract in .agents/hooks/README.md.
scripts/Two kinds, and which kind a thing is matters more than the list. Distribution tooling, a deliverable, which ships the kit: install.py, build-adapters.py, validate-skills.py, check-provenance.py, run-checks.py, check-citations.py. Maintainer tooling, which reaches no adopter tree and which install.py never places: observatory/, a local reporting surface over this repository's own session corpus, contracted by agent-observatory.md. What decides the kind is whether an adopter receives it, not where it sits, and the stdlib rule in the conventions section governs both. Membership is the term here, not the enumeration: a script added or retired needs no amendment to this row, per the precedent chore-0049 set when a gate count went stale the first time anyone added a gate.
.tasks/Atomic, agent-assignable work items for building this kit, plus validate.py.
docs/spec/Behavioral specifications (the contracts), plus the reports that sit beside them, one file kind per question asked: <spec>.conformance.md audits code against the contract, <spec>.verification.md records a verdict with evidence, <spec>.readiness.md records a go/no-go gate over a spec plus its task decomposition, <spec>.characterization.md records behavior pinned before a contract existed, <spec>.runbook.md tells a person how to perform a step no agent here can perform.
tests/The kit's own tests, derived from the specifications under docs/spec/.
ROADMAP.mdThe strategic plan: which skills get built, in what order (builder-facing).
CHANGELOG.mdAppend-only ledger of finished work.
docs/CATALOG.mdThe reader-facing narrative catalog (what the kit offers, for people browsing it).

ROADMAP.md and docs/CATALOG.md overlap by design: CATALOG.md is the narrative for readers of the kit; ROADMAP.md is the execution order for whoever is building it. When they disagree, ROADMAP.md is authoritative for what happens next.

3. Work altitude model and lifecycle

AltitudeTierLives in
30,000 ftEpic (a tier of the catalog)ROADMAP.md sections
10,000 ftFeature (one skill)ROADMAP.md forward-plan items
1,000 ftTask.tasks/<id>.md
100 ftacceptancethe task file's mechanical criteria

A task file is the 1,000-foot decomposition of one roadmap Feature; its parent: links back up. Only decompose a Feature into task files when it is actually about to be built. Speculative task files for far-off skills rot and are dishonest about touched_files; keep those at the ROADMAP.md altitude until their turn.

Task lifecycle open -> in_progress -> done. On completion: confirm the acceptance command passes; confirm every depends_on is in .tasks/done/; run doc-sync over the reader-facing documents and apply or dismiss each finding; when the task declares a spec, bring that contract's conformance matrix up to date, or record the deferral; move the file to .tasks/done/ with status: done and re-anchor its relative links for the extra directory level; add one dated line to CHANGELOG.md; strike the roadmap Feature through if it is complete.

A spec is earned, not a default. The spec chain (spec-author through spec-conformance) is for behaviour a person must agree before it is built. A task that changes no behavioural contract declares no spec and closes with the steps above minus the conformance one. The sizing rule adopters get is in the README under "Choosing a path".

Why the re-anchoring, doc-sync, and conformance steps are in that list, each with the incident that put it there, is in the rationale. On the conformance step: a task that declares a spec closes by producing or updating that spec's <spec>.conformance.md with spec-conformance, over the scenarios its scenarios field claims. Deferring is legal, and for a forward spec it is the only honest answer: a scenario nothing has built is recorded as not-built with what is owed, and a spec with no implementation at all keeps its matrix owed, said plainly in the task rather than invented. A matrix written to satisfy this step is worse than no matrix.

4. How a skill is structured

Each skill is a directory under .agents/skills/<name>/ containing:

  • SKILL.md (required): YAML frontmatter (name, description) then a Markdown body, the single harness-agnostic source of truth. It must not depend on any one tool's features. The description says both what the skill does and when to use it, and leads with the key use case in the third person: Claude Code drops long descriptions first when its skill listing overflows, so validate-skills.py warns over 700 characters. Aim under ~500 lines; push detail into referenced files.
  • templates/, references/, scripts (optional): supporting files, referenced one level deep.

Run python scripts/validate-skills.py to lint every skill against these rules.

Two body shapes, both valid

The body takes one of two shapes, decided by how the skill is consumed. Neither is preferred and neither should be retrofitted onto the other.

  • Workflow skills carry a procedure: ordered steps an agent executes, usually with sections for when to use it, when not to, inputs, the procedure itself, and notes. Most skills are workflows.
  • Lenses carry Intent, Workflow, and an Output heading instead, because they are composed into another skill rather than run on their own. Giving a lens a step-by-step procedure invites an agent to run it standalone, which is the one thing it is not for.

A skill's shape is declared in the skill and enforced by validate-skills.py, not listed here. A lens says so in its own body ("This skill is a lens" or "This is a lens"), and the validator warns when the headings under that declaration are a workflow's. Inferring the shape from the headings instead would make every skill conform by construction.

The history of this rule, including the test-quality warning it leaves standing on purpose, is in the rationale.

Every skill points at every universal lens

Whatever its shape, a skill must reference each lens that declares itself universal, because a lens is swappable: an adopter who replaces it is silently ignored by any skill that never points at it. Two do so today, house-style.md and autonomy.md, each saying **Scope: universal.** in its own opening. review-quality.md is topical and is composed only by the skills that review, which is correct: requiring the rest to name a rubric they never apply is the noise that gets a rule ignored.

Universality is declared in the lens and enforced by validate-skills.py, not listed here. A lens that wants the stronger rule says so in its opening; one that says nothing is topical and is still covered by the at-least-one rule.

The enforcement is new; how the two halves drifted apart before it is in the rationale.

Which conventions govern depends on what the skill writes:

  • A skill that produces its own output (a report, a review, a spec) follows this repository's house-style module.
  • A skill that writes into a target repository (init-worktracking, new-task, pr-describe, and the agent prompts fix-batch dispatches) follows that repository's conventions instead, and names this kit's module only as the fallback when the skill is run here. Importing this kit's voice into a repo that did not choose it is a defect, not a nicety.

A skill that does both says so, and says which applies where.

5. Portability contract (the whole point)

  • AGENTS.md is canonical in any repo this kit scaffolds. Cursor, Codex, and OpenCode read it natively. Claude Code reads only CLAUDE.md when one exists, so the CLAUDE.md this kit scaffolds imports AGENTS.md with @AGENTS.md; a sentence asking Claude to read the file is followed only when the model decides to open it.
  • Write once, adapt thin. A skill's logic lives only in its SKILL.md. Any per-harness file is generated by scripts/build-adapters.py, never hand-maintained in parallel.
  • No tool lock-in in skill bodies. Gate any single-harness capability behind a clearly labeled optional section.
  • A skill's links must resolve where the skill is used, not only where it is written. A skill ships as a directory alongside its sibling skills and the rules module, without this repository around it. So a link may reach its own files, a sibling skill, or .agents/rules/, and nothing above that. A link that escapes resolves here and dangles everywhere the skill actually runs, which is how house-review once shipped with no rubric: the failure is silent, because the body still reads correctly and only the missing target is absent. validate-skills.py fails on this, and build-adapters.py rewrites the three legal classes when it inlines a body, so the same rule holds on both distribution paths. When a skill needs to name a file outside that tree, such as the target repository's AGENTS.md, name it in prose rather than linking to it.
  • A relative link to a sibling SKILL.md is also a profile edge. install.py resolves an install profile by expanding its seed over the sibling links it finds in skill bodies, so adding one link can pull that skill, and everything that skill links to, into a profile that did not carry it. The expansion is deliberate and correct: a profile that places a skill without what that skill composes ships a broken tree. What it means for an author is that the link syntax, not the mention, is the switch. Write [doc-sync](../doc-sync/SKILL.md) when the skill genuinely composes doc-sync and is broken without it; write the name in backticks, doc-sync, when you are only stating where it sits in the chain. chore-0040 wrote the link form for a neighbour it merely named, and one edit pulled a fourteen-skill component into core.

6. Conventions

Follow .agents/rules/house-style.md for writing and formatting: no em-dashes, sentence-case headings, named sources, relative markdown links, Mermaid for diagrams. That file is swappable; this reference to it is not.

  • A relative link from one skill body to a sibling SKILL.md is load-bearing, not merely formatting: it is a profile edge for install.py, and changing it changes which skills an install places. Read the portability contract above before adding or removing one.
  • Python (tooling under scripts/ and .tasks/validate.py): standard library only where possible, so it runs anywhere with a bare Python 3. PEP 8. No third-party dependency unless truly load-bearing and documented.
  • Cross-platform: target Windows, macOS, and Linux. Prefer pathlib; never assume POSIX symlinks are available.

The acceptance command

python scripts/run-checks.py

run-checks.py runs every gate that decides whether a change here is acceptable, in one command with no flags: skill lint, the test suite, backlog validation, adapter and install dry runs, the real install cycle, the documentation link check, the conformance-matrix citation check, and the roadmap bookkeeping check. The set is open, and its membership rather than its size is the property: naming a count here goes stale the first time anyone adds a gate. It exits 0 when all pass, 1 when one ran and failed, and 2 when one could not run at all, with 2 outranking 1 for the same reason install.py --check and check-provenance.py do it: a gate that could not execute means the report is incomplete, which is a different claim from "the change is bad". Every gate runs even after one fails, because an agent working unattended gets one round trip and a report truncated at the first failure costs it another.

.github/workflows/checks.yml calls this same script rather than restating the gates. One rule, two callers, per chore-0029.

Passing it is necessary but not sufficient. CI runs three operating systems by two Python versions, so any single run of this command covers one of those six cells. A change that passes locally can still fail on a platform you did not run. The command says so in its own summary on every run, and it is repeated here because this is where an agent reads the rules before it starts working, rather than after a run has already happened.

The other insufficiency is the one more runs do not close. Every gate here decides a mechanical property, so read a green run as evidence about the mechanical layer and nothing else. Buy the rest by hand, cheapest first: construct the empty or degenerate input and see what the tool actually says. A check that cannot fail is unchecked, whatever it printed. The incidents behind this paragraph are in the rationale.

The one committed hook registration, and why it is an exception

The layout table above states that hook installation is opt-in and activation is the adopter's: install.py --with-hooks places the files and prints a registration rather than editing anyone's settings. That rule holds everywhere except one place in this repository, and the exception is written here rather than left to be discovered.

.claude/settings.json is committed, and it registers skill-reachability-reminder.py on SessionStart. A project-scope settings file applies to every collaborator who opens this repository, so nobody opts in.

The reason is mechanical rather than a preference: a user-level ~/.claude/settings.json does not reach a cloud session, and a cloud session is the exact case this hook exists for. The full argument is in the rationale.

The exception is kept as small as it can be: one hook, in the reminder shape, which never blocks and never writes, firing only on a genuinely new session. Its worst case is one injected paragraph. Adding a second hook to that file, or any hook that blocks, is a new decision and not covered by this one.

The interpreter is a fallback, python3 <hook> || python <hook>, and the bound on that shape is the hooks module contract. a || b reaches b only when a fails, and every hook here always exits 0, including on failure, so the only way the second interpreter runs is the first never starting. That is what makes the shape legal: a hook that exited non-zero after writing to stdout would run twice and emit two objects, which corrupts the exchange the harness parses. If a hook is ever registered here that does not honour exit 0, this command has to change with it. tests/test_hooks.py asserts both halves, running the committed command as the harness does and asserting every shipped hook exits 0 on every path.

The fallback replaced a one-sided python3 that had never run on Windows (bug-0050); the rationale records what that cost.

Commit messages: no co-author trailer

Do not add a Co-Authored-By trailer naming an AI model to any commit here. This overrides the default some harnesses apply, including Claude Code's, and it applies to every agent that commits in this repository: an interactive session, a worktree agent dispatched by fix-batch, and a cloud session alike. All three have added it. Authorship of the work is already recorded where it belongs, in the task file, the CHANGELOG.md line, and the pull request body.

The reason is mechanical: the main protection ruleset demands an extra approving review for a commit attributed to an identity with no GitHub account, and the only human here cannot approve their own pull request, so the trailer deadlocks the sync to main. The committed .claude/settings.json sets attribution.commit to an empty string so Claude Code stops adding it by default; other harnesses still rely on this rule. The incident is in the rationale.

Provenance for material folded in from elsewhere

Prose credit alone decays into folklore (the incident that taught this is in the rationale), so any file or region adapted from an external source carries a provenance block, and scripts/check-provenance.py re-fetches every recorded source and reports drift.

FieldRequiredContent
sourceyesAbsolute raw URL of the exact upstream file, not the repository's landing page. This is what gets re-fetched.
authoryesThe upstream author, named.
licenseyesThe upstream license.
retrievedyesISO date the fetch actually ran.
sha256yesSHA256 of the retrieved upstream bytes.
originnoA further hop, when the immediate source is itself carrying the material from somewhere else. Prose only, never fetched.
notenoOne line of context.
statusnounlocatable, when the upstream source genuinely cannot be found. It replaces retrieved and sha256, and makes note required, where the note says what was searched.

Placement follows the file type, and there is no sidecar file: a Python hook carries the block in a Provenance section of its module docstring, a skill in a ## Provenance body footer inside a fenced code block tagged provenance, and a rules lens the same way. One adapted file may carry more than one block when it draws on more than one upstream file. A source: line alone does not make a block outside a declared placement: there the parser requires at least one other field, because source: is an ordinary word that other templates use. Inside one of the two placements above, a lone source: is a record and is reported as missing its required fields, so a typo on the field after source: cannot delete the block (bug-0041). Nor can a typo on the source key itself, nor a declared placement carrying no source: line at all: both are reported against the placement rather than the token, and an empty placement is named without failing the run (bug-0042).

A skill's block never becomes a new frontmatter key. ALLOWED_FRONTMATTER_KEYS in validate-skills.py deliberately mirrors Anthropic's own six-key schema, so a seventh key would be rejected by Anthropic's validator even after ours was widened to accept it. Use the body footer, or the existing license or metadata key.

Four rules the convention only works if you follow:

  • Digest the retrieved upstream content, never the adapted local file. The local file is expected to differ, because adaptation is the point. The digest answers whether the thing we adapted from has changed since we looked, which is the only question a drift check can honestly answer for adapted material.
  • Fetch with urllib and digest with hashlib. Never use an agent's web-fetching tool. Those return a model-summarized markdown conversion of a page rather than its bytes, so the digest would be a digest of a summary: stable-looking, meaningless, and impossible for anyone to reproduce.
  • The block records the immediate source, not the origin. You can only drift-check what you actually retrieved, so the digest belongs to the file you fetched. When the chain has a further hop, name it in origin and leave tracking its drift to whoever fetched it.
  • An unlocatable source is recorded, not guessed. A confident-looking digest for content nobody re-fetched is worse than no provenance at all, because a wrong digest reads as verified and passes every automated check.

A block backfilled after the fact pins upstream as of its retrieved date, not the exact bytes originally adapted, whenever the snapshot that was adapted from is gone. Say so in note rather than letting the date imply more than it proves.

The check stays out of required CI: it needs network, and a check that fails when GitHub is slow gets disabled within a week. Run it on demand. It reports drift and never syncs. An overwrite would destroy the adaptation every fold-in here carries.

7. Contribution bar

A skill earns a place in this kit only if it is something the author actually uses and has iterated on. Do not add speculative skills. A freshly drafted skill stays in_progress (or is marked as a draft in ROADMAP.md) until it has been used and refined; only then is it "shipped." Hyper-specific personal skills stay out of the shared kit (see docs/CATALOG.md tiers).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.