Skip to content

docs: trim the agent instruction files to the rules nothing else enforces - #1197

Merged
mforce merged 9 commits into
mainfrom
docs/trim-agent-instructions
Oct 11, 2026
Merged

mforce merged 9 commits into
mainfrom
docs/trim-agent-instructions

Conversation

@mforce

@mforce mforce commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

What and why

Agents load the six agent instruction files into every session. Together they had grown to about 15,300 words. Most of that text repeated one of three things. Some repeated what a guard already prints when it fails. Some repeated what CONTRIBUTING.md or a decision record already explains. The rest was the history of how a rule was earned. This PR keeps the rules that nothing else states where an agent will meet them, and it keeps the condition, exception or "never" that each one depends on. Where a guard carries a rule, the scoped file keeps one line that names the guard and links the decision record. Every record that the old files linked is still linked.

After the trim was drafted, main changed these files six times. Each change is ported by the same classes:

Word counts

File Before After
AGENTS.md 4,726 1,655
CLAUDE.md 1 1 (unchanged, @AGENTS.md)
src/AGENTS.md 8,384 2,125
.github/AGENTS.md 1,985 862
web/AGENTS.md 1,171 371
src/Cluckwork.Infrastructure/Persistence/AGENTS.md 289 157
Total 16,556 5,171

"Before" is main at 6502603e.

What was dropped, by class

Each original rule was classified as one of four kinds.

  • G, guard-enforced. A test, analyzer or boot check fails with a message that names the fix. The rule text is gone, and an Enforced rules and their records list in src/AGENTS.md or the Persistence file names the guard and links the record. Examples include the proxy-trust, Postgres TLS and JWT-key boot guards, GSS negotiation, the design-time migration connection, and the InitialCreate freeze, whose guard prints the dotnet ef migrations add command.
  • D, derivable. The repo already says it in CONTRIBUTING.md, a README, a runbook or the code. Examples include the run commands for the full stack, the frontend and the debug API, the pre-commit hook, the CI audit gates and their exception file, the migrate and healthcheck verbs, phase context and epic bookkeeping, and the table of contents.
  • H, history. Incident narratives, counts and dates belong in the decision record. Examples include the tj-actions and trivy-action citations, the singleton walk counts for Background worker has no single-runner guarantee — double-runs if scaled >1 instance #271, the Replace TenantBypass line-number anchors with stable query identities #632 re-pin counts and the libpq spelling tip from Deploy: support URI-form (postgresql://) connection strings — Npgsql's key-value-only parser rejects them #261.
  • K, kept. Nothing else enforces the rule, or a guard covers it but has a gap. For a gap, only the gap is kept. For example, SolutionTestProjectSplitTests catches a missing test-matrix leg, but nothing catches if:, continue-on-error or fail-fast: false on that job, so that line stays in .github/AGENTS.md.

The root file's build command also named Cluckwork.sln, which no longer exists. It now names Cluckwork.slnx.

New lines in the trimmed text

These five lines are new rules, not shortened ones. Reviewers should judge each one on its own.

  • Run only the tests your change touches, with --filter. CI runs the full suite.
  • Read the issue's comments as well as its body.
  • Assert the invariant, not a message. A test that pins error text passes when the behavior breaks.
  • A walk that finds nothing must fail. Give it a floor.
  • Report your guard mutations in the PR. The PR template already asks for this.

K rules kept

Root AGENTS.md.

  • Read the decision record before you change a rule.
  • Read each scoped file whether or not your harness loads it. A rule moves to a scoped file only when every path that can trigger it is listed.
  • Do not copy issue state into these files.
  • Integration tests use Testcontainers Postgres, never SQLite. A new test project needs a matrix leg.
  • The guard-writing rules:
    • A wrong guard is worse than none.
    • Mutation first, claim second.
    • Walk everything and exclude deliberately.
    • Key a registry by what the code declares, never by path or file:line, and assert uniqueness when a stable key can collide.
    • Prove that pinned values are portable.
    • Prefer the boring guard.
    • Run an adversarial pass before the first push.
    • Grep for the readers of a registry.
    • Read a call-site syntax guard before you write the call.
    • Tracked-file guards apply to committed docs. Fix a copied document at its source, never in the copy or an allow-list.
  • Take screenshots from a stack rebuilt at the head. Attach them; never commit them.
  • The verify skill and its shared-stack hazard. Run /maintain-verification-skill after a screen slice lands.
  • Reason about inert through CDP.
  • Update the Glossary and Help with user-visible changes. The Docs impact workflow checks part of this.
  • Secrets.
  • The host-agnostic repo rule, with its passing-example exception.
  • Use gh for PRs. Never commit to main.
  • The Enforced rules table and its rule for adding a row.
  • The agent command guard and its per-harness enable steps. The git hooks are a separate backup.
  • The PR title is the release note. Put Closes #N in the body.
  • Never hand-edit release-please files.
  • Commit lock files in the same commit as the package change.
  • Amend another open issue the PR ships, with an amendment at the top of that issue and a follow-up or won't-fix for each unmet criterion.
  • graphify: query it first, do not skip it because its files are dirty, and update it periodically.

