7.1 Autonomy vs. Reactivity in System Design
[DEMO: Two systems side by side. On the left, a classic API handler that only runs when you click a “Send Request” button. On the right, a scheduled task that wakes up every 10 seconds and updates a counter even if you never touch the UI. A toggle lets you add LLM-powered decision-making to either side so you can see that “intelligence” does not change who owns the trigger.] Most software you have written behaves like a reflex. Something else pokes it. It responds. Then it waits again. A web handler does nothing until an HTTP request arrives. A message consumer does nothing until a message appears in the queue. Even a multi-agent system from the previous chapters is usually throttled by a user: the outermost call is “respond to this prompt,” and everything inside is downstream of that single invocation. Autonomy does not depend on how “smart” the code is; it depends on where control flow begins. A cron job that runs at 3am without a human is autonomous in timing, while a button wired to an LLM remains reactive because a user still triggers it. The intuition that autonomy is about “wanting things” is not helpful here. We need a mechanical definition.Autonomy is about control flow origin. Reactive systems run only when external events call into them. Autonomous systems have internal triggers—schedules, stored goals, or event subscriptions—that start work without a user request. A cron job is autonomous in timing but not in decision-making; an intelligent autonomous system combines internal triggers with model-driven choices about what to do when triggered.
scheduledSummarySweep is not special. It could have been the body of an API handler. What changed is the origin of control flow. There is no button that says “run the sweep now.” The runtime’s scheduler decides when to call it.
From an architectural point of view, autonomy means shifting triggers from external callers to internal schedules or event sources.
This leads to a more precise way to classify systems:
- Purely reactive: all entry points are externally triggered (HTTP, RPC, user actions).
- Purely autonomous: all entry points are internally triggered (schedules, internal events).
- Hybrids: some paths are reactive, some are autonomous.
- If every path into the box originates in another box, you have a reactive system.
- If some paths originate on a clock, or on stored goals, or on internal event conditions, you have an autonomous system.
7.2 Maintaining Oversight Without Constant Supervision
[DEMO: A small autonomous queue processor that runs every few seconds. The UI never sends it commands, but you can flip to an “Activity Log” tab to see what it has done, a “Status” panel showing current state, and an “Alerts” area that lights up when something goes wrong. A separate toggle pretends you “went to sleep” by hiding the log for 30 seconds; when you return, you can reconstruct everything the system did.] Once you let your system wake itself up, you lose direct visibility into what happened while you were away. When a user presses a button and watches a spinner, you get oversight for free. If the response looks wrong, they complain. If the endpoint is down, they refresh and file a ticket. The supervision is continuous because the human is in the loop. Autonomous behavior cuts that feedback loop. The timer fires at 3am, work happens in the dark, and by the time you open your laptop the effects are already baked into your database, your email outbox, your logs, your bill. Instead of relying on users to notice failures, you need mechanisms that record what ran, whether it succeeded, and how it is behaving now. You cannot supervise it continuously. You have to instrument it instead.Oversight in autonomous systems comes from observability, not live supervision. Logging tells you what happened. Status fields expose what is happening now. Alerts notify you when your attention is actually needed. You design explicit observation and intervention points so the system can run unattended most of the time and still surface the right information when you return.
logActivity. That gives you a narrative you can replay later:
- When did it last run?
- Is it currently running, or stuck?
- Has it been failing repeatedly?
- Is the backlog growing?
checkHealth reactively (a monitoring service calls it) or autonomously (another schedule inside the same system). Either way, it flips the oversight burden: instead of you watching every execution, the system synthesizes its own health summary and only shouts when something looks wrong.
Three simple mechanisms—logs, status, alerts—change the feel of autonomy. The system may run while you sleep, but you wake up to:
- A scrollable history of what it did.
- A snapshot of how it is doing now.
- A clear signal if something went off the rails.
7.3 Preventing Runaway Behavior
[DEMO: An autonomous agent that tries to “improve” a text document with an LLM. Without any limits, it keeps re-opening the document every few seconds and making micro-edits forever. A second version has explicit time, iteration, cost, and scope limits. The UI lets you toggle limits on and off to see how quickly the unconstrained version spirals in API calls and junk edits, while the constrained one stops and records why.] If you give a system permission to start work on its own, you also give it permission to make the same mistake again and again. An API handler that misbehaves is bounded by user patience. If the response looks obviously wrong, the user stops clicking. If it times out, clients back off. The reflex only fires when someone presses the nerve. Autonomous paths have no such natural brake. A mis-specified goal can persist in storage and be re-pursued every half hour. A model that never quite decides a task is finished can keep asking for “one more refinement.” A loop that calls an external API based on model output can rack up thousands of dollars in charges before anyone notices. Goals are serialized task descriptions stored in your database or queue—for example, records that contain an objective, parameters, and status fields that the autonomous loop reads and updates across runs. That makes them powerful and dangerous: you have introduced objectives that persist across executions, but without a human at the steering wheel every time they are acted upon. Runaway behavior is addressed through architectural limits on execution time, retries, resource consumption, and allowed actions.Runaway behavior is prevented by explicit boundaries. You design limits on time (how long one execution can run), iterations (how many steps it can take), cost (how many resources it can consume), and scope (what kinds of actions it is allowed to perform). A kill switch gives you a global off button. Goals themselves are just persisted data; the safe behavior comes from the constraints around how and when they are pursued.
callModel. That gives you a single choke point where you enforce per-day or per-goal limits without trusting the model to “use resources wisely.”
Scope boundaries are about what the system may do, not how much.
- Time limits bound how long a run can run.
- Iteration limits bound how many steps a run can take.
- Cost limits bound resource consumption over time.
- Scope limits bound what actions are even possible.
- A kill switch lets you disable everything with a single write.
7.4 Failure Modes in Autonomous Systems
[DEMO: An autonomous worker that processes items from a queue every few seconds. A “Crash Now” button simulates a runtime failure in the middle of processing. The first version loses track of which items were in progress and double-processes some on the next run. The second version uses simple checkpointing and locking, so after a crash it resumes cleanly, skips already-done work, and never overlaps runs.] Autonomous systems fail in ways that reactive systems rarely do. If an HTTP handler throws an exception, the user sees an error and retries. If your service is down, clients back off or display a “we’re having issues” banner. The failure is bounded by the request-response cycle. A scheduled method that crashes has no immediate witness. If a timer fires every five minutes and the work now takes ten, two executions can overlap. If your system is down during a scheduled window, the timer might fire into the void. When it comes back, it might not realize it missed work. An error in a single item can poison every run if you do not isolate it. Because autonomous code runs without supervision, you should expect crashes, missed timers, overlapping runs, and partial writes to occur over time and design explicit recovery paths for each.Design for failure explicitly. Autonomous methods should be restartable, interruptible, and non-overlapping. You checkpoint state so crashes leave you in a recoverable position. You use simple locks to prevent concurrent runs from stepping on each other. When reactive and autonomous paths coexist, you decide which one yields to the other instead of letting them race.
doWork, the task is left in a known “in-progress” state with a checkpoint. On the next run, you can decide whether to retry, mark it failed, or resume from checkpoint. The important part is that you do not silently lose or duplicate work.
To handle overlapping executions, you need a simple lock. Schedules do not wait for each other.
start..end window. You are no longer counting timers; you are counting data ranges. The autonomy is expressed in terms of “have we processed everything up to time T?” rather than “did this particular cron tick execute?”
Autonomous and reactive paths can also collide. For example, a queue item might be processable either by a background sweep or by a direct user request. You need to decide how they interact.
One design is to let reactive work preempt autonomous work:
Key Takeaways
Autonomy is not a mystical property of models. It is a choice about where your system’s control flow starts and what keeps it going. When code only ever runs in response to external calls, you have a reactive system. When some of your entry points are driven by internal schedules, stored goals, or event subscriptions, you have an autonomous system. The LLM inside that system can make more complex decisions about what to do, but it does not change the fundamental question of who decided that now was the time to act. Because autonomous execution happens without a human’s finger on the button, you must give the system new affordances:- Observability, so you can reconstruct what happened and see what is happening now.
- Explicit boundaries on time, iterations, cost, and scope, so “run without being asked” does not become “runaway.”
- Kill switches and locks, so you can stop behavior quickly and prevent overlapping runs.
- Checkpointing and catch-up logic, so crashes, delays, and missed windows leave you in a recoverable state instead of a corrupted one.