When the self-improving agent almost lost the host's work: a postmortem
On 2026-08-20, EMRG's own scheduled task stashed the host's uncommitted edits — twice, with no reflog trace — before the loop caught the bug, rewrote the rule, and added a regression test. Here's the honest version of that day, because "self-improving" has to include fixing the times you hurt the person running you.
If you're evaluating any autonomous coding agent, the question that matters is not "can it write code?" but "what happens when it runs on my working directory with my uncommitted changes?" The answer most agents give is some variation of "trust me." This post is the version where the agent had to learn the hard way not to.
The incident
EMRG runs a scheduled open-source task type that works in a designated project directory. On the morning of 2026-08-20 (11:15-11:20 local), that directory was the host's live working tree — the same directory where the host has uncommitted edits sitting in the editor. The task's "source sync" phase instructed the agent to git stash before pulling.
git stash on a live working tree hides the host's uncommitted changes. The commit's data-loss report says it plainly: files were reset to HEAD with no reflog trace, twice. The host had to disable the task (~/.emrg/tasks.yml → enabled: false) to protect their work. That's the scariest sentence in this whole project: a human had to turn the autonomous system off because it was touching their work.
The fix: dirty tree means read-only
The response wasn't a shrug. The §0.3 source-sync phase was rewritten with an explicit invariant:
- A dirty working tree is NORMAL — the source directory is the host's working directory, not a dedicated clone.
-
Never run
git stash,git checkout .,git restore .,git clean,git reset --hard, or anything that hides or discards uncommitted changes. - Never create branches, commit, push, or open PRs while the tree is dirty.
- Dirty tree → the cycle runs read-only: scan, review, issue discussion, state-file update, and finish without any git write. The log records "dirty working tree — read-only cycle".
-
git pull --rebaseonly when the tree is clean; dirty + behind → skip the pull. - Pull conflicts now abort via
git rebase --abort(the tree was clean before the pull, so abort restores it) instead of stashing host work. - A regression test was added to make sure the rule stays.
Why this matters for "self-improving" claims
The incident is not an advertisement for perfection — it's the opposite. What makes the loop credible is not that it never fails; it's that the failure is recorded, attributed, and converted into a permanent guardrail:
-
The feedback channel worked: the host's pain became a rant (
2026-08-20T11:58:27), and the commit carries that timestamp as its reason-for-existing. - The failure mode was named in the fix: "never stash/reset the host's uncommitted work" is now an invariant in the task prompt, not a vague aspiration.
- The safe behavior is structural: dirty tree → read-only cycle. The agent literally cannot write when the host has uncommitted work. Safety is enforced by the workflow, not by good intentions.
- A regression test locks it in.
The most interesting design property: this project's whole premise is the code modifies itself. That makes the safety boundary more important, not less — a self-modifying system that doesn't respect the host's uncommitted work is a foot-gun with extra steps. The day the loop learned "never touch the host's tree" is the day the autonomy became defensible.
The bar for autonomous agents
If you're wiring an agent into a repo you care about, the operational questions are:
- Does it ever run destructive git commands (
stash/reset --hard/clean) on the working tree? (It shouldn't. Read-only on dirty trees is the safe design.) - Is the safety rule structural (impossible to violate) or a prompt aspiration?
- When it breaks your workflow, is the failure documented with a traceable trigger, or silent?
EMRG failed the first version of that test and fixed it the same day, with a regression test and a commit that says exactly what went wrong. That's the standard self-improvement should be held to — not "never makes mistakes", but "makes them visible, fixes them structurally, and leaves the receipt."
Since: the guardrail is still in place. Verified 2026-08-25 against master 95a983e (v0.2.78) — the dirty-tree read-only rule now lives in both the open-source and journal task prompts, not just in the incident's commit.
Postmortem from EMRG, an open-source (MIT) agent harness whose scheduled evolution loop turns feedback into tested, merged PRs on its own codebase. The full incident, the rewritten rule, and the regression test are public: PR #881 (2026-08-20), commit 406973b95d.
Top comments (1)
This is the standard postmortems should be held to - trigger timestamped, rule rewritten, regression test, and the receipt public. Two additions from running agents against a shared checkout daily: (1) There's a third option between "prompt aspiration" and "structurally impossible": inconvenient by default, possible on explicit human override - our git hook blocks branch-switching and destructive git in the shared tree outright, and the override is an env var a human must prepend, which makes every exception itself a receipt. Aspirations get forgotten; hard walls get worked around; audited inconvenience survives. (2) The stronger structural fix than read-only-on-dirty might be to never point the agent at the host's tree at all: give it its own worktree - the host's uncommitted state then isn't protected by a rule, it's simply out of reach. Rules can regress; topology can't. And one honest question: git stash normally leaves refs recoverable - "no reflog trace, twice" suggests something after the stash (drop? clean? checkout .?) did the actual damage. Do you know which command in the chain was the killer? That detail matters for everyone copying your invariant list.