src/AGENTS.md.

  • The Result pattern, one handler per feature, validators and the endpoint shape.
  • .editorconfig :warning rules are a decision. Never name a namespace segment after a type.
  • The ledger authorises no module move and no cross-owner write.
  • Set the ledger Kind yourself and never widen a reason.
  • Never silence a CW diagnostic or add an exemption. Every new module project gets the analyzer reference.
  • Seam surface. Endpoints never take persistence types.
  • Declare an adapter tier's privilege before its surface exists, and drop the dormant assertion when the tier activates.
  • Outside reads of a contracted module's tables go through its contract or a compatibility row. Raw SQL needs a module-reach review.
  • Adapters use contracts only. Never move IReportQueries or IExportQueries into Contracts/.
  • Regenerate the coupling matrix; never hand-edit it.
  • Ports that join a caller's transaction never save.
  • Lock order is part of the contract.
  • Farm-code resolution, and IFarmDirectory never behind a forwarding helper.
  • Register an ICurrencyBoundRowSource and keep the registration order.
  • Fixture ports run outside Production.
  • Every mutation bumps Version and gets a race test.
  • Tenant AccountId and the tenant query filter.
  • Parent-derived flock children need a filtered parent or a policy, plus a causal mutation test.
  • A farm code changes only through Account.Rename, with list-accounts immediately before.
  • System-actor callers need an access review.
  • The chronology rules: same-direction keys, the FIFO and name-order exemption, preserved action timestamps and the backfill whitelist.
  • The transient-retry boundary, with both of its cures.
  • AuditEvents.Sequence is global.
  • Farm settings GET and PUT are OwnerOnly, and so is the navigation gate.
  • No key material in the repo. Check JWT keys with IsNullOrWhiteSpace.
  • Never cache the credential epoch.
  • The OAuth issuer, token, scope, openid, step-up and shared-limiter rules. ClientMetadataFetcher is the only fetch path.
  • ICurrentUser is an authorization input. Seeders load real roles.
  • Generated passwords go to stdout only.
  • MCP tools learn their caller only from McpCallContext. Every tool method carries a role and a scope [Authorize], never [AllowAnonymous].
  • OAuth tokens are bound to <OAuth:Issuer>/mcp. A second endpoint needs its own resource and audience.
  • Scope boot guards by process role, with one row per violation. tzdata and ICU.
  • A new boot guard or a config-key change updates the sim harness, and never by disabling a guard. A new required config key updates the AppHost.
  • A write-contract change updates its non-CI callers.
  • No boot seeding.
  • Production log scope leakage.
  • The accepted risks: Suspension: login can still mint an inert credential in the check-then-mint window #579 with its four premises, the Identity tables, audit clock rollback, the leader lock, ungated coverage, the AllowConnectedApps default and the Data Protection certificate.
  • The single serving instance and the walk-not-memory rule.

.github/AGENTS.md.

  • Pin Actions to a SHA. Never ubuntu-latest.
  • The required-checks ruleset changes in the same PR as a renamed required job or a new path filter.
  • The docs-only gate: PR-only verdict, build-and-test never gated, continue-on-error kept, a self-test with no dependents.
  • The matrix flags. New restore inputs.
  • Promotion retags, keeps --prefer-index=false and the post-retag digest check, reads the run artifact, and keeps publish.needs complete.
  • A release stays a draft until it is promoted.
  • The App token for release PRs, its downscoping and its environment.
  • The hotfix-line rulesets with their bypass lists, and how --source-ref is derived.
  • The Release-As: timing and its one exception.
  • Multi-arch digests and the cache prefix.
  • Deploy by index digest. The canonical trust-boundary paragraph is kept word for word.

