I can now leave a well-defined piece of work running at night and return in the morning to a review-ready merge request for Tilt.
That sounds like the usual AI promise: write one large prompt, close the laptop, and wake up to finished software.
The reality is less magical and much more useful.
Overnight delivery works because I spend more time defining what the agent is allowed to do, what it must prove, and where it has to stop. The model matters, but the operating system around the model matters more.
I recently turned that operating system into a written playbook for my work at Tilt. This article is the personal version of it.
It describes my own operating method, not an official Tilt policy, a client commitment, or a promise that every project can be completed overnight. I published the more structured delivery model as a HILLS Lab field note. Here I want to explain more of the reasoning behind the choices.
Why this matters at Tilt
Tilt is not a demo application where a slightly wrong answer can be fixed later without consequence.
The backend supports live commerce: auctions happening in real time, payments, shipping, seller workflows, operational tools, and integrations between systems that do not always fail in polite ways.
A feature can look small in Linear and still cross several boundaries in production. A new field may affect an API contract. A retry may create duplicate work. A migration may be technically valid but operationally unsafe. An external provider may behave differently from its documentation. A change that works in isolation may fail under the timing and concurrency of a live auction.
This is exactly where AI can be valuable, but it is also where vague autonomy becomes dangerous.
I do not want an agent that confidently does everything. I want one that can move quickly inside a clear contract and stop when reality leaves that contract.
Overnight starts before the prompt
The most important part of an overnight run happens while I am still at the computer.
I begin in Linear. I read the project context, linked documents, related tickets, comments, previous decisions, and the acceptance criteria. Then I compare that with the current code and existing merge requests in GitLab.
Slack is useful here too. Some of the real context around a feature lives in conversations: why a decision was made, which edge case support has seen, what product expects, or whether an external dependency is actually ready. That context can inform the work, but it should not remain an invisible requirement. Important conclusions need to make their way back into the ticket or another durable source.
The tools have different jobs
The workflow works because I do not ask one tool to become the source of truth for everything.
- Linear holds the product and delivery state: objective, scope, dependencies, acceptance criteria, comments, and status.
- GitLab holds the source state: repositories, branches, merge requests, reviews, and CI. I use
glabwhen the agent needs a current command-line view instead of relying on remembered state. - Slack handles time-sensitive coordination and the concise morning handover. It helps people respond, but durable decisions still belong in Linear or committed documentation.
- Cursor Agent performs the bounded local implementation and verification work.
- Sentry and Grafana provide runtime evidence when operational verification is part of the authorized scope. They are evidence sources, not automatic permission to touch production.
That separation matters. Linear says what should happen. GitLab shows what exists and whether the checks pass. Slack keeps people connected. Monitoring shows what the software actually did. Cursor can move between those systems, but it cannot silently borrow authority from one and apply it to another.
Before implementation begins, I want five things to be explicit:
- the outcome we are trying to create;
- the part of the system that is in scope;
- the parts that are deliberately out of scope;
- the evidence required before the work can be called ready;
- the decisions that still require a human.
"Deliver this overnight" is not a useful instruction by itself.
Does delivery mean code on a branch? A green GitLab pipeline? A merge request that has answered automated review? A merged change? A deployed and verified environment?
For my default workflow, overnight delivery means a review-ready merge request. Merge and production deployment remain separate actions unless I explicitly authorize them.
Why I use a MacBook instead of cloud runners
I currently run these longer Cursor Agent sessions locally on my Tilt MacBook. That is a deliberate trade-off, not a belief that laptops are better infrastructure.
The MacBook already has the exact development environment I use during the day: the repositories, package managers, local services, repository rules, GitLab access, Linear and Slack integrations, and the tools required to inspect the result. A local run can use that existing authorized context without copying a second set of credentials into a remote runner.
A cloud runner would give me useful isolation and reproducibility. It would also require another control plane: secure secret distribution, network access, repository setup, persistent handover state, artifact retention, cost controls, and a reliable way to reproduce the same integrations that are already working locally.
That extra infrastructure can make sense for repeatable team workflows, scheduled jobs, many parallel workers, or stricter sandboxing. At my current scale, building and maintaining it would add more operational surface than it removes.
There is a security reason too. Long-running delivery needs broad read access to context, but each task should still have a narrow write scope. Keeping the work on an already authorized company machine avoids spreading source code, development credentials, and integration tokens across another compute environment. It also lets me pause the run directly and inspect every worktree, command, branch, and generated artifact in the morning.
The local setup still needs basic operations. The machine stays connected to power and a stable network. Cursor remains open. Amphetamine keeps macOS awake for the authorized session. Authentication is checked before I leave so a six-hour run does not stop after twenty minutes.
Amphetamine solves machine availability. It does not make the software correct, and the MacBook does not make an unsafe prompt safe. Scope, permissions, stop conditions, tests, review, and CI remain the actual controls.
I expect this decision may change. If the workflow becomes shared infrastructure rather than my personal delivery system, isolated cloud workers could become the cleaner option. For now, the MacBook is the smallest system that preserves the environment I trust without creating a second platform I also have to operate.
Fresh context for every ticket
One of the easiest mistakes is asking one very long AI conversation to deliver an entire project.
The first ticket may go well. The second inherits useful knowledge. By the fourth, the conversation also carries stale repository state, superseded assumptions, old errors, and conclusions that may no longer be true.
For each new ticket or independent slice, I start a fresh worker.
It selects the intended repository and base branch, fetches the current GitLab remote, rereads the Linear ticket, loads the committed repository instructions, and checks related repositories before planning.
Old conversations can provide clues, but they are not authoritative. The current ticket, current code, current merge requests, and current documentation win.
This costs a little setup time. It saves much more time than debugging an implementation built on yesterday's state.
One ticket, one bounded workspace
Parallel agents make it very easy to create a fast mess.
I use isolated Git worktrees and focused branches so each worker has a clear area of ownership. Dependencies and merge order are decided before editing. If one merge request depends on another, that relationship is explicit rather than hidden in a local working tree.
The implementation rules are conservative:
- prefer additive contracts when possible;
- fail closed when authorization or required state is unclear;
- keep migrations and rollout behavior explicit;
- avoid unrelated cleanup;
- do not let two workers become conflicting writers;
- do not stage or commit files owned by another piece of work.
This is standard engineering discipline. AI increases the speed at which ignoring it becomes expensive.
For larger scopes, coordination gets its own context
When a project contains several dependent tickets, I do not keep all implementation inside one giant Cursor thread.
One orchestrator context owns the dependency graph and delivery manifest. It tracks active branches, merge order, reviews, CI state, blockers, and cleanup. Fresh workers implement bounded slices in isolated worktrees.
The orchestrator should not quietly become another writer. Once it starts editing everything itself, it loses the independent view required to coordinate the project and becomes more likely to collide with the workers it created.
I apply the same separation to meaningful reviews. A fresh reviewer is more useful than asking the implementation context to confirm that its own reasoning was correct.
The agent has stop conditions
A useful overnight agent needs permission to continue. A trustworthy one also needs instructions to stop.
My stop conditions include ambiguous product decisions, missing credentials, unknown commercial values, breaking API changes, destructive operations, production exposure, security uncertainty, and any action outside the authorized scope.
If one part is blocked, the agent can continue independent safe work. It can improve tests, document an assumption, prepare a migration without running it, or finish another isolated slice.
What it cannot do is invent a product decision because I am asleep.
This distinction is important. Autonomy should remove waiting from well-understood work. It should not remove humans from decisions that carry responsibility.
Verification is part of implementation
I do not consider the coding phase complete when the code compiles.
The worker runs the relevant formatter, linter, type checks, and tests locally. It opens the GitLab merge request with the Linear issue linked, explains the change, and records anything that was not verified.
Then GitLab CI becomes another source of truth.
A local green run does not guarantee the pipeline will agree. The pipeline may reveal an environment difference, a broader test failure, a generated artifact, or a contract the local worker did not know existed.
I also use independent review. The context that implemented a solution is naturally biased toward the solution it chose. A fresh reviewer is more likely to question authorization, concurrency, rollback behavior, observability, migration safety, and whether the acceptance criteria were actually met.
Automated review comments are resolved like normal engineering feedback. Valid findings are fixed. Incorrect findings are answered with evidence. The goal is a clean review thread, not a green badge produced by ignoring the difficult comments.
What I expect in the morning
The best overnight result is boring to inspect.
I want a focused GitLab merge request with a clear description, green checks, a small diff for the stated outcome, linked Linear context, resolved automated review, and a short list of exact human actions still required.
For a larger scope, I want a compact delivery manifest:
- each merge request and what it changes;
- the required merge order;
- local checks and GitLab CI results;
- unresolved review findings;
- feature-flag or rollout state;
- blockers and assumptions;
- the exact manual actions still required;
- confirmation that local work was preserved or safely cleaned up.
If something is blocked, I want the blocker stated without pretending the rest of the project is complete. If a rollout requires a manual value or production decision, that should be visible before anyone presses merge.
I do not want to reconstruct the night from a Cursor transcript. The merge request, Linear status, CI results, and handover should explain the current state on their own.
What still belongs to me and the team
This workflow can compress implementation time, but it does not transfer ownership.
I still review the code. The team still decides whether the product behavior is right. A human still approves sensitive migrations, production exposure, commercial configuration, and changes with a meaningful operational blast radius.
Slack conversations still matter because software is built by people with context that does not fit neatly into a ticket. Linear still matters because that context needs a durable home. GitLab still matters because code, CI, review, and merge state need an auditable path.
Cursor connects those systems and does a large amount of the mechanical work, but it does not become the system of record for any of them.
The speed comes from preparation
At first, I thought the main advantage would be that the agent could keep writing code after I stopped working.
That helps, but it is not the biggest gain.
The bigger gain is forcing myself to define delivery properly. Overnight execution creates useful time between two human decision points. Repository reading, bounded implementation, tests, builds, independent checks, and evidence collection can continue while the judgment-heavy decisions remain visible for the next morning.
A ticket with a clear objective, bounded scope, known dependencies, acceptance criteria, validation plan, and stop conditions is easier for an AI agent to deliver. It is also easier for a human engineer to deliver, review, operate, and revisit six months later.
The playbook improves the work even when nothing runs overnight.
It turns hidden assumptions into visible gates. It separates implementation from deployment. It treats repository freshness as a requirement. It makes handover part of the job instead of an afterthought.
Overnight is a delivery window, not a quality standard
I will keep expanding this workflow at Tilt, but carefully.
More integrations are useful only when they improve evidence or reduce manual context transfer. More agents are useful only when their ownership does not overlap. More autonomy is useful only when stop conditions and review remain stronger than the pressure to ship.
The goal is not to wake up to the largest possible diff.
The goal is to wake up to work I can understand, verify, and confidently move forward.
That is how I think about overnight delivery at Tilt: prepare the context while I am present, let the machine handle bounded execution while I am away, and keep the final responsibility exactly where it belongs.