AI coding

How to Keep an Agent's Working Notes Out of the Commit

Antonio Evans8 min read

A top-down view of a developer's desk with an open laptop showing a git diff.
Key points

Key points

  1. 1Write agent progress notes to a dedicated directory such as .agent/ or .scratch/, never into tracked source paths.
  2. 2Add the notes path to both .gitignore (shared, committed) and .git/info/exclude (local, per-clone) so it stays out of the diff either way.
  3. 3Verify before every commit with git status --short and git diff --cached --name-only, and add a pre-commit hook that fails when a staged path matches the notes pattern.
  4. 4Ignore rules only affect untracked files, so any notes file already in the index has to be removed with git rm --cached before the ignore takes effect.

Where the agent should write its notes

An agent running a multi-step task accumulates state: what it tried, what failed, which files it read, what it plans to do next. That state has to live somewhere on disk if it is going to survive a context reset. The mistake most teams make is letting the agent improvise, which is how you end up with notes.md, todo.txt and debug-20250301.log scattered across the repository root.

Anthropic's guidance on context engineering frames the problem precisely: an agent in a loop generates more and more data that could be relevant to the next turn, and that information has to be cyclically refined rather than simply dumped somewhere 1. In a key-notes-on-disk approach, you are externalizing that refinement. The agent writes its plan and progress to a file, and later reads it back instead of replaying the whole history.

Four locations work, and they trade off differently:

LocationCommitted?Visible to teammatesBest forMain risk
.agent/ inside the repoNo (ignored)NoSingle-repo agent work, plans, scratchpadsAccidentally staged before the ignore rule exists
.scratch/ inside the repoNo (ignored)NoThrowaway output, logs, generated samplesSame as above, plus teams ignoring it inconsistently
Sibling worktree (../repo-agent/)Separate branchOnly if pushedLong autonomous runs that need a clean main treeDisk usage, and the worktree itself can be committed to if you forget
Outside the repo ($TMPDIR/agent-<id>/)NoNoEphemeral runs, CI jobsLost between sessions, no easy diff for humans

The decision rule is simple. If the note has value to a future session of the same agent, keep it inside the repo under .agent/ and ignore it. If it has value to a human reviewer, it belongs in a pull request description or a design doc, not in a working file. If it has no value after the run, put it outside the repo entirely so it can never be staged by accident.

The OpenDev paper on terminal coding agents describes exactly this pattern: an automated memory system that accumulates project-specific knowledge across sessions, paired with explicit session boundaries, so that memory lives separately from the codebase it describes 2. A dedicated note directory is the lightweight version of that idea.

A useful prompt to pin down the location, adapted from the practice of specifying exact files and constraints in agent instructions 3:

Write all progress notes, plans and scratch output to .agent/<task-slug>/.
Never create new top-level files. Never write notes into src/, tests/ or docs/.
Before you finish, print the contents of .agent/<task-slug>/plan.md so I can review it.

That last line matters. It turns the note from hidden state into something you can read without it ever touching the index.

Excluding the directory with .gitignore and .git/info/exclude

Git has three ignore mechanisms, and they serve different audiences: .gitignore is committed and shared, .git/info/exclude is local to one clone and never committed, and a global file at ~/.config/git/ignore applies across every repository on the machine 4.

For agent notes you want the first two. .gitignore is the team-wide contract: it says "this repository expects a .agent/ directory and nobody commits it." .git/info/exclude is your personal belt-and-braces, useful when the agent invents a new filename pattern mid-task and you do not want to touch the shared file.

# .gitignore
# Agent working files: never commit
.agent/
.scratch/
*.agent.log
# .git/info/exclude
.agent/
.scratch/

Two details catch people out. First, a pattern like .agent/ matches the directory anywhere in the tree, which is usually what you want; if you only mean the root, anchor it with /.agent/. Second, ignore files apply to untracked files only. If .agent/plan.md was committed before you added the rule, Git will keep showing its modifications in git status forever, because the file is already in the index 4.

There is also a distinction teams miss: being ignored by Git does not make a file invisible to the agent. Many agents scan the filesystem directly rather than reading the Git index, and tools such as Cursor use a separate .cursorignore to control what gets indexed, independent of Git 5. Cursor's own users have reported that agent mode can read files that .gitignore excludes, which is why the maintainers discussed a separate hard-block mechanism 6. The practical consequence: ignore files keep notes out of the commit, but if the note contains a secret, an ignore file is not a security control. Keep secrets out of the notes directory entirely.