web/AGENTS.md.

  • Check every string that a removed transform touched.
  • Help prose follows each control's label in each locale.
  • Count selector call sites, record the count, and record any deliberate zero.
  • Retire a style-guard assertion only together with its CSS.
  • Every glossary term gets an in-app entry or a SPEC_ONLY row.
  • Screens load data through useLatestLoad or usePagedList, never an async useEffect.
  • The lint install. Suppressions only shrink. No auto-fix for exhaustive-deps.

src/Cluckwork.Infrastructure/Persistence/AGENTS.md.

  • One migration per change.
  • Base reference data is guarded raw SQL: grades guard the whole set, the rest guard per key.
  • Regenerate the schema docs.
  • Do not time-partition AuditEvents.

Pointer fixes

Code comments that cited removed text now point to where that text lives. Only comment lines changed.

  • ci.yml and docs/decisions/782-ci-job-gating.md cited .github/AGENTS.md for the tj-actions compromise. They now cite 146-ci-security-gates.md.
  • tools/simulation/ui/src/env.ts cites README.md for "single container".
  • ProductionLogFormatTests cites 404-production-logs.md for the @tr/@sp field names.
  • RecoverAdminCommandTests cites the break-glass runbook for the least-privilege role.
  • AccountMaxDiscountMigrationTests cites 732-farm-code-rename.md for raw UPDATEs on Accounts.
  • AppDbContext, NamedEntityDiscoveryTests and DefaultResponseCaching name src/AGENTS.md, where the tenancy and no-MediatR rules now live.

These pointers were already broken on main, and this PR fixes them too:

  • ExportQueries cited an "AGENTS.md money rule" that never existed. It now cites tech_spec.md §3.2.
  • SimulationDataSeeder quoted "no silent caps" from AGENTS.md, which never said it. It now cites fix(seed): seeded audit events carry "(unresolved)" as the actor, now visible on record History columns #500 review round 2, finding 6, where the phrase came from.
  • ServingGuardCoverageTests attributed a quote to AGENTS.md. The quote comes from 347-process-role.md, and the comment now cites that record.
  • VoidPaymentHandler and AdjustExpenseHandler pointed at the root file for the Version rule, which lives in src/AGENTS.md.

Decision records and indexes:

  • Nine records (260, 261, 263, 265, 266, 267, 283 for first-run admin, 332 and 510) now open by saying that src/AGENTS.md lists the rule under Enforced rules and their records. Record 318 says the same of the Persistence file.
  • Record 146 says that CI and CONTRIBUTING.md carry the audit gates, and that its remaining compact constraint, SHA-pinned Actions, lives in .github/AGENTS.md. Record 852 names its remaining constraint, IMortalityLedger.AppendAsync never saves.
  • Records 565 and 776 named the root file. Their rules now live in src/AGENTS.md.
  • In docs/decisions/README.md, the "Rule lives in" column is regenerated from where each record is linked today. The introduction now describes the two places a rule can appear.
  • README.md and docs/README.md no longer describe AGENTS.md as "every invariant".

271-single-serving-instance.md keeps its history paragraphs (the old counts, "wrong in AGENTS.md until 2026-08-16") as written, because they describe the past.

Review round 1

