TL;DR This is about Claude’s scheduled cloud routines specifically, which is the only agent platform I have tried it on. I moved my routine prompts out of the scheduler and into a git repo, leaving a fifteen-line stub behind that reads its instructions from main at runtime. Changing a routine is now a commit rather than a form edit, and each routine has standing authority to open a PR against its own prompt when it trips over a problem. I set this up today, so I have no idea yet whether the self-improvement PRs will be worth merging.

I run a handful of scheduled Claude routines. They fire on a cron in a cloud sandbox, do a job with no human watching, and report back. Everything below describes that setup: the file layout would carry over to another scheduler, but the sandbox details and the API behaviour are Claude’s, and I have not tried any of this elsewhere. Each one is driven by a long prompt: a few hundred lines of methodology, hard rails, and output contract, built up over months of watching runs go wrong.

I lean on them now. One fires every hour on weekdays, takes the top unclaimed ticket off my team’s board, claims it so nothing else races it, writes the change, opens the PR, then sits on CI and answers the review bots until a human could sign it off. Another sweeps my own open PRs once a week, rebases the stale ones and fixes whatever CI is unhappy about.

The rest watch things. One audits autoscaler sizing across our clusters against thirty days of metrics and opens a PR only where the numbers justify one. Another posts a quality summary over a production agent every morning, and a weekly sibling of it writes a dated review doc, opens the PR for that, and mirrors it into our docs.

None of them merges. The output is a PR, a comment or a message, and I make the call on it, which is the part that lets me leave them running overnight without checking in.

Until today each prompt lived in two places. The scheduler held the one that ran. A copy sat in a git repo that I kept in sync by hand. To change a routine I edited the file, then pushed the same text through the API, and skipping the second step changed nothing at all. Skipping the first left the repo lying to me three weeks later, so I stopped keeping the prompt in the scheduler at all.

Pointer in the scheduler, instructions in git Link to heading

The live prompt is now a stub, about fifteen lines:

# weekly-audit - routine stub

**Your instructions are NOT in this prompt.** The binding instructions are
`weekly-audit/PROMPT.md` on `main` of `me/agent-routines`, attached to this
run as a source repo. Read it FIRST, in full, and follow it exactly. On any
conflict between that file and this stub, **PROMPT.md wins**.

If the file cannot be read at all, STOP: report the error and exit. Do NOT
improvise the routine from memory.

Everything the routine does lives in PROMPT.md in the repo. To change behaviour I commit and push, and the next run picks it up. No API call, no drift, and I get a diff and a blame line for free.

Keep the “do not improvise” sentence. Without it, a model that cannot find its instructions will reconstruct something plausible from the routine’s name and run that. I would rather the run do nothing than invent its own job at 7am with a write token.

The split Link to heading

I keep two files per routine:

  • STUB.md lives in the scheduler: the pointer plus the sandbox mechanics. Which credentials sit in the environment, how to reach the API when git transport is blocked, how to bootstrap the CLI tools, where to send output.
  • PROMPT.md lives in git and gets read at runtime: the methodology, the rules, the definition of done.

Mechanics stay in the stub because you need them to fetch anything at all. When the pointer misses, the run can still tell me what it tried and where it looked. Move the mechanics into the fetched file and a bad fetch leaves a three-line prompt and nothing to debug with.

The layout:

agent-routines/
  AGENTS.md               how the repo works, which edit needs a sync
  ROUTINE.md              rules shared by every routine; fetched every run
  weekly-audit/
    STUB.md               lives in the scheduler; pointer + mechanics
    PROMPT.md             lives in git; fetched every run
  nightly-digest/
    STUB.md
    PROMPT.md

Editing PROMPT.md needs a push. Editing STUB.md needs a push and a re-sync. Nearly everything I touch is the first kind.

Attaching the repo to the routine Link to heading

The routine needs the repo attached. Repos attached to a Claude routine as sources land in the sandbox before the run starts, so the stub reads its instructions off local disk. The API fetch is a fallback: the token in the sandbox covers the org repos and not my personal one, so it will often 404. Belt and braces:

P=$(find "$HOME" /workspace . -maxdepth 6 -path "*agent-routines/weekly-audit/PROMPT.md" 2>/dev/null | head -1)
[ -n "$P" ] || { curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github.raw" \
  ".../contents/weekly-audit/PROMPT.md?ref=main" > /tmp/PROMPT.md && P=/tmp/PROMPT.md; }
cat "$P"

The gotcha that cost me a canary run Link to heading

The API that updates a routine looks like it takes a partial update, and it replaces the whole object instead. I sent a job config carrying only the new prompt, and the write dropped the rest of the session context: attached repos, model selection, allowed tool list. The routine came back alive, pointing at no repos, running the default tool preset.

I caught it because I tried it on the weekly routine with three repos rather than the hourly one with sixty, then read the response back instead of trusting the 200. Two rules I now apply to any config API I have not mapped:

  1. Canary on the cheapest resource, not the most important one.
  2. Read the echoed object, field by field, against what you sent. A 200 means the write happened, not that it did what you meant.

Letting the routines edit themselves Link to heading

The routine can already read and write this repo, so giving it authority over its own prompt took one paragraph. That paragraph lives in a single ROUTINE.md at the repo root, which each stub reads alongside its own PROMPT.md. I write the rule once instead of pasting it into eight stubs and watching seven drift:

If this run hits a problem with your own instructions, a stale fact, a command that no longer works, a contradiction, a gap that cost you time, fix it at the source. Open one small PR against the routines repo editing your own PROMPT.md or STUB.md, or ROUTINE.md when the fix applies to every routine rather than yours. Then send me the link. This is a proposal, not a blocker: never let it displace the actual task.

It opens a PR and never pushes to main, so the worst a bad self-edit costs me is closing a pull request. The link comes straight to me as well: no team review channel, no reviewer group, no ticket. My routines’ housekeeping is not my colleagues’ queue, and an agent that can file work into a shared inbox will fill it faster than anyone drains it.

The prompt also ranks the fix below the job. A routine that spends its hour polishing its own instructions has failed the ticket it was meant to ship, however good the diff.

I expect small stuff: a flag that changed upstream, a fact that went stale, two steps in an order that trips the model every time. None of those ever got fixed while fixing them meant me noticing, remembering, and hand-editing a prompt in a web form. Now the run that hit the wall drafts the patch and I read a diff. Whether the diffs turn out to be worth merging is the part I cannot answer yet, and I will know more once a few have landed in front of me.

Further reading Link to heading