Agent architectures should expose control-state transitions, not framework objects
I think agent architectures should expose control-state transitions, not framework objects. A caller needs to know that work moved from planning to action, that an effect is awaiting confirmation, or that execution stopped under a policy—not that a framework emitted a Command, entered a StateGraph node, or returned a particular runner class. Framework objects explain an implementation. Control states explain what the system is allowed to do next.
This distinction matters because the agent loop is a state machine even when its library makes it look like a conversation. A model proposes an action; a tool boundary accepts, rejects, or defers it; an effect begins; evidence returns; the loop continues, recovers, waits, or terminates. Those transitions exist regardless of whether the implementation uses a graph, an async generator, callbacks, or an actor runtime. I want the architecture to name that stable machinery directly.
Framework vocabulary is attractive because it is already available in code. It is also the wrong public contract. A graph node may combine planning, permission resolution, and tool dispatch. A message object may represent streamed text, an approval request, or a confirmed side effect. Once those objects cross an API boundary, their accidental shape becomes an integration promise. Upgrading the framework then means either breaking consumers or preserving concepts the system no longer believes.
I would instead define a small transition vocabulary around operational consequences: accepted, reasoning, awaiting approval, effect pending, effect confirmed, effect indeterminate, recovering, paused, cancelled, failed, and completed. The exact set belongs to the product, not to a generic library. Each transition should carry a run identity, attempt identity, cause, time, authority context, and permitted successors. Payloads can vary while the control contract remains legible to clients, operators, and policy engines.
That contract separates progress from output. Streamed tokens are output; they do not prove that the agent advanced. A tool-call proposal is intent; it does not prove that an external action began. A transport closing is an observation; it does not prove completion. By emitting state transitions separately, I can tell a terminal interface to offer resume, an SDK to wait for approval, and an orchestrator to suppress a duplicate effect without asking each consumer to infer control from message fragments.
Recovery becomes clearer for the same reason. “The node ran twice” is a framework fact with uncertain consequences. “The effect remains pending after worker loss” is a control fact that demands reconciliation before replay. Explicit continuation and terminal reasons let tests assert that a context repair led back to reasoning, that a stop policy led to a blocked terminal state, or that cancellation settled outstanding work before completion was published. The architecture exposes the decisions that make retry safe.
The same model gives observability a useful grain. Traces can still record model calls, framework nodes, and tool latency, but the transition log preserves why control moved. It lets an operator distinguish model-selected completion from budget exhaustion, user interruption from policy intervention, and retry from fresh work. Those are not decorative labels—they determine whether the next response should be resume, repair, escalate, or stop.
There is one precise concession: framework objects are an adequate boundary for a low-risk, in-process prototype when one team owns both caller and runtime, no durable pause or recovery is promised, and no external side effect depends on the distinction. I would not build a translation layer merely to make such an experiment look architectural. The concession ends when state survives the process, another consumer integrates, or an action may need reconciliation.
I therefore treat framework adapters as private translators. They may turn nodes, messages, hooks, and runner results into the product’s control transitions, but those objects do not escape as the product model. This keeps the framework replaceable and makes the harder promise explicit: across interfaces and runtime revisions, the same transition carries the same authority, recovery meaning, and permitted next move. Production agents need that continuity more than they need a convenient object graph.