Round 1 compared every shortened rule with the base text and checked every claim that a guard enforces a rule against that guard's code. Each finding below was fixed by restoring the original condition as one short sentence.

  • F01. IFarmDirectory is never behind a forwarding helper, because FarmDirectoryCallerTests checks names, not calls.
  • F02. Parent-derived flock children such as DailyEntryGrade and EggInventoryMovement gate through a filtered parent or a policy. Each new one needs a rationale and a causal mutation test.
  • F03. Suspension: login can still mint an inert credential in the check-then-mint window #579's four premises are back. Revocation must execute inside the suspension's transaction.
  • F04. build-and-test is never gated, changes keeps continue-on-error, and the self-test has no dependents. Only a pull_request run gets a docs-only verdict.
  • F05. Both retry cures are back: SingleAttemptExecution when replay is observable, and a durability probe when replay writes nothing.
  • F06. Job handlers are always at-least-once and idempotent. A session-pinned endpoint adds at most one leader; it does not replace idempotence.
  • F07. No analyzer exemptions. Every new module project needs the analyzer reference.
  • F08. Process-role rows are one per violation.
  • F09. Outside table reads need a contract or a compatibility row. Raw SQL needs a module-reach review.
  • F10. The ledger and the table-owner walk authorise no module move and no cross-owner write.
  • F11. A stable registry key that can collide needs a uniqueness assertion.
  • F12. Chronology keys run in the list's direction. Name-ordered, FIFO and FOR UPDATE ordering is exempt. Action timestamps and Version are preserved.
  • F13. The legacy backfill uses exact whitelisted events and falls back to the unknown sentinel.
  • F14. Run list-accounts immediately before a rename.
  • F15. Promotion keeps --prefer-index=false and the post-retag digest check.
  • F16. Release PRs use the App token, never GITHUB_TOKEN.
  • F17. The release-line rulesets need their exact rules and bypass lists. --source-ref is never taken from image.json or a wildcard.
  • F18. The scaling walk covers in-memory state and package Add*/With* registrations at the resolved version.
  • F19. Fix a copied document at its source, never in the copy or an allow-list.
  • F20. The issue amendment goes at the top of the issue, and every unmet criterion gets a follow-up or a won't-fix.
  • F21. The agent command guard and the git hooks are described separately, with the per-harness enable steps.
  • F22. Declare a tier before its surface, and drop the dormant MCP assertion on activation.
  • F23. Farm settings GET and PUT are OwnerOnly, and the navigation entry checks the exact Admin role.
  • F24. Grades guard the whole set. Roles, conversions and the account guard per key.
  • F25. System callers use SystemActors. Seeders load the Owner's real roles.
  • F26. The passing-example exception for provider names is back, and so is the every-triggering-path condition for moving a rule.
  • F27. /maintain-verification-skill is back, and so are the graphify obligations.
  • F28. Record the selector count, and record any deliberate zero before merge.
  • F29. The 16 records whose links the trim removed are linked again. Rules are restated inline where a condition came back; elsewhere, Enforced rules and their records lists name the guard. The MCP guard-limits plan is a link again.
  • F30. The ?? rule applies only to the JWT key settings, and the "never disable" rule only to fixing the sim harness.
  • F31. The Release-As: line explains its one exception, release-please's override block.
  • F32. Records 146 and 852 name their remaining compact constraint, and the decision index introduction matches the rows.
  • F33. "Use gh for GitHub PRs" is back. The mirror remote is gone, because no tracked file mentions it.

The #806 rules from #1195 are folded into src/AGENTS.md. The OAuth bullet gains the /mcp audience binding. The MCP bullet gains the per-method role and scope [Authorize] requirement and links 806-mcp-endpoint.md. F22's line no longer names the dormant MCP assertion, which #1195 removed.

The same round flagged three rules that the trim had widened. They are narrowed back. The issue-state rule covers these files only. The registry-key rule no longer bans namespaces. The AppHost update applies only to a new required config key, as before.

How it was verified

  • A script followed all 250 relative links and anchors in the 24 Markdown files this PR changes, including the six instruction files. All resolve. All 16 previously removed decision records are linked again.
  • dotnet test tests/Cluckwork.Application.Tests --filter "FullyQualifiedName~RenameAccountDocsTests|FullyQualifiedName~TenancyDocsFreshnessTests" passed 19 of 19 at the head.
  • dotnet test tests/Cluckwork.Api.IntegrationTests --filter "FullyQualifiedName~ImagePin|FullyQualifiedName~SolutionTestProjectSplitTests" passed 3 of 3 at the head. The integration project does not build on main at 6502603e: Mcp/McpConnection.cs from feat(mcp): map /mcp for connected apps, with role and scope gates on tools #1195 has three unused usings that build: make an unused using a build error (IDE0005) #1199 now rejects. This PR does not touch that file, so these tests ran with -p:EnforceCodeStyleInBuild=false, and CI's integration and build jobs will fail here until main is fixed.
  • node --test .github/scripts/changed-paths.test.mjs passed 16 of 16.
  • I checked each restored condition against the base text. Each new guard name in the enforced lists is a test class or workflow that exists. One check changed the text: image-scan.yml turned out to report findings without gating, so its line says that.
  • Every changed line in the C#, TypeScript and YAML files is a comment. No migration or docs/schema/ text changed.

@mforce
mforce marked this pull request as ready for review October 10, 2026 22:04
Folds #806's OAuth audience and MCP tool-gate rules into src/AGENTS.md, and
ports the rules #1188, #1190, #1193 and #1199 added to the instruction files.
@mforce
mforce merged commit 7af27fd into main Oct 11, 2026
21 checks passed
@mforce
mforce deleted the docs/trim-agent-instructions branch October 11, 2026 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant