| name | agent-work-artifacts-layout |
| description | Where to put transient files, one-off scripts, audit reports and PR bodies the agent creates while working. |
| tags | ["workflow","hygiene","gitignore","scripts"] |
Agent work artifacts: where do they go?
When to use
Any task where the agent produces files that are not the user's
intended deliverable: PR body drafts for gh pr create --body-file,
throwaway repro_*.py / check_*.sh scripts, audit reports,
analysis dumps, temporary fixtures. Without a convention these end
up either polluting the working tree (triggering "do I need to
delete this?" round-trips) or getting lost across sessions even
when they had reuse value.
Problem
Three recurring failure modes:
- PR body files left in the repo root (e.g.
pr_body.md),
forcing the user to confirm deletion after every PR.
- One-off scripts committed to random places — sometimes in
the repo root, sometimes nowhere — making future contributors
unsure whether they are project tooling or dead weight.
- Fixed temp paths like
/tmp/pr_body.md that collide between
parallel agent sessions.
Solution
Route every artifact through this four-way decision:
1. Single-use (PR body, gh intermediate, shell pipe)?
→ system temp via mktemp -t <slug>.XXXXXX.<ext>
(POSIX: $TMPDIR or /tmp; Windows: %TEMP%)
2. Repo-scoped helper, human-authored, part of project lifecycle?
→ scripts/ (or tools/ if the repo already uses that)
with argparse CLI, docstring, usage example.
3. Repo-scoped helper, agent-authored, reviewed and reusable?
→ .agent/tools/ (tracked subdirectory of the agent working area,
see the four-cell layout below)
4. General-purpose, useful across projects?
→ offer to publish as a GitHub Gist.
5. Agent working notes / drafts / caches NOT meant for sharing?
→ .agent/scratchpad/ | .agent/artifacts/ | .agent/context/
(gitignored, local-only)
If none apply, delete the file rather than leaving it ambiguous.
.agent/ four-cell layout
.agent/ is not a single bucket — split it by lifetime and
tracking intent:
| Subdirectory | Purpose | Tracked? |
|---|
.agent/scratchpad/ | Free-form notes, pseudo-code, drafts. | No |
.agent/artifacts/ | Generated deliverables (exported docs, images, one-off reports). | No |
.agent/tools/ | Reviewed agent-authored helpers for this repo. | Yes |
.agent/context/ | Project index / knowledge-base caches / embeddings. | No |
The tracked subset is surfaced via a whitelist-style .gitignore
(see Example). Keeping .agent/tools/ in Git lets genuinely reusable
agent-authored helpers survive across sessions without mixing with
the project's human-authored scripts/.
scripts/ vs .agent/tools/
They coexist, with clear provenance separation:
scripts/ (if/when the repo has one) = human-authored core
tooling that's part of the project's normal lifecycle
(release, lint, bootstrap).
.agent/tools/ = helpers written by an agent, reviewed, and
kept because they proved useful. Provenance is tagged at the
directory level — no silent promotion to scripts/, only via
a dedicated "graduation" PR.
Example
Right way — PR body in system temp
body=$(mktemp -t pr_body.XXXXXX.md)
gh pr create --repo <owner>/<repo> --base <default> --head <slug> \
--title '<title>' --body-file "$body"
Wrong way — PR body in repo root
vi pr_body.md
gh pr create --body-file pr_body.md
Right way — commit message via repo-local scratchpad
In an agent terminal, commit flows that chain mktemp + git commit
rm often trigger human-confirmation prompts on every commit.
.agent/scratchpad/ sidesteps this by removing both mktemp and
the trailing rm:
MSG=.agent/scratchpad/commitmsg.$$.txt
printf '%s\n' 'Subject line' '' 'Body paragraph.' > "$MSG"
git commit -F "$MSG"
This applies to commit messages, not PR bodies — PR bodies
should still use system temp (see previous example) because they
are one-shot and benefit from OS-level cleanup.
See shell-heredoc-and-multiline-strings
for when to choose each temp location.
Right way — repo-scoped helper that others might reuse
<repo>/scripts/check_doc_consistency.py # committed, with argparse + docstring
Right way — agent working area with whitelist gitignore
<repo>/.agent/
README.md # tracked: layout convention for contributors
scratchpad/ # ignored: notes, drafts
artifacts/ # ignored: generated deliverables
context/ # ignored: indexes, caches
tools/
README.md # tracked: intake standard
<helper>.py # tracked: reviewed agent-authored helpers
<helper>.sh # tracked
Corresponding .gitignore block (whitelist pattern — blanket ignore
first, then un-ignore each tracked path):
/.agent/*
!/.agent/README.md
!/.agent/tools/
/.agent/tools/*
!/.agent/tools/*.py
!/.agent/tools/*.sh
!/.agent/tools/README.md
Verify with git check-ignore -v after editing — it's the only
way to be sure reopens line up with blanket ignores.
Intake standard for .agent/tools/
Because helpers here are tracked, they need a quality bar. Every
committed script should have:
- Provenance header naming the agent session.
argparse CLI with --help.
- Dry-run default;
--apply / --write to mutate.
- Module-level docstring with a real usage example.
- Stdlib-only (or deps the project already has).
- UTF-8 safe (
encoding="utf-8" on file I/O).
- Introduced in its own dedicated PR, not smuggled in.
Document this in a .agent/tools/README.md at repo setup time.
Pitfalls
-
The convention is inert without an actual .gitignore. Writing
this skill, or a .agent/README.md, or just telling the agent to
"put drafts in .agent/scratchpad/" does not make those files
ignored. Without a committed .gitignore entry they show up as
?? in git status, and a single git add -A (easy to type when
rushing a commit) sweeps them into the tree. Verify on day one:
git check-ignore -v .agent/scratchpad/anyfile.txt
If that command prints nothing, the convention is a lie and the
next git add -A will betray it. This skill's own repo shipped
without a .gitignore for its first nine PRs — nobody got bitten
only because every commit used explicit git add <path>. Don't
rely on that.
-
.agent/ is convention, not project API. A tracked
.agent/README.md explaining the layout is fine (similar in
spirit to .github/'s own README), but nothing in the build,
tests, or CI should read from .agent/. If a file needs to run
on fresh clones or other contributors' machines, it doesn't
belong here — promote it to scripts/ with a dedicated PR.
-
Whitelist gitignore is order-sensitive. The blanket
/.agent/* must come before the !-prefixed reopens,
otherwise the reopens have no effect. Always verify with
git check-ignore -v <path> for both a tracked path and an
ignored path before committing.
-
Use .agent/scratchpad/ for commit-message drafts, not PR bodies.
Commit messages are produced many times per session and benefit
from a fixed local path (no mktemp, no rm, fewer agent-terminal
confirmation prompts). PR bodies are one-shot, often contain the
final version of release notes, and should stay in system temp so
the OS cleans them up — a lingering draft under .agent/scratchpad/
invites confusion about which version made it to GitHub.
-
Do not rely on .agent/scratchpad/, /artifacts/, or
/context/ for anything needed on another machine — they are
gitignored. Only .agent/tools/ survives cloning.
-
Windows: /tmp and mktemp are not portable. Prefer
tempfile.gettempdir() from Python when the caller might be on
Windows.
-
Do not confuse scripts/ with contrib/. In PostgreSQL /
Git, contrib/ means "distributed alongside core but not part of
it". Unless the target repo already uses that convention, stick
with whichever of scripts/ or tools/ the repo already has.
-
Do not silently "graduate" .agent/tools/ scripts to
scripts/. Promotion is a conscious decision that warrants a
PR of its own (with a new provenance line: "originally authored
by agent, audited and graduated"). Silent moves destroy the
provenance signal the .agent/tools/ location exists to preserve.
-
Industry precedent: CPython, Django, Kubernetes, Rust, LLVM
all ship a top-level scripts/ or tools/. No mainstream project
formalises an "AI agent working area" yet; .agent/ is the
convention this skill proposes, sitting alongside existing local
caches like .pytest_cache/, .mypy_cache/, .idea/.
See also