Agent Rules / Agent workflow
How does an agent know an edit is finished?
Rule AR-26
After an edit, re-check the callers of every changed function before declaring the task done.
An edit is finished when every line that depended on the old behaviour has been seen again, not when the file you opened looks right. The caller list from before the edit (AR-25) is stale the moment you change code. Re-run it against the new state, compare, and account for each entry.
Why it matters
An agent changes parse_date(text) to parse_date(text, tz) and fixes the two calls in the file it has open. The task says "done". Elsewhere:
# ingest/csv_loader.py
rows = [parse_date(r["when"]) for r in reader] # still one argument
# ingest/api.py
parse = parse_date
...
parse(payload["date"]) # alias: no name to searchThe tests that import parse_date directly pass, because they were updated. The loader has no test. The first nightly import raises TypeError: parse_date() missing 1 required positional argument: 'tz'.
Without a re-check, "done" means the agent ran out of places it remembered looking. With one, the claim is bounded by something that can be repeated.
The steps
- Collect the changed symbols from the diff, not from memory: ``
bash git diff --name-only git diff -U0 | rg "^[+-]\s*(def |class |function |export )" - Re-run the caller search for each symbol, the same way as before the edit: language-server references, then
rg -n -w <name>. - Run the type checker on the whole project, not only the file you touched, since a call-site error appears in the caller's file: ``
bash pyright # exit code 1 if errors reported mypy -p billing # or: mypy src/ npx tsc --noEmit # type-check without writing output - Diff the two caller lists. Every entry from the pre-edit list is either updated in the diff or explained (still valid, with a reason). Every new entry is a new caller you introduced.
- Read the dynamic sites you listed. A type checker can't clear a table lookup or a call built from a string. Open the line, read the call against the new signature, say what you saw.
- Then run the tests (AR-29) and write the report (AR-30).
If step 4 leaves an entry you can't account for, the task isn't done. Say what's left.
Worked example
Pre-edit list for parse_date, and what the re-check found (illustrative):
pre-edit callers: 7 direct (ingest/ x4, cli/ x2, tests/ x1), 1 alias (ingest/api.py:12 parse = parse_date)
after edit: diff touches 6 of 7 direct calls
re-check:
ingest/csv_loader.py:30 parse_date(r["when"]) NOT in diff -> fix
ingest/api.py:44 parse(payload["date"]) alias, call is one argument -> fix
pyright (alias file only; file and line from the snippet below): 1 error before the fix, 0 after
ingest/api.py:7:12 - error: Argument missing for parameter "tz" (reportCallIssue)Pyright does flag the call through the alias: it follows parse = parse_date and checks the call against the new signature, so the alias is not silent. The type checker catches arity breaks through aliases. The caller diff still has to account for every entry, and dynamic and string-based sites (a table lookup, getattr, a call through a value typed Any) need a reading pass, because the checker has nothing to compare there.
To re-run the alias check, create these two files and run pyright ingest/api.py in that folder (pyright 1.1.414, default mode, no config; pyright reports only the files you name):
File ingest/dates.py:
from datetime import date
def parse_date(text: str, tz: str) -> date:
return date.fromisoformat(text)File ingest/api.py:
from datetime import date
from ingest.dates import parse_date
parse = parse_date
def handle(payload: dict[str, str]) -> date:
return parse(payload["date"])Expected: one error, ingest/api.py:7:12 - error: Argument missing for parameter "tz" (reportCallIssue), exit code 1.
Rule block for AGENTS.md / CLAUDE.md
## Before saying "done"
- List the functions, classes and exports your diff changed (from `git diff`, not memory).
- Re-run the caller search for each one and compare with the list you made before the edit.
- Every earlier caller is either in your diff or has a written reason it stays valid.
- Run the project's type checker on the whole project, not only the files you edited.
- Read each dynamic call site you listed against the new signature.
- If any caller is unaccounted for, say so in the final message instead of reporting the task finished.Edge cases
- Behaviour changes with the same signature. The type checker is silent. Callers that depended on the old return value or side effect need a read, and the tests that cover them need to have run (AR-29).
- Callers in other repositories or packages. Not visible from here. State that the check covers this repo only.
- A caller list the tool says is incomplete (AR-27). Treat the missing part as unchecked, not as empty.
- Very large diffs. Group by symbol and re-check one at a time. A single grep over everything produces a list nobody reads.
- Reformatting or rename-only diffs. Use AR-28; the re-check then reduces to confirming the rename covered every reference.
What LLMGRAPH shows
After an edit, the stop check lists each broken call by file and line, marked certain, and counts the calls that were updated and fit.
References
- Pyright command line and exit codes: https://github.com/microsoft/pyright/blob/main/docs/command-line.md
- mypy command line (files,
-m,-p): https://mypy.readthedocs.io/en/stable/command_line.html - TSConfig
noEmit: https://www.typescriptlang.org/tsconfig/#noEmit - git diff options (
--name-only,-U0): https://git-scm.com/docs/git-diff. Verified 2026-10-30:--name-onlyshows "only the name of each changed file in the post-image tree";-U<n>generates diffs with n lines of context, so-U0gives hunks with only the changed lines (the page defines<n>, not-U0by name). - ripgrep user guide: https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md. Verified 2026-10-30 for
-w,--typeand--glob; see the same entry on AR-25.
Changelog
- 2026-10-30: draft 1.
- 2026-10-30: docs checks resolved
- 2026-10-30: alias call corrected (pyright flags it); snippet added.
- 2026-10-30: copy-gate wording fixes
Related
- AR-25 What to check before changing a function signature?
- AR-27 Is a list of callers ever the full list?
- AR-29 What should a coding agent run before finishing?
- AR-30 What should an agent report when it can't verify an edit?
Last verified