Local or Remote? A Decision Rule for Scheduling AI Agents
Once you can put an autonomous coding agent on a cron, the interesting question stops being what it should do and becomes where it should run. There's a clean rule for that, and it has almost nothing to do with the task. It comes down to how the job authenticates to the outside world.
I have been running a handful of scheduled agents against a production codebase — jobs that drain a translation queue, generate review drafts, and triage an inbox. Each one is a short prompt fired on a schedule, with tools attached. The first time you wire one up, you spend your energy on the prompt. By the third, you realize the prompt was the easy part. The decision that actually matters is the runtime: does this agent run in a cloud sandbox or on your own machine?
Both are real options, and they fail in different ways if you pick wrong. Here is the model I landed on.
Routines in Claude Code — and who else offers them
Scheduled autonomous agents have quietly become something you can buy off the shelf. Anthropic ships routines in Claude Code; OpenAI has scheduled tasks in ChatGPT; and plenty of teams bolt an agent onto a plain cron job or a GitHub Actions scheduled workflow. The vocabulary differs, but the shape is identical: a prompt, a schedule, and a set of tools.
Everything in this piece, I run with Claude Code routines. It's a useful system to reason about because it exposes both runtimes directly — a remote routine that executes in Anthropic's cloud, and a local scheduled task that runs on my own machine. The same rule below tells me which to reach for each time.
The part that still surprises people
You don't hand-write any of this. I describe the job to Claude Code in plain language and it writes and registers the routine for me — drafts the prompt, picks the cron schedule, attaches the right connector, and creates the routine. The agent sets up the agent. What's left for me is the two decisions below, which no tool can make on my behalf.
Two places an agent can run
A remote routine executes in a managed cloud sandbox. It gets a fresh checkout of your repository, a clean toolset, and whatever connectors you attach — then it runs and disappears. Nothing persists between invocations. It never touches your laptop, which means it keeps running whether or not you are at your desk.
A local routine runs on a machine you control, inside your real working directory. It inherits that environment: your .env file, your network, any local services, your filesystem. It is only "up" when the machine is up.
The temptation is to choose based on the task — "this one feels important, run it in the cloud." That instinct is wrong. The task doesn't decide. The secret does.
The deciding axis is the secret, not the task
Every useful agent has to authenticate to something: an email account, a database, an object store, a third-party API. How it holds that credential is the entire decision.
If the job reaches the outside world through an MCP connector — a Model Context Protocol server you authorize once — then the credential lives inside the connector, held by the platform. You never hand the secret to the agent. You attach the connector by reference and the platform injects auth at run time. That job is a perfect fit for a remote routine: the sandbox needs no secrets of its own, so the fact that it is ephemeral and off-machine costs you nothing.
If instead the job needs raw secrets from your .env — a database URL and token, cloud keys with a specific credential chain, anything your repo code reads from process.env — then a remote sandbox is the wrong home. It has no access to your local environment, and you should never smuggle those secrets in through the prompt (the prompt is stored and logged; that is how credentials leak). A local routine already has that environment sitting next to it. Run it there.
The rule
Connector-shaped access → remote. The connector holds the credential, so the cloud sandbox needs nothing. Secret-shaped access (.env, local services) → local. The machine already holds what the job needs, and secrets never have to travel.
| Job needs… | Run it | Why |
|---|---|---|
| An MCP connector (email, chat, SaaS) | Remote | Credential lives in the connector; sandbox carries no secret and runs even when you're offline. |
Database / object-store / API keys from .env |
Local | The cloud sandbox can't see your environment, and prompts are the wrong place for secrets. |
| Both a connector and local secrets | Local | The hard constraint wins. Local can also reach a connector; remote can't reach your .env. |
In practice this sorts itself cleanly. My inbox-triage agent only needs an email connector, so it runs remotely on an hourly cron and never depends on my laptop being awake. My translation worker and draft generator both read a Turso database and write to S3 with specific credentials — those stay local, where the environment they need already exists.
Instructions are code, so version them
The second thing I changed was where the instructions live. The obvious approach is to paste the agent's instructions straight into the routine's config in a UI. It works, and it rots immediately. The moment you have two or three routines, you have two or three blobs of important operational logic sitting outside source control — unreviewable, undiffable, and prone to drift.
So I inverted it. The instructions live as a Markdown file in the repository, under version control, and the routine config is a thin pointer that tells the agent to go read it:
---
name: email-triage
description: Read and reply to inbox for the project
---
Read and follow these instructions: docs/routines/email-triage.md
That is the whole config. Everything that actually governs behavior — the classification rules, the reply templates, the safety limits — is a reviewed file in the repo. The payoff is the same payoff you get from treating anything as code:
- One source of truth. I had a routine whose pasted instructions had silently diverged into a stale copy of a doc that already existed in the repo. Pointing both at the same file made that class of bug impossible.
- Review and history. A change to how an agent emails real people now goes through a diff and a commit, not an untracked text box.
- Shared across runtimes. A local routine and a remote one can read the same file. Local and cloud stay byte-for-byte identical because they literally share the instructions.
What the remote sandbox takes away
Choosing remote buys you independence from your machine, but it takes something back, and you have to design around it: there is no persistent filesystem between runs. Each invocation starts clean. Any state you scribble to disk is gone by the next hour.
That breaks the lazy way of making a recurring job idempotent — a local file of "already handled" IDs. On a remote routine that file never survives, so the agent would happily reprocess the same work every run. The fix is to push the dedup state into the system the agent already talks to. For my inbox agent, that meant labeling each handled message server-side and skipping anything already carrying the label. The mailbox itself becomes the memory. It is more robust anyway — it survives across machines, sessions, and runtimes, none of which a local scratch file does.
Two guardrails that fall out of the same reasoning:
- Never put a secret in the prompt or the config. Both are stored and logged. If a job's auth can't be expressed as an attached connector, that is a strong signal it belongs local.
- Don't assume your
process.envexists remotely. Repo code that reads environment variables will find them empty in the sandbox unless the value arrived through a connector or a managed environment.
A note on permission modes
A scheduled agent runs with nobody watching, which pushes permissions to the front. Interactively, Claude Code asks before it edits a file or runs a command. A routine can't field those prompts — there's no human at the keyboard at 9am — so you set its authority up front. Two ways to do that: an explicit allowlist of the exact tools it may use, or an auto mode that classifies each action and auto-approves the ones it judges safe.
Here's the practical part. If the routine is non-destructive — it only reads, or it only writes to a queue, a draft table, or scratch space it owns — then auto mode is a real time-saver. There's nothing to guard, so prompting for every step is pure friction; let it run unattended. I lean on this for the read-and-summarize and draft-generating jobs, and it's the difference between a routine that quietly does its work and one that stalls forever waiting on a confirmation no one will click.
The moment a routine can do something irreversible — email real people, write to production, delete anything — I tighten back up: a narrow allowlist, or a human-in-the-loop step where the agent proposes and I approve. Match the leash to the blast radius. Auto mode for the harmless jobs; a tight allowlist for the ones that bite.
The rule of thumb
When I add a new scheduled agent now, I don't start with the prompt. I ask one question: how does this job authenticate? If the answer is "through a connector," it runs in the cloud and I stop worrying about my laptop. If the answer is "with secrets from my environment," it runs local. Then — wherever it runs — its instructions go into a versioned file, and the routine just points at it.
Two questions, really. Where does the secret live, and where do the instructions live. Get those right and the prompt is the easy part again.