Global ignore is the wrong tool here. A rule in ~/.config/git/ignore protects you but not your teammates, and it silently changes behavior in unrelated repositories 4. Reserve it for OS cruft like .DS_Store.

Verifying before you commit

Two commands cover almost every case, and both are cheap enough to run before each commit.

git status --short shows you untracked files with a ?? prefix. If a .agent/ path appears there, the ignore is not working: either the pattern is wrong, or the file is already tracked. git diff --cached --name-only lists exactly what is staged, and it is the authoritative answer to "what is about to go into this commit."

# What is staged right now?
git diff --cached --name-only

# Anything from the notes directory staged?
git diff --cached --name-only | grep -E '^\.(agent|scratch)/' && echo "BLOCKED: notes staged" && exit 1

When you cannot tell why a file is or is not ignored, git check-ignore -v names the file, the line and the pattern responsible 4:

$ git check-ignore -v .agent/plan.md
.gitignore:3:.agent/	.agent/plan.md

That output tells you whether the shared rule, your local exclude, or a global rule is doing the work, which is precisely the information you need when a teammate reports different behavior from yours.

A worked example makes the cost concrete. Suppose an agent writes a 40-line progress note after each of 12 steps, and the note is 2 KB. A 24 KB file inside a pull request is not catastrophic, but it does three things: it adds noise to review, it creates merge conflicts across branches, and it becomes a search result for anyone grepping the repository later. Multiply that by every agent run on the team and the diff stops being readable. The same argument applies to context budget: a bloated lockfile or coverage folder displaces the source the agent should be reading 5. Notes files are the same problem, from the other direction.

Catching files that are already tracked

Ignore rules do not untrack. If .agent/plan.md is in the index, editing it will show up as a modification no matter what .gitignore says. The fix is one command, then a commit:

# Stop tracking, keep the file on disk
git rm --cached -r .agent/
git commit -m "Stop tracking agent working files"

After that commit, the ignore rule applies and the file stays on your disk but leaves the repository history, except for whatever was committed earlier. If the notes contain anything sensitive, removing them from the working tree is not enough: they remain in history until you rewrite it, which is a decision for the repository owner, not a routine cleanup.

git update-index --skip-worktree and --assume-unchanged are not substitutes. They are index hints for tracked paths, and Git documents --assume-unchanged as a performance optimization for expensive stat calls, not a way to hide personal edits 4. Using them to hide agent notes means a future git reset silently resurrects the file. To audit whether flags are already set in a repository you inherited:

git ls-files -v | grep '^[a-zS]'

Treat the output as a cleanup list. Uppercase S marks skip-worktree paths, and lowercase markers can reveal assume-unchanged entries 4.

A pre-commit check that fails closed

Manual verification works until someone commits under time pressure. A pre-commit hook makes the rule mechanical. Keep the pattern in one place so the hook and the .gitignore cannot drift apart.

#!/bin/sh
# .githooks/pre-commit
PATTERN='^\.(agent|scratch)/'

staged=$(git diff --cached --name-only --diff-filter=ACMR)
if echo "$staged" | grep -qE "$PATTERN"; then
  echo "Refusing to commit agent working files:"
  echo "$staged" | grep -E "$PATTERN"
  echo "Move them to .agent/ (ignored) or unstage with: git restore --staged <path>"
  exit 1
fi

Enable it with git config core.hooksPath .githooks. The --diff-filter=ACMR flag limits the check to added, copied, modified and renamed paths, so deletions of stale notes do not trip the hook and block the very commit that removes them.

The hook should fail closed, and the error message should say what to do. Compare with the general advice on agent verification: give the agent a check it can run, such as tests, a build or a diff to compare, because without verification it stops when the work merely looks done 7. The same logic applies to the human side of the loop. The hook is the check that runs whether or not anyone remembered.

Put the pattern in a single file that both the hook and the ignore rule read, or at minimum keep a comment in .gitignore pointing to the hook, so a reader changing one knows the other exists.

Multiple agents sharing one workspace

Two agents in one working tree will collide over the same note paths. The solution is per-agent subdirectories keyed by a stable identifier, with one shared ignore rule covering all of them.

