entropik.
§ 02.09 · architecture · patterns · autonomy

Autonomous Patterns

Five patterns I now trust when I build systems that act without asking permission. Each one is a rule I broke first, regretted, and eventually worked out the shape of.

Acting without permission, safely

The move from "system that responds to the user" to "system that acts on the user's behalf" sounds incremental. It isn't. The moment a platform can initiate — send an email, draft a document, flag a risk, adjust a route — the bar for safety and transparency steps up by a full level. And the old habits from request-response software become actively dangerous.

What follows are five patterns I now reach for when I'm building anything that's going to act without being explicitly asked. None of them are original — most of them are restatements of things Erlang programmers, SRE practitioners, and the OpenClaw team have been saying for years. What's mine is the sequence in which I learned to trust each one.

Pattern 1 — Sessions as processes

Each interaction spawns an isolated session with its own context, permissions, and lifecycle. Not a thread in a monolith — more like a small operating-system process. It has a beginning, a purpose, a resource budget, and a termination point.

I didn't start this way. My early platforms had a conversational layer where every user's state sort of lived together, with tenant IDs as the only real boundary. It mostly worked. The part where it didn't mostly-work was when something went wrong — a context leak, a permission check that didn't re-evaluate, a token budget that blew past its limit — and the blast radius was the entire conversational surface instead of a single session.

Now I treat each session as having hard edges. Permissions are evaluated once at spawn, not continuously. Resource limits (token budget, wall-clock timeout, memory) are enforced per session. No mutable state crosses session boundaries. A session crash does not affect its neighbours. Interactive sessions are short-lived. Background sessions, kicked off by a heartbeat, can be longer. Triggered sessions, started by an external event, come and go quickly.

The property this buys is that when something goes wrong, it goes wrong small. The property I didn't fully appreciate until I'd had it for a while is that session-shaped execution is also the right primitive for autonomy — a session is a unit of "the system did this thing" that can be logged, reviewed, cancelled, and audited as a single object.

Pattern 2 — Heartbeat before action

Heartbeats are proactive monitoring. Every so often, the system scans for conditions that might need attention. If a condition is met, the heartbeat spawns a session to handle it. If no condition is met, the heartbeat exits.

The separation I've learned to hold firmly is this: heartbeats detect, skills act. The heartbeat itself is read-only. It doesn't mutate state. It doesn't write events. It observes, decides whether action is warranted, and if so, dispatches a session that does the acting. Putting action inside the heartbeat is one of those shortcuts that feels efficient for about two weeks before it produces the exact class of bug you most dread — an automated side effect that fires on a schedule you can't quite remember configuring.

The trigger types I use in practice are temporal ("it's been X days since we checked"), threshold ("a metric crossed a boundary"), external event ("something happened outside the system"), and pattern ("this behaviour has recurred N times"). All four reduce to the same shape: cheap condition detection in the heartbeat, dispatched skill if the condition is met.

The other design rule that earns its keep: heartbeats are cheap. If condition detection takes more than a second, the heartbeat is doing too much. Push the work into the dispatched session, where the budget and the observability belong.

Pattern 3 — Gateway and Runtime kept separate

I wrote the shape of this in the Three-Layer Stack — the Gateway holds state, does routing, handles permissions and context assembly; the Runtime is stateless and makes model calls. I mention it here because it's what makes the other four autonomous patterns tractable.

A heartbeat spawns a session in the Gateway. The Gateway assembles context and dispatches to the Runtime. The Runtime makes the model call, returns structured output, exits. The Gateway takes that output and writes the event. Permissions are checked once, in the Gateway, at session spawn. The Runtime never sees session identity or user identity — just a pre-assembled context, a prompt, and a schema.

The property this gives you is that autonomy is recoverable. A Runtime crash is a session-level failure, not a platform-level one. A context assembly bug lives in the Gateway and can be patched without touching the model layer. A model swap happens at the Runtime boundary and doesn't cascade.

Pattern 4 — Configuration by observation

The platforms I've built all have a "soul" — a small pile of markdown files that describe the personality of the system, the conventions of the domain, the preferences of individual users, the quirks of specific agents. Early versions of these were editable-but-static: you wrote them once, and they stayed that way until someone remembered to update them.

The pattern I trust now is that these files evolve through observation. When a user systematically prefers three sources over one, the agents file learns that. When a user batch-approves on Monday mornings, the user file notes that. When a user corrects "plaintiff" to "claimant" four times in a week, the soul file updates. The observations flow from events and traces automatically. The user can still edit the files directly — they're markdown — but most of the evolution happens without anyone sitting down to configure anything.

The reason this matters for autonomy is that an autonomous system needs to know what "appropriate" looks like for the person it's acting on behalf of, and no amount of upfront settings captures that. Settings pages are where the product pretends personalisation happened. Observation is where personalisation actually happens.

Pattern 5 — Connectors as dumb pipes

Connectors are the boundary interfaces — email inbound, webhook inbound, form submission, file upload, message queue consumer. Their job is exactly three things: transport, normalise, validate. Move the data in, turn it into a platform event, check the signature. That's it.

The rule I hold firmly is that a connector should be writable in an afternoon. If it takes longer, business logic has crept into the transport layer. A connector that decides what to do with an incoming email is in the wrong layer. A connector that writes to the database is in the wrong layer. A connector that retries or queues is in the wrong layer. All of those concerns belong one step up, in the services that subscribe to the events the connector publishes.

This one feels like over-engineering to anyone who hasn't yet had their integration layer become the most complex part of their platform. I've had that experience more than once, and the connectors-as-dumb-pipes rule is what keeps me from having it again.

How the five fit together

Sessions bound execution. Heartbeats decide when a session is warranted. Gateway-Runtime separation lets the session run safely. Configuration-by-observation lets the session behave appropriately for the user. Connectors bring external events in without spreading business logic across the transport surface.

Together they describe a platform that can act on someone's behalf without being opaque, fragile, or quietly wrong. They don't describe the fun part of autonomy — the model picking up a subtle pattern, the heartbeat catching a problem the user hadn't noticed yet. They describe the boring structural conditions under which the fun part is safe.

Autonomy is a structural property of the system, not a feature of the model. Most of the failures I've seen in autonomous platforms — including my own — were structural. Getting the structure right is most of the work.

// continue the thought

Want to think through how this lands in your project? Tell kr8 what you’re working with.

0 / 4000 chars
kr8 · next

// Keep reading the playbook?

TOPOLOGY