Agents

Write Loops, Not Prompts

Write Loops, Not Prompts

A prompt gets you an answer. A loop gets you a system that keeps answering after you close the laptop.

I spoke to someone recently who was running lots of agents and prompting each one individually, like an animal. Instead of writing loops.

That conversation stuck with me because I used to do the same thing. You sit in tmux, you type a prompt into one pane, you wait, you type into the next pane. You are the scheduler. You are the bottleneck. Your dopamine-seeking brain loves it, because every pane is a slot machine. But it does not scale past you.

There is a vibe going around Silicon Valley right now called nested loops: set up loops, and loops inside loops, so the machines maintain themselves and the AI does the boring work on a schedule. I decided to take it literally.

The first version of that idea was one typed paragraph, not a design doc: every morning check and update npm packages, every 30 minutes search for supply-chain attacks, every 30 minutes check for stale processes overloading the server and kill or fix them, every 15 minutes check tmux for dead panes and recreate them. No architecture review. Just a list of things I was tired of doing by hand.

Before any of it got built, I made the agents go research it properly instead of shipping on vibes. Search what these Silicon Valley operators are actually doing in 2026, search Hacker News, see what the YC crowd argues about when they argue about automating a machine end to end. Then come back with a naming scheme and a skill list, machine level first, repo level later. The point was not to copy anyone. The point was to not skip the step where you check if your idea is already a known bad idea.

Here is what runs on my machines today. Every morning, a loop checks and updates the npm packages we depend on. Every 30 minutes, a loop searches X and the security feeds for supply-chain attacks. Every 15 minutes, a loop hunts stale processes overloading the server and kills or fixes them, and another finds dead tmux panes and recreates them. There is a keyleak scan, a cost report, a dust sweep for orphaned files. None of it needs me anymore.

The dead-pane loop exists because of one specific afternoon. I asked an agent to redo a couple of tmux sessions, it killed more than I meant, and what came back was "you killed them all." Fair. Instead of writing a one-off recovery script and moving on, we built skill-tmux-revive as a real CLI, not a script you have to remember exists, that restores sessions and windows from a resurrect snapshot with the same session-name-equals-window-name discipline every time. The first real test recovered 21 tmux sessions on spark01 in one pass — alumia 01 through 12, codewith 01 through 04, every iapp-[name] window, restored exactly as they were. Now when a pane dies, nobody has to notice, let alone fix it by hand.

The dust sweep exists for the same reason, aimed at a slower failure. Long-running fleets accumulate orphaned files, stale build output, forgotten tmp directories. A loop runs a dust-sweep script on a schedule instead of me discovering a full disk mid-deploy. The same instinct produced a memory-guard and a memory-watch loop after one machine kept getting sluggish under agent load: heap caps, earlyoom, zswap, swappiness tuning, all wired into a schedule instead of a fix that only happens once things are already on fire.

Loops also catch the loops you did not mean to write. One of our MCP services, testers-mcp, hit a crash-restart loop north of 85,000 restarts before anyone noticed, because stdio was getting handed /dev/null for stdin and failing silently on every attempt. The fix was to make it fail loudly instead of quietly respawning, and to put it back on HTTP on its own port. Stdio bloats the load on the machine anyway. That incident is the whole argument for loops in one story: a system quietly restarting itself 85,000 times is not resilience, it is a bug wearing a uniform.

Two details matter more than the list.

First, ordering. The package-update loop runs after the supply-chain-watch loop, never before. Updating dependencies without checking for active attacks first is how you install the attack. We learned this the honest way: one of our own npm packages got hijacked at version 0.14.33 and we had to reclaim it at 0.14.34. The loop that protects you must gate the loop that changes you.

That rule is not a comment in a skill file, it is enforced by where the messages live. The security loops — supply-chain-watch, keyleak-scan, dependency-audit — post into a shared space that package-update agents are required to read before they touch a lockfile. A read before the write, enforced, not remembered.

The tmux side got the same split. One loops session, four windows: loops-security, loops-infra, loops-guard, loops-cost. Before any of them got a single automated task appended, we argued about the name — should we even call it a loop if it can run without one being scheduled. The answer was yes, because the loop is not the cron entry, the loop is the discipline: check, post, gate, repeat. The schedule is just how often it happens.