.agent/
├── fe-4821/
│   ├── plan.md
│   └── progress.log
└── api-9107/
    ├── plan.md
    └── progress.log

Keep the parent ignore rule as .agent/ so new subdirectories are covered automatically. Give each agent its own slug from the task or ticket identifier, and tell it in its instructions to write only under .agent/<its-slug>/. That prevents two agents overwriting each other's plan and makes cleanup trivial: delete the slug directory when the task closes.

For genuinely long autonomous runs, a sibling worktree is the cleaner boundary. Each agent gets its own checkout of the same repository, so the main tree stays clean and only finished, human-reviewed commits ever land there. The tradeoff is that the worktree has its own .gitignore-covered paths and its own index, so the pre-commit hook has to be installed in each one. If you use core.hooksPath, that configuration is per-clone, and a linked worktree shares the hooks directory of its parent, which is convenient but means a single hook applies everywhere.

Whichever model you choose, the instruction to the agent should be explicit about identity and location, in the same spirit as Anthropic's advice to be clear and direct and to give reasons for constraints rather than bare prohibitions 8. "Write your plan to .agent/fe-4821/plan.md; this file is ignored by Git so it will never reach a pull request, which is why it is the safe place for half-finished reasoning" is more likely to be followed than "do not commit notes."

If your team runs several agents across different model providers and wants a single place to drive them, that is the kind of setup Quanta Inference is built for: one OpenAI-compatible API key for text, image, video, audio and embedding models, with prepaid pricing in US dollars, so switching the model behind an agent does not mean rewriting the client. See https://quantaintelligence.ai/inference.

A short checklist

Before you hand a repository to an agent:

  • Create .agent/ and add .agent/ and .scratch/ to .gitignore, plus .git/info/exclude for your own clone 4.
  • Confirm nothing under those paths is tracked: git ls-files | grep -E '^\.(agent|scratch)/' should return nothing, and if it does not, run git rm --cached -r.
  • Install the pre-commit hook and set core.hooksPath.
  • Put the note location in the agent's persistent instructions, with the reason it exists 3.
  • Decide whether note content may include secrets, and if not, say so, because Git ignore does not stop an agent from reading the file 5.
  • Reserve one subdirectory per agent when more than one shares the tree.

The underlying principle matches the broader move from prompt engineering to context engineering: what the model sees and what the repository records are separate questions, and both need deliberate curation 1. Keeping working notes is good practice; keeping them out of the commit is what makes it safe practice.

FAQ

FAQ

Why not just tell the agent not to commit its notes?

Instructions fade. Models lose track of earlier requirements as context grows, which is exactly why separate planning and execution phases and event-driven reminders exist in agent architectures 2. A Git-level rule does not depend on the model remembering anything, so it holds across resets, model swaps and long runs.

Does .gitignore stop the agent from reading the notes?

No. .gitignore controls what Git tracks, not what an agent can open on the filesystem, and tools like Cursor use separate ignore files precisely because Git ignore is insufficient for indexing 5. Treat the notes directory as readable by the agent and keep secrets elsewhere.

What if the notes file is already committed?

Ignore rules only apply to untracked paths, so run git rm --cached -r .agent/ and commit the removal 4. The file stays on disk and the ignore rule takes effect from the next commit onward.

Should I use a global ignore file instead?

Only for machine-wide cruft. A global rule in ~/.config/git/ignore protects your clone but not your teammates' clones, and it applies to every repository you touch 4. Use the committed .gitignore for anything the team should share.

Two agents keep overwriting each other's plan file. What changes?

Give each agent its own subdirectory under .agent/, keyed to a task or ticket identifier, and state that path in the agent's instructions. A single .agent/ ignore rule covers all subdirectories, and deleting a finished slug directory is the whole cleanup.

Sources

Sources

  1. Effective context engineering for AI agents - Anthropic
  2. Building AI Coding Agents for the Terminal - arXiv
  3. Fundamentals | Agentic Coding Handbook
  4. Three Ways to Ignore Files in Git (Beyond .gitignore)
  5. The Ultimate .gitignore for the AI Era
  6. Questions on .gitignore, .cursorignore, .cursorban - Feedback
  7. 25 Best Prompts for AI Coding Agents - SSOJet
  8. Prompting best practices - Claude Platform Docs
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles