From 81a0ac7121e49e4b31198f7e3bc372a5cf8089c1 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Fri, 31 Jul 2026 13:28:42 -0700 Subject: [PATCH] Force the agents-md skill before any AGENTS.md write An AGENTS.md is loaded into every session for its repo, so a careless one taxes every future task there and fails silently. Pair a skill with a gate: - .cursor/skills/agents-md/SKILL.md holds the guidance (size budget, the what/why/how to include, the exclude list, progressive disclosure, pointers over pasted code), cached from philschmid.de/writing-good-agents so applying it never needs a fetch. - require-agents-md-skill.sh (PreToolUse) blocks writes to any AGENTS.md, in any repo, until the skill is loaded that session. Covers Write/Edit by file_path and Bash by redirect/tee/heredoc; Bash reads still pass. - mark-agents-md-skill-read.sh (PostToolUse) clears the gate on a Skill call, a Read of the SKILL.md, or a Bash reference to it. Session-keyed rather than task-keyed so it also covers interactive edits outside orchestrated runs. --- .cursor/skills/agents-md/SKILL.md | 58 ++++++++++++++++ .cursor/skills/rubric-drift.lock.json | 3 +- .../hooks/mark-agents-md-skill-read.sh | 31 +++++++++ .../hooks/require-agents-md-skill.sh | 66 +++++++++++++++++++ claude-settings/hooks.json | 20 ++++++ 5 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 .cursor/skills/agents-md/SKILL.md create mode 100755 agent-watcher/hooks/mark-agents-md-skill-read.sh create mode 100755 agent-watcher/hooks/require-agents-md-skill.sh diff --git a/.cursor/skills/agents-md/SKILL.md b/.cursor/skills/agents-md/SKILL.md new file mode 100644 index 0000000..51a4ca8 --- /dev/null +++ b/.cursor/skills/agents-md/SKILL.md @@ -0,0 +1,58 @@ +--- +name: agents-md +description: Write or revise an AGENTS.md agent-context file for a repo. Use when creating, editing, reviewing, or shortening an AGENTS.md (or an equivalent agent-instructions file), in any repo. A PreToolUse hook forces this skill before any AGENTS.md write. +compatibility: No dependencies. Guidance is cached in this file; never fetched at use time. +metadata: + author: j0ntz +--- + +Produce an AGENTS.md that earns its per-session cost: short, deliberate, and about what the agent cannot cheaply discover on its own. + + + +Keep the file under 300 lines, and treat anything over ~100 as needing justification. Every line enters EVERY session for that repo, so it is charged on every future task there. The measured baseline: an auto-generated context file performs ~3% WORSE than having no file at all while costing 20%+ more tokens, and a deliberate hand-written one gains only ~4%. A file only beats no file when it is short and non-obvious, so cut before you add. + +Cover exactly three things. WHAT: the stack and what the major components are for (highest value in a monorepo, where the apps/packages split is not obvious). WHY: the intent behind the project and its key pieces, so the agent can make judgment calls rather than only follow steps. HOW: the build, test, and verification commands, especially NON-OBVIOUS tooling (`uv` not `pip`, `bun` not `npm`, a runner needing a specific invocation). Naming a tool here is the single highest-leverage line in the file: named tools get used ~160x more often than unmentioned ones. + +Do NOT include: directory listings or codebase overviews (agents locate files fine, and the listing is stale on the next move); code-style rules (linters and formatters enforce style deterministically, and models follow surrounding code anyway); task-specific or non-universal instructions (a model reliably follows ~150-200 instructions and the harness already spends ~50, so anything that is not always-true crowds out something that is); auto-generated content (stale, unowned, measurably harmful); anything already stated in a doc this file could link to instead, since redundancy actively lowers performance. + +Keep AGENTS.md an INDEX, not a manual. Push detail into separate files (`docs/…`) and list each with a one-line description of when to open it, so detail is pulled on demand instead of paid for every session. + +Reference code by path (`src/util/foo.ts`, or `file:line`) instead of pasting snippets. Embedded code decays silently as the real code moves, and costs many times what the pointer costs. + +Write the file yourself, line by line. Never generate it from a directory walk or a template fill. It is infrastructure: a bad line does not fail loudly, it degrades every future session in that repo. + +This guidance is a CACHE, distilled from . Do not fetch that URL to apply the skill; the principles are stable and the hook that forces this skill must not depend on the network. Re-distill into this file only when the source materially changes. + + + + +If the repo already has an AGENTS.md, read it in full first. Also list the repo's existing docs (`ls docs/ README.md CONTRIBUTING.md` and any `.cursor/` guidance) — anything already documented there is a LINK target, never content to restate per `exclude-list`. + + + +Gather only what an agent could not cheaply discover: the exact verification command and what it runs, tooling that differs from the ecosystem default, the component split and its intent, and any convention that fails silently when violated. Read the package manifest and the CI config for the real commands rather than guessing them. + + + +Write the file against `what-why-how`, `exclude-list`, `progressive-disclosure`, and `pointers-over-copies`. Lead with one short paragraph on what the project is and why. Put commands in a compact table. Close with the index of linked docs. Prefer a sentence that changes a decision over a sentence that describes a fact. + + + +Re-read the draft and delete every line that fails this test: would an agent that never read this line do anything differently? Verify against the checklist before saving. + + + +- [ ] Under 300 lines, and every remaining line justifies its per-session cost. +- [ ] WHAT / WHY / HOW covered, with exact commands and non-obvious tooling named. +- [ ] No directory listing, no style rules, no task-specific instructions, nothing auto-generated. +- [ ] Detail is linked, not inlined; code is referenced by path, not pasted. +- [ ] Nothing duplicates a doc it could link to instead. + + + +Make AGENTS.md a thin index over them (`progressive-disclosure`). Do not summarize their content into it — the redundancy costs performance. +Spend the WHAT budget on the apps/packages/services split and each one's purpose; that is the case where structure is genuinely not discoverable. Consider a per-package AGENTS.md instead of one large root file. +Push back with `size-budget`: propose linking a new doc under `docs/` and adding one index line, rather than growing the always-loaded file. +Rewriting it shorter IS the fix, not an optional cleanup. Cut the excluded categories first; they are usually most of the file. + diff --git a/.cursor/skills/rubric-drift.lock.json b/.cursor/skills/rubric-drift.lock.json index d9603c7..e58347a 100644 --- a/.cursor/skills/rubric-drift.lock.json +++ b/.cursor/skills/rubric-drift.lock.json @@ -1309,6 +1309,7 @@ "orch-eval": "2026-07-19: the eval layer itself; grades orch runs, is not one", "eval-run": "2026-07-19: the eval layer itself; grades orch runs, is not one", "resolve-run": "2026-07-19: the eval layer itself; grades orch runs, is not one", - "coinhub": "2026-07-24: Standalone operator-invocable coinhub maintenance skill; domain-procedure rules (snapshot regen, safe force-push, theme drift), not orchestration-gating behavior. Not wired into one-shot delegation, so not an orch dependent the agent-eval/orch-eval rubrics grade. Re-evaluate if one-shot ever delegates to /coinhub." + "coinhub": "2026-07-24: Standalone operator-invocable coinhub maintenance skill; domain-procedure rules (snapshot regen, safe force-push, theme drift), not orchestration-gating behavior. Not wired into one-shot delegation, so not an orch dependent the agent-eval/orch-eval rubrics grade. Re-evaluate if one-shot ever delegates to /coinhub.", + "agents-md": "2026-07-31: Non-orch: repo-authoring tooling invoked by the AGENTS.md write gate, not part of the one-shot delegation chain; its correctness is enforced by require-agents-md-skill.sh plus the skill's own checklist, not by a run-eval dimension." } } diff --git a/agent-watcher/hooks/mark-agents-md-skill-read.sh b/agent-watcher/hooks/mark-agents-md-skill-read.sh new file mode 100755 index 0000000..20d2108 --- /dev/null +++ b/agent-watcher/hooks/mark-agents-md-skill-read.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# mark-agents-md-skill-read.sh — PostToolUse(Skill | Read | Bash). +# Writes the per-session marker require-agents-md-skill.sh checks, when the +# completed tool call actually loaded the `agents-md` skill: a Skill invocation, +# a Read of its SKILL.md, or a Bash command referencing that path (cat/grep +# count — any engagement with the content beats none). Never blocks; exit 0. +set -euo pipefail + +INPUT=$(cat) + +SESSION=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || true) +[ -n "$SESSION" ] || SESSION="${AGENT_TASK_GID:-default}" +MARKER="/tmp/agent-agents-md-skill-$SESSION" +[ -f "$MARKER" ] && exit 0 + +TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true) +case "$TOOL" in + Skill) + SKILL=$(printf '%s' "$INPUT" | jq -r '.tool_input.skill // empty' 2>/dev/null || true) + case "$SKILL" in *agents-md) touch "$MARKER" ;; esac + ;; + Read) + FP=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true) + case "$FP" in *skills/agents-md/SKILL.md) touch "$MARKER" ;; esac + ;; + Bash) + CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null || true) + case "$CMD" in *skills/agents-md*) touch "$MARKER" ;; esac + ;; +esac +exit 0 diff --git a/agent-watcher/hooks/require-agents-md-skill.sh b/agent-watcher/hooks/require-agents-md-skill.sh new file mode 100755 index 0000000..d18b24b --- /dev/null +++ b/agent-watcher/hooks/require-agents-md-skill.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# require-agents-md-skill.sh — PreToolUse(Write | Edit | Bash). +# Blocks any write to an AGENTS.md, in ANY repo, until the `agents-md` skill has +# been loaded THIS session, evidenced by the marker mark-agents-md-skill-read.sh +# (PostToolUse) writes. +# +# Covers every authoring vector, not just the obvious one: Write/Edit by +# file_path, and Bash by redirect/tee/heredoc into the file (a `cat > AGENTS.md` +# authors exactly what Write does). Bash READS of the file stay unblocked. +# +# The hook's ONLY job is to guarantee the skill; all guidance on what makes a +# good AGENTS.md lives in ~/.cursor/skills/agents-md/SKILL.md, cached there and +# never fetched at use time. Keep this script free of that content so the two +# cannot drift apart. +# +# Why gate at all: an AGENTS.md is loaded into EVERY session for its repo, so a +# careless one taxes every future task there and fails silently rather than +# loudly. The confident first draft — a directory listing, pasted code, and +# style rules a linter already enforces — measurably performs WORSE than having +# no file. +# +# Session-keyed, not task-keyed: this covers interactive edits in any checkout, +# not just orchestrated runs, so it must NOT require AGENT_TASK_GID. +# +# Not one-bounce: blocks until the marker exists. The remedy is one Skill call +# (or one Read), which then passes every later AGENTS.md write in the session, +# so a loop only occurs if the agent refuses to load the skill. +set -euo pipefail + +INPUT=$(cat) + +TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true) +IS_WRITE=0 +case "$TOOL" in + Write | Edit) + FP=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true) + # Any path, any repo — only the file name decides. + case "$(basename "${FP:-}")" in + AGENTS.md | agents.md | Agents.md) IS_WRITE=1 ;; + esac + ;; + Bash) + CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null || true) + # Redirect (`> AGENTS.md`, `>> …`), tee, or heredoc target. Reads such as + # `cat AGENTS.md` / `grep x AGENTS.md` carry no such operator and pass. + if printf '%s' "$CMD" | grep -qiE '(>>?[[:space:]]*|tee([[:space:]]+-a)?[[:space:]]+)[^[:space:]|;&]*AGENTS\.md'; then + IS_WRITE=1 + fi + ;; +esac +[ "$IS_WRITE" = 1 ] || exit 0 + +SESSION=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || true) +[ -n "$SESSION" ] || SESSION="${AGENT_TASK_GID:-default}" +MARKER="/tmp/agent-agents-md-skill-$SESSION" +[ -f "$MARKER" ] && exit 0 + +cat >&2 <<'MSG' +BLOCKED: no AGENTS.md write before the `agents-md` skill is loaded this session. +Load it now (one call unblocks every later AGENTS.md write in this session): + Skill tool -> skill: "agents-md" +It carries the size budget, the include/exclude lists, and the checklist this +file has to satisfy. An AGENTS.md enters EVERY session for its repo, so writing +one from instinct is how a repo acquires a permanent context tax. +MSG +exit 2 diff --git a/claude-settings/hooks.json b/claude-settings/hooks.json index 9b928cb..1256850 100644 --- a/claude-settings/hooks.json +++ b/claude-settings/hooks.json @@ -8,6 +8,16 @@ } ], "matcher": "Read|Bash" + }, + { + "hooks": [ + { + "command": "~/.config/agent-watcher/hooks/mark-agents-md-skill-read.sh", + "timeout": 10, + "type": "command" + } + ], + "matcher": "Skill|Read|Bash" } ], "PreToolUse": [ @@ -103,6 +113,11 @@ { "command": "~/.config/agent-watcher/hooks/block-upfront-conflict-probe.sh", "type": "command" + }, + { + "command": "~/.config/agent-watcher/hooks/require-agents-md-skill.sh", + "timeout": 10, + "type": "command" } ], "matcher": "Bash" @@ -118,6 +133,11 @@ "command": "~/.config/agent-watcher/hooks/require-maestro-device.sh", "timeout": 10, "type": "command" + }, + { + "command": "~/.config/agent-watcher/hooks/require-agents-md-skill.sh", + "timeout": 10, + "type": "command" } ], "matcher": "Write|Edit"