The first real batch shipped as nine skills in one pass: supply-chain-watch, keyleak-scan, repo-sync, package-update, mcp-fleet-health, machine-guard, loops-setup, loops-status, loops-add. Every one synced to spark01 the same day it landed on the primary machine, because a loop that exists on one box and not the others is not a policy, it is a coincidence. Deliberately, none of them got written as rigid scripts. The instruction was explicit: do not make these very deterministic, the agent has to do the actual work itself. The schedule is deterministic. The judgment inside it is not.

Second, judgment. The supply-chain loop is instructed to escalate only when two facts line up: we install the package, and it is being actively exploited. Everything else gets noted on schedule, not alarmed. The deterministic part of the loop decides when to run and what to record. The model decides what actually matters. That split — deterministic scheduling, agent judgment — is the whole design.

Every loop agent posts a digest to a shared space in our conversations CLI. One message, not spam. The agents self-organize in there: the supply-chain agent posts findings, the package-update agent reads them before touching anything. Silence is not a valid status. If a loop ran and posted nothing, that is a bug, not a quiet day.

None of it lives only in a crontab either. There is a loops project in the todos CLI so the work is plannable and reviewable, not just scheduled, and each skill writes its own memory into mementos after it runs. Machine automation that only exists as a scheduled job is a script pretending to be infrastructure. Give it a project, give it a memory, and it behaves like a colleague instead of a cron line nobody can explain a year later.

We hold the same standard on the human side. One agent got a standing duty to investigate the loops tmux session, the heartbeat checks, and every cron tier on its machine, then post exactly one concise digest to an ops group — what loops exist, when each last ran, anything stale. One message, not a stream of updates. The follow-up was not "how did it go," it was "did you actually post it, and if yes, give me the message id." Evidence or it did not happen applies to loops the same way it applies to code.

Loops are also management. When an agent drifts off task, I do not babysit it. I set a one-minute loop that keeps it on track: keep testing, keep fixing, never cheat, never take shortcuts. A recurring reminder is a north star. It outperforms me standing over the pane.

That habit turned into its own skill. Give it a piece of work, and it writes the tasks, then starts a one-minute loop whose entire prompt is your north star: stay on these tasks, finish them, test them, including in a browser if it is a frontend change, and do not call it done until it is fully tested and working. I do not repeat myself into the pane forty times a day. The loop repeats it for me, on schedule, without getting bored or generous with itself.

And yes, we verify the scheduler itself. There are smoke-test loops whose only job is to fire at an exact minute and reply with an exact canary string. If the canary is late, the automation layer is broken and everything above it is lying to you. Trust nothing you have not tested, including the thing that runs the tests.

Two of those canaries are literally named LOOP_SMOKE_0150 and LOOP_SMOKE_0151, scheduled to the minute, no job other than showing up on time and saying so. Someone once asked me, straight-faced, so what's a canary. The honest answer: it is the cheapest loop we run, built to fail loudly the moment the scheduler underneath it stops being honest. If your canary is late and nobody notices, you do not have a monitoring system, you have a decoration.

All of this is open source. OpenLoops ships as @hasna/loops: a local CLI and daemon for persistent loops and workflows — scheduled work that survives process restarts and records every run. SQLite state, run manifests, dry-run preflights. MCP mutations are off by default and require exact confirmation strings, because a loop framework that can be talked into destructive changes by any model in the room is not a framework, it is a liability.

The economics are the underrated part. I have spent 170+ billion tokens coding with agents. At that volume the question is not whether the model can do it, it is whether the task needs a frontier model at all. Most loops run on cheap workhorse models, and the expensive model gets pulled in only when the cheap one escalates. Smart LLM writes the plan, cheap LLM executes it. Your loop bill should look like a utility bill, not a fine-dining receipt.

People ask what actually changed after all this. Simple: the machines stopped depending on my attention. Packages update themselves, safely ordered behind the attack watch. Dead panes come back on their own, 21 sessions at a time if that is what it takes. Dust gets swept before a full disk becomes an incident. Costs get reported before they surprise me. I read digests over coffee instead of typing the same prompt forty times a day.

Build the builder, not the building. A prompt gets you an answer. A loop gets you a system that keeps answering after you close the laptop.

Stop prompting like an animal. Write loops.

← Back to the articles

Newsletter

What we shipped, what broke,
and what we learned