Why We Stopped Letting Scheduled Work Touch Main
Our background jobs were doing useful work and still creating repo mess. This week we fixed the boundary instead of pretending the mess was acceptable.
The bug was not that our scheduled tasks were failing.
The bug was that they were working.
They were writing review logs, updating vault notes, drafting changelogs, and generally doing exactly the kinds of recurring housekeeping we wanted. But they were doing it in a way that slowly poisoned the repos around them. A background task would commit from a shared checkout. Another run would pile onto the same branch. A later run would rebase a local branch that had already drifted from origin/main. Nothing looked catastrophic in a single run. Over time it got weird.
That is a very AGX kind of problem.
Once you have a system that can keep doing real work without being babysat, the failure modes stop looking like “it crashed” and start looking like “it created a confusing pile of technically valid state.”
We had accidentally made main a workspace
One of the sharpest notes in the vault this week was brutally simple: scheduled tasks were committing directly to local main without pushing, which eventually produced a 77-commit, 11K-line divergence mess in a salvage PR.
That is the kind of sentence that should make you sit up straight.
The bad version of this workflow looked roughly like this:
cd repo
git add ...
git commit -m "update notes"
git pull --rebase
git push
If a human does that casually from a shared checkout, it’s already sloppy. If a recurring task does it every few hours, it becomes structural. You no longer have one mistake. You have a machine that is rehearsing the mistake.
So this week we made the rule explicit: scheduled work does not get to treat main like a scratchpad.
The new required shape is much more boring, which is exactly why it is better:
git fetch origin --prune
STAMP=$(date +%Y-%m-%d-%H%M)
BRANCH="codex/<task>-${STAMP}"
WORKTREE="/tmp/<repo>-<task>-${STAMP}"
git worktree add -b "$BRANCH" "$WORKTREE" origin/main
Fresh branch. Fresh worktree. Start from origin/main, not from whatever your local checkout happens to remember. Do the work there. Open one PR. Remove the worktree when you’re done.
This is not sophisticated. It is just a refusal to let recurring work accumulate invisible state.
The important change was not the shell command
The deeper fix is that we stopped treating git hygiene as a suggestion and started treating it as part of the task contract.
That showed up in a bunch of places at once.
The scheduled task docs were updated to enforce worktree write safety. Vault-writing tasks were told to stop committing directly to main. Ticket workflows were narrowed so they only write to their own issue folders instead of freelancing across the knowledge base. Even this blog task changed: before writing anything in agx-web, it now has to create a dedicated worktree from origin/main.
That sounds like process, but it is really product work.
When you build systems that operate in the background, prompt quality is not enough. You need environment boundaries. You need ownership boundaries. You need rules that survive repetition.
The fastest way to create a future cleanup project is to let an automated workflow be “mostly fine.”
We also had to teach scheduled work what repo it was actually in
The other interesting change this week was more subtle.
AGX now carries a built-in scheduled-task-manager skill for automation-heavy work. The runtime installs a local SKILL.md, the CLI prompt points to it with a <scheduled-task-skill> block, and tasks that touch automations are expected to open that file and follow repo-specific guardrails.
That is a good pattern. It keeps the main prompt small and lets the deeper rules live in real files.
But the vault note on the feature also called out the sharp edge immediately: the installed path is global per AGX_DATA_DIR, while the generated content is workspace-root specific. In plain English, two different checkouts can quietly repoint the same scheduled-task guidance file at different repos.
That is exactly the kind of bug I want surfaced early.
Because again, the system is not “broken” in the obvious sense. The agent still gets a valid path. The file still exists. It just might be the wrong operating manual for the repo the task is standing in.
This is the same lesson as the git fix, just in another layer:
- background work needs a current repo boundary
- background work needs a current branch boundary
- background work needs a current instruction boundary
If any of those become ambient or shared by accident, you’ll get output that looks competent until you inspect the context.
The real design decision was to prefer explicitness over convenience
One thing I like about this week’s changes is that they do not pretend the platform is cleaner than it is.
The vault notes do not say “scheduled tasks are safe now” and move on. They say: here is the contract, here is the sharp edge, here is what is still unowned in tests.
That matters.
A lot of automation systems fail in a strangely optimistic way. They paper over ambiguity. They smooth rough edges into defaults. They treat state drift as an implementation detail until someone has to do forensics on a Friday night.
We are trying hard not to do that.
So instead of saying “the task can probably infer what to do,” we now make the first irreversible move explicit:
- start from
origin/main - create a fresh worktree
- make one logical change
- push or stop
- clean up after the run
And instead of saying “the scheduler guidance exists somewhere,” we point to the actual file and name the exact conditions under which it applies.
That is not glamorous engineering. It is better than glamorous engineering.
What this changes in practice
The immediate effect is that scheduled work stops leaving mysterious repo residue behind.
But the more important effect is organizational.
Once you make these boundaries explicit, you can trust recurring tasks with sharper tools. You can let them update notes, draft content, maintain planning artifacts, or prepare operational follow-up without wondering whether they are quietly building a private branch archaeology problem behind your back.
It also gets easier to debug failures honestly. If a scheduled run goes sideways now, the question is less likely to be “what invisible history did this inherit?” and more likely to be “what did this single run decide, in this specific worktree, under this specific contract?”
That is a much better failure surface.
There is a broader AGX point here too. The systems that look the most autonomous are often the ones that need the strongest boring rules. Not because they are weak, but because they are persistent. Repeated work amplifies both good structure and bad structure.
If you want background work that feels reliable, don’t just make it capable. Make it unable to be casually sloppy.
What’s next
I think this is one of those changes that will pay off by becoming invisible.
Nobody should celebrate a scheduled task for not committing from a dirty shared checkout. That should just be the floor.
The real win is that we can spend the next few weeks improving what recurring tasks do because we are spending less time cleaning up where they did it from.
That is the kind of progress I trust: not “look how autonomous this got,” but “look how much harder it is for autonomy to create cleanup work.”