Make Agents Split Commits by Patch

When an agent bags a feature, a refactor, tests, and formatting into one commit, git bisect, review, and revert all get harder at once. The same “atomic commit” expectation you have for humans belongs in the agent’s Rule and Done definition: one commit = one logical patch.

This post covers only one-commit criteria · message format · mixed changes. agent-pr-body is PR body sections; agent-diff-review is reading diffs for review. Here the axis is staging and splitting commits. No pricing, plans, or affiliate links.

Grounded in git commit, git add —patch, Conventional Commits, and common atomic-history practice.

What counts as one commit?

One-line answer: A commit should carry one intent (a review / revert / bisect unit). Cut by logical patch, not “I touched the same file.”

Pass/fail criteria to put in the agent brief:

CriterionPassFail (split)
IntentOne bug / one feature / one refactorFeature + unrelated refactor + formatting
Build/testsThat commit alone is green (or team-allowed WIP)Broken middle commits left behind
ReviewWhy is clear from message + diffReviewer guesses intent from file lists
RevertReverting that commit does not smash other workTwo features tangled in one SHA
ScopeRelated tests/types/docs belong to the same intentDrive-by cleanup in another module

Prompt line:

One commit = one logical patch (review/revert/bisect unit).
Do not mix feature, unrelated refactor, and formatting in one commit.
If the working tree has multiple intents, split commits before finishing.

Avoid:

  • End-of-session git add -A && git commit -m 'wip' — history loses recovery units.
  • Implementation without tests, tests in the next commit — unless the team documents WIP/fixup! rules, prefer tests in the same intent for bisect (follow team policy).
  • Whole-tree formatter noise inside a feature commit — put formatting in a separate commit or pre-commit.

You do not need “one file = one commit.” Several files for one intent → one commit. Two intents in one file → split with git add -p.

What message format?

One-line answer: Require a subject (about 50–72 chars) + body (why and scope). If the team uses Conventional Commits, lock type(scope): summary. Ban subjects that are only “update”, “fix”, or a file dump.

Git opens an editor for the message; convention is a summary line, blank line, then body. Conventional Commits define types such as feat / fix / docs / refactor / test / chore, optional scope, and BREAKING CHANGE in the body.

Minimal agent template:

<type>(<optional-scope>): <imperative summary ≤72 chars>

Why: <one or two sentences>
Scope: <what changed / what did not>
Test: <commands you actually ran, or "n/a: docs-only">

Example (conceptual):

fix(auth): reject expired refresh tokens at gateway

Why: clients with stale refresh kept getting 500 from upstream.
Scope: gateway middleware + unit tests; no public API change.
Test: go test ./internal/gateway/...

Rules:

  1. Imperative, outcome-focused — prefer “reject expired refresh tokens” over “fixed bug.”
  2. Test lists only commands that ran — if not run, omit and fail Done (agent-test-gate).
  3. Issue IDs — only if the team uses them (Refs: ENG-1234 or a title prefix). Do not invent IDs.
  4. Trailers (Co-authored-by / Generated-by) — only when team policy says so. Never put secrets in messages.
  5. amend / force — forbid --amend / push --force on already-pushed commits without a human instruction (spell this in the Rule).

If the repo has a template, point the agent at .gitmessage or that path instead of reinventing the format in every chat.

How to handle mixed changes?

One-line answer: If the working tree has more than one intent, do not commit once. Stage by hunk or path (git add -p or equivalents), then commit in intent order.

Checklist for Done:

[ ] git status / git diff — list distinct intents
[ ] For each intent: stage only related hunks (git add -p PATH or pathspecs)
[ ] git diff --cached — confirm staged set matches ONE intent
[ ] Commit with the message template for that intent
[ ] Repeat until clean (or report leftover out-of-scope files)
[ ] Never git add -A when multiple intents are present

Tool notes:

SituationMethodCaution
Hunk-levelgit add -p / git add -p -- pathInteractive; non-interactive agents need path/partial staging
File-level enoughgit add path1 path2Fails if one file holds two intents
Unstagegit restore --staged -- pathFix mistakes before commit
Already committed as one bagSoft reset and re-split per human policyDo not rewrite pushed history lightly
Format noise mixed inSeparate format commit or pre-commitKeeps feature diffs readable

Non-interactive agents may not drive git add -p prompts. Then: (1) edit in intent order so files do not mix, (2) use apply/restore-patch or editor staging APIs, or (3) Rule: if mixed, do not commit—ask a human to split.

Upstream habits that reduce mixing:

  • Write an intent list first; before commit, match it to git diff.
  • Keep auto-format / import churn out of the feature commit.
  • Generated code and lockfiles only in team-allowed scope; unrelated bumps are separate commits or out of scope.

Adjacent: PR Summary belongs in agent-pr-body; teaching the agent to read diffs before review belongs in agent-diff-review.

One-line wrap-up: One intent = one commit → type(scope): summary + why/tests → if mixed, split with add -p (or equivalent); forbid git add -A.

FAQ

Will “atomic” mean tiny noisy commits?

Split at boundaries that matter for review and bisect—not “one line = one commit.” Cut where a revert would still make sense.

We do not use Conventional Commits?

A one-line subject plus Why/Test in the body is enough. Align type prefixes with team docs; still ban “update” / file-list-only subjects.

The agent already ran git add -A?

git restore --staged, then restage by intent. If it already committed and pushed, do not rebase/amend without human approval.

What about fixup / squash?

Teams that squash before merge may allow fixup! / squash! locally. Put the final history shape (how many commits survive, what messages) in the agent Rule.

Sources