Skip to content
AI 知识地图 0.18 · 2026-07-30
关于与纠错文字目录 / Search
Understanding the principles

Agent Frameworks: Abstract the Run Loop, but Don't Outsource Correctness and Control

From model adaptation, tool registration, state graphs, persistence, and tracing, to abstraction leaks, version migration, and escape hatches, determine when a framework is worth adopting.

Core idea Frameworks reduce boilerplate for model calls, tool adaptation, state persistence, and observability; they do not define task success, permissions, idempotency, or recovery for you. The thicker the framework, the more you must keep domain state, acceptance criteria, and portable tests outside the framework.
After reading, you should be able to:Draw the framework's layers and responsibility boundaries; derive real requirements from a minimal loop; design portable state and replay tests; choose the abstraction layer based on quality, cost, and failure recovery.
  1. Handwrite a minimal loop to expose real requirements
  2. List necessary capabilities and failure modes
  3. Choose the thinnest candidate framework
  4. Use a domain schema to isolate state
  5. Implement retry, cancellation, and permissions
  6. Preserve original requests and escape hatches
  7. Inject faults and compare TCO
  8. Use contract traces for canary upgrades

1First understand the minimal loop, then choose a framework.Positioning

The core runtime of an Agent actually requires only a tiny amount of code—so little that it can be written out in full on a whiteboard. Its minimal loop contains only six actions: assemble context, call the model, parse tool intent, execute the tool, record the observation, and decide whether to finish or continue.

Assembling context means combining the system prompt, the current conversation history, the available tool descriptions, and the information retrieved from external sources into one complete model input. Calling the model means handing this input to the language model and obtaining one output. Parsing tool intent means recognizing whether the model's output expresses the intent "I want to call a certain tool", as well as the tool name and arguments to call. Executing the tool means actually passing the parsed arguments to the corresponding function or external interface. Recording the observation means appending the result returned by the tool execution to the context as the input for the next round of the model. Deciding whether to finish or continue means determining whether this round's result already satisfies the user's goal and can end, or whether it needs to run another round with the new observation.

These six actions connect end to end and form the Agent's minimal run loop. It does not need any framework; it can be implemented with a few dozen lines of sequential code. Understanding this loop is the prerequisite for choosing an Agent framework, because all frameworks are essentially doing the same thing: productizing these six steps.

In the process of productization, frameworks add extra capabilities on top of the minimal loop. They may add routing, distributing requests to different tools or different sub-Agents based on intent; they may add checkpoints, saving state at key steps so that recovery can start from an intermediate point after a failure; they may add human interruption, pausing before sensitive operations to wait for human confirmation; and they may add tracing, recording the input and output of each loop round for observability and debugging. These capabilities have value, but they are enhancements to those six steps, not replacements for the six steps.

Therefore, an Agent framework can be defined as a type of development component that encapsulates model calling, tool loops, state saving, and tracing into a runtime. Its input has two parts: one is the minimal Agent loop that the team already understands, and the other is the specific engineering pain point that the team has clearly encountered. Its output is less boilerplate code, along with optional routing, checkpoints, and observability capabilities.

The key here lies in the causal direction: frameworks reduce implementation friction, not define business success. A team that repeats thousands of lines when writing tool loops can immediately save boilerplate code by introducing a framework; a team that cannot even clearly explain "why it needs an Agent or what problem it should solve" will not have its goal become clearer by introducing a framework. When the requirements themselves are vague, a heavy framework will not provide answers; it will only encapsulate an originally visible loop into an invisible call stack, making it harder for the team to understand what the system is actually doing. Therefore, the correct order is always to first write down and understand that minimal loop, and then determine which part the framework can take on for you.

2Frameworks typically span five different layersLayering

“Supporting AI Agent” is an over-compressed phrase; it may only mean that the framework can call a certain model interface, or it may mean that the framework comes with a full persistence workflow. To compare two frameworks that both claim to “support AI Agent,” you need to break them down. Most frameworks’ capabilities span five different layers, each with its own typical responsibilities, and share a common boundary: the framework should not make business decisions on your behalf.

The first layer is model adaptation. This layer is responsible for constructing requests, processing streaming output, and converting tool descriptions into a tool schema that the model can understand. The question it answers is: can this model handle the current task? At this layer the framework encapsulates the details of “calling the model once,” but it cannot judge for the team whether the model is suitable for the task; that judgment always belongs to the user.

The second layer is the runtime loop. It is responsible for calling the model, executing tools, and handling termination conditions. This is the most central part of the minimal loop. At this layer the framework can provide the ability to start the loop and stop it reliably, but it cannot decide for the business “what counts as done” or what risk budget is acceptable. Completion conditions and risk budgets must be defined by the business itself.

The third layer is state and graph. It is responsible for expressing and persisting nodes, branches, and checkpoints. The framework can use state graphs to make multi-step flows explicit, saving intermediate state at checkpoints, but it cannot define the semantics of domain objects for the business. A state graph is only a carrier; semantics such as “what state an order is in, and how states validly transition between one another” in the domain still belong to the business.

The fourth layer is the extension ecosystem. It includes connectors, memory, and plugins. This layer determines how many external systems the framework can integrate with and how many capabilities it can provide out of the box. The framework can provide ecosystem breadth, but it cannot assume supply chain trust on behalf of the business. Introducing a plugin is equivalent to introducing a piece of third-party code; the right to decide whether to trust it rests with the team.

The fifth layer is observability and deployment. It includes trace, queues, and evaluation. This layer makes the system visible, orchestratable, and measurable in production. The framework can produce trace and metrics, but it cannot judge for the business which metrics represent real value. Metrics are a means of observation; value is the business goal.

After breaking the framework into these five layers, the vague statement “supports AI Agent” can be replaced with five concrete questions: which model adaptation methods it supports, how the runtime loop configures termination conditions, how state graphs are expressed and persisted, how many trustworthy connectors exist in the ecosystem, and what metrics observability and deployment can provide. By comparing layer by layer, you can avoid treating completely different levels of capability as the same thing.

Finally, remember an asymmetric relationship: covering more layers does not necessarily mean being more production-ready. A framework that covers all five layers may only have shallow implementations in each layer; a framework that solidly covers only the runtime loop and observability may actually be more suitable for real online systems. No matter how many layers the framework covers, the business still must own the goals, permissions, domain semantics, and value metrics — these four things cannot be replaced by any framework layer.

LayerTypical responsibilitiesShould not decide for business
Model adaptationRequest, streaming, tool schemaWhether the model is suitable for the task
Runtime loopCalling, tools, terminationCompletion conditions and risk budget
State/graphNodes, branches, checkpointsDomain object semantics
Extension ecosystemConnectors, memory, pluginsSupply chain trust
Observability/deploymentTrace, queues, evaluationWhether metrics represent real value

3Complete example: from a handwritten customer service loop to a persistent state graphCase walkthrough

The most reliable basis for judging whether "adding a framework" is worthwhile is not the framework's marketing, but whether it can make a loop originally supported by handwritten if/else clearer. A complete evolution of a customer service system is used for illustration.

The initial customer service system had only one main path: classify, look up the order, answer. At this stage, using a sequential if/else handwritten loop is the most transparent—the code is short, the control flow is visible at a glance, and any framework layer only adds comprehension cost without bringing benefit. The real turning point occurs after requirements begin to accumulate.

The first new requirement is that refunds require human approval. This means an additional branch appears in the loop that the model cannot complete on its own: after proposing the refund action, it must stop and wait for human confirmation instead of continuing forward. The second requirement is that waiting for callbacks can span days. A refund callback may not return until a day later; the process cannot remain blocked waiting, nor can it lose the fact that it is "waiting for callback" just because the process restarts. The third requirement is recovery from checkpoints after failure. If an error occurs midway, the system cannot rerun the entire conversation from the beginning; it must continue from before the step where the error occurred. The fourth requirement is that different tenants have different tool permissions. In the same tool loop, the set of tools that different tenants can call differs, and permission checks cannot be hard-coded globally.

After these four things are stacked together, purely sequential if/else starts to fail: the control flow is no longer a straight line, but has persistent waiting, human breakpoints, recovery points, and paths that branch by tenant. At this point, it becomes valuable to make the implicit state in the handwritten loop explicit. Model the state as a set of fields such as {case_id, intent, order, proposed_action, approval, result}: case_id identifies the case, intent records the classification result, order saves the order that was looked up, proposed_action saves the model's proposed next action, approval records the human approval status, and result saves the final result. Then represent these states with explicit nodes, and use conditional edges to represent valid transitions between states, so that a persistent wait like "waiting for callback" across days becomes a clear node on the graph, rather than an implicit state hidden inside some code's await. At this step, the framework begins to provide substantive value.

Migration itself must proceed in a restrained sequence, rather than introducing all of the framework's features in one step. First, use two real cases to record the event traces produced by the handwritten loop exactly as they are—inputs, tool calls, and results at each step. Second, reproduce the same state transition path in the framework, without adding any framework-specific extra features at first; the goal is to make the framework version's behavior exactly identical to the handwritten version. Third, perform fault injection: kill the process midway, then verify that the system can recover from the state before approval and will not re-initiate the refund. This verification directly determines whether the migration is successful, because repeated refunds are an unacceptable business incident. Fourth, compare the original request, token consumption, tool parameters, and final business state side by side to confirm that the framework version has not silently changed behavior. Fifth, retain a fallback path that bypasses the framework and directly calls tools and models, so that business can continue when the framework has problems.

There is only one acceptance criterion: the framework version must bring real improvement in recovery or debugging. If the framework version merely draws the same logic as a prettier diagram but cannot improve failure recovery or make troubleshooting easier, then it has not proven the value of the migration. A pretty diagram has never been the acceptance criterion; improvement in recovery and diagnosis is.

4State belongs to the domain; messages are just a viewState design

Putting all business data into the framework's message list is the easiest way to completely lock in migration. The message list stores natural-language text of the conversation history; its purpose is to generate the next reply, not to carry queryable, migratable, constrainable business facts. Once order IDs, approval states, evidence, and idempotency keys are hidden only in the natural-language history, they become strings scattered among sentences, unable to be indexed by a database, unable to be checked against constraints, and unable to be moved out as a block when switching frameworks.

The correct direction is the opposite: domain state has its own versioned schema, and messages are just a view derived from that state. Data such as order IDs, approval states, evidence, and idempotency keys first have an authoritative, structured place in the domain schema, and are then projected into messages when model input is needed. This way, the message list can be rebuilt from state at any time, and deleting the entire history loses no business facts.

Versioning is the key mechanism here. The domain schema evolves with the business, and old persisted data uses an old version. The framework's checkpoint does not directly save all business objects; it saves the state version and event references. During recovery, the system performs the corresponding migration based on the version number, converting old-version data to the new version, and then continues running. This mechanism makes “changing the model, changing the message format, and changing the framework” all local replacements: the model and message format are just ways of projecting state to text, the framework is just a runtime shell, and the business database and its domain objects remain unchanged and do not need to be rewritten.

Concurrency is another scenario that must be handled explicitly. When multiple branches update state simultaneously, if a single global mutable dictionary holds the state, two users' operations will intermingle—fields written by one person get overwritten by another person's update. Therefore, updates from concurrent branches require explicit merge rules: each branch produces its own update, which is then merged into the authoritative state according to predefined rules, rather than each branch directly rewriting the same shared object.

Taken together, the inputs to domain state design are business entities such as orders, approvals, evidence, idempotency keys, and version numbers, and the output is a schema independent of framework messages along with the corresponding migration records. Messages are derived from state, checkpoints store versions and event references, and concurrent branches are merged according to explicit rules. The framework's message list is always just a view; it cannot become the single source of truth for the business, nor a dictionary shared across tenants.

5Original diagram: the framework provides a runtime between domain control and external systemsvisualization

This layered diagram answers a fundamental question: in the architecture of an AI Agent system, which layer should be replaceable at any time, and which layer must be owned by the application itself. The answer can be drawn with a clear boundary.

The top layer of the diagram is the domain layer owned by the application itself. This layer defines goals, state, permissions, and acceptance. Goals are the business outcomes the system is to achieve, state is versioned domain data, permissions are constraints on what each tenant can do, and acceptance is the criterion for determining whether a run is successful. These four things determine the correctness and control of the system, so they must remain in the application's domain layer and cannot be outsourced to any framework.

The middle of the diagram is the framework runtime. It provides the run loop, graph, checkpoints, and tracing. The run loop is responsible for repeatedly executing model calls and tool calls, the graph is responsible for expressing states and transitions between states, checkpoints are responsible for saving recoverable state at critical points, and tracing is responsible for making each run visible. These are purely mechanical capabilities, replaceable, and are exactly where the value of the framework lies.

The bottom of the diagram is the adapter layer, which connects external systems such as models, tools, storage, and queues to the runtime. Model adapters handle requests and streaming output, tool adapters convert tool descriptions into model-understandable schemas and execute calls, storage adapters carry domain state and checkpoints, and queue adapters carry asynchronous tasks across processes.

This boundary means: the application owns goals, permissions, acceptance, and schema; the framework only carries the loop, graph, checkpoints, and tracing. The framework is a replaceable runtime in the overall architecture, not the source of correctness.

Here we need to precisely understand what "replaceable" means. Saying the framework is replaceable means its interfaces and test isolation are good enough that you can swap it out when needed without affecting the domain layer; it does not mean any two frameworks can be interchanged losslessly. One framework's loop scheduling policy, checkpoint format, and tracing data structures may all differ from another's, so replacement still requires migration work. Replaceable describes clear boundaries, not a promise of zero cost.

Application owns: goals · domain state · permissions · acceptance · risk budgetOwn schema and tests, not bound to framework message typesFramework runtimeModel/tool loopState graph/routingCheckpoint/interruptionTracing/evaluation hooksModel providerTools/APIsStorage/queueObservability backend

Scroll horizontally to view the full diagram on small screens.

Figure 1 The framework is a replaceable runtime; correctness and control must remain in the application-owned domain layer.

6Retry, cancellation, and termination semantics must be visibleReliability

The "automatic retry" provided by the framework is an easily overrated convenience, because it may quietly repeat side effects where users cannot see. To use retries correctly, you must distinguish between call types.

Retry policies for model calls and read-only tools can be relatively lenient: if a model request fails due to network jitter, retrying once is safe because the model call itself does not change the external world. But write tools are completely different. If a refund request times out, the refund may actually have succeeded, but the response just did not come back; in this case, automatic retry will initiate a second refund, causing a duplicate charge. Therefore, write operations must carry an idempotency key—a key that uniquely identifies this operation, allowing the external system to recognize and deduplicate repeated requests; at the same time, it should be combined with result querying, so that after a timeout you first ask "Did that previous attempt actually succeed?" before deciding whether to resend.

Cancellation semantics must be propagated completely. When the user clicks cancel, this signal cannot stop in one place; it must travel all the way down: to the ongoing model stream, the executing tools, and the subtasks derived from them. If cancellation takes effect only at the entry point, while already-running tools and subtasks continue to execute, the user thinks they have cancelled, but side effects are still occurring.

Termination conditions also need to be explicitly defined. When the step limit is reached, the budget is exhausted, there has been no progress for several consecutive rounds, or completion conditions have already been met, the run must terminate explicitly. The most dangerous is the default unlimited retry: it will replay a permanent error over and over, continuously consuming tokens and budget while creating re-entry—the same operation is triggered repeatedly. Costs balloon in silence, while the log only leaves a vague "failure".

Therefore, the framework should expose complete information for each physical attempt: backoff strategy, timeout, and final status, rather than compressing a multi-attempt process into a single "failure." Only by seeing each attempt can operators judge whether this is a normal retry of a transient error or a permanent error being repeatedly amplified.

To summarize the semantics of reliability: its inputs are call type, error, idempotency key, budget, and cancellation signal; its outputs are the types of actions: retry, query, cancel, or terminate. Model and read-only tools can do bounded retries based on errors; write tools should first query results and deduplicate; cancellation must propagate to subtasks. The framework's "automatic retry" is essentially just "scheduling once again"; it does not judge whether the error is permanent, nor does it eliminate side effects for you—it may amplify permanent errors and side effects.

7Abstraction leaks are not an exception but a debugging entry point.Escape hatch

A unified model interface is one of the most appealing selling points of a framework: write code once, swap in any model. But it also often masks new capabilities or error details. The reason is that different providers are not completely homogeneous in tool calling, caching, reasoning, streaming events, and error codes—they all superficially call it “calling a model,” but the underlying behaviors and return structures differ.

In the face of such differences, frameworks commonly take one of two approaches, each with its own costs. One is a lowest-common-denominator adapter: it exposes only the smallest set of capabilities all providers support and discards everything else. The cost is lost capability—a particular model's cache hints, inference parameters, or streaming events are erased at the adapter layer. The other is a thick wrapper: it translates provider differences into a unified internal representation. This looks more complete, but the translation process may rewrite parameters or messages, making what the model actually receives differ from what the business code thinks it is, leading to mismatches during debugging.

Therefore, frameworks must leave an abstraction escape hatch. Specifically, they must preserve raw requests and responses, preserve provider extension fields, preserve a path that bypasses the adapter and calls the model directly, and make the adapter pluggable and replaceable. Business code, in turn, has the opposite constraint: it must not depend on non-exported internal objects within the framework. Once business code clings to the framework's private internal implementation, the framework's adapter cannot be replaced, and the escape hatch becomes useless.

The point beginners most easily confuse is this: a unified interface does not mean semantic equivalence. That two models can be called through the same unified interface does not mean they produce semantically equivalent results for the same instruction. After changing an adapter or changing a model, previously passed task evaluations and safety evaluations may no longer hold. Therefore, after changing an adapter, you must rerun task evaluations, safety evaluations, and cost evaluations; you cannot assume behavior is unchanged just because “the interface has not changed.”

The inputs to the abstraction escape hatch are the provider's raw requests and responses, extension fields, and the framework's adaptation results; the outputs are diagnosable differences and direct call paths. The problem it solves is precisely the problem of a unified interface masking new capabilities and error details. Here, abstraction leaks are not defects to be fixed but entry points for debugging: when results do not meet expectations, it is these leaked raw details that tell you at which layer the difference occurs.

8Plugins and Connectors Expand the Supply Chain and Permission SurfaceSecurity

Installing a community toolkit may seem like just adding a convenience function, but it actually means executing third-party code in your process. The connector's code runs in the same process as your business code; it may read environment variables, read files, and access the network; its registered callbacks may modify system state; and its bundled default prompts may also inject hidden rules that quietly change the model's behavior. These actions are not all laid out for you at install time; they may only be exposed after running.

Plugin governance should therefore start with locking down the supply chain. Lock versions and hashes to ensure that the package installed today is the same one that was reviewed yesterday; review the maintainer's background, license, and the other packages it depends on, because risk propagates along the dependency chain. A successful install or a popular source cannot prove that the supply chain is trustworthy—popularity only means that many people use it, not that no one has checked it.

The runtime environment should use a least-privilege sandbox. A connector should get only the tiny set of permissions necessary to complete its task, not the permissions of the entire process. Tools should be allowed one by one rather than all permitted by default. Development and production credentials must be separated; a tool that should only run in tests must never be able to read production secrets.

Upgrading the framework itself is also an opportunity for another review. A new version of the framework may change implicit defaults or may include migration scripts. When implicit defaults change, behavior in places without explicit configuration will quietly change; migration scripts are themselves code that will be executed. So when upgrading, re-audit both of these rather than only looking at the changelog headings.

Let's organize the mechanism of plugin governance: its inputs are connector code, dependencies, permissions, prompts, and production credentials; its outputs are one of four dispositions: allow, sandbox restrictions, version lock, or deny. Community packages may read environment, network, and files, so they must be allowed tool by tool, and defaults must be re-audited on every upgrade. The core conclusion here is: supply chain trustworthiness must be established through review, not inferred from successful installation or source popularity.

9Selection experiments should test failures, not just development speed.Decision

When evaluating frameworks, the most common bias is to test only development speed: one framework can build a prototype in two days, another takes five, so the two-day one looks like the winner. But this comparison misses costs over a longer horizon. The real question is: will the two days of development time the framework saves you be fully offset or even become a net loss due to the extra costs it later incurs from monthly upgrades and troubleshooting?

Selection experiments should use a time-box approach, running trials on two representative tasks within a limited time and then comparing a set of metrics, not just “how long to write the first version.” These metrics include: time to first implementation, task success rate, P95 latency, token and tool costs, trace coverage, recovery after process crashes, handling of human interruptions, and cost of upgrade regressions. Time to first implementation only answers “is it quick to get started,” while the remaining metrics answer “can it run stably, can problems be diagnosed clearly when they occur, and will upgrades break things.”

Write total cost of ownership as a sum. Over a selected time range, total cost of ownership TCO equals the sum of five items: initial development cost Cdevelop, daily operating cost Crun, fault diagnosis cost Cdiagnose, upgrade and migration cost Cmigrate, and vendor lock-in cost Clockin. Each item corresponds to a clear source—writing the first version, running it every day, investigating when something goes wrong, version upgrades and migration, and the cost of being locked in if you want to leave in the future. The value of this formula is that it forces the team to put the “saved development time” back into the full cost picture instead of looking at only one item.

Here is a decisive criterion: if, when a failure occurs, you cannot locate the original model request, tool parameters, and state version, then even the richest set of framework components is a net liability. The more components there are, the more complex the runtime and the more failure points there are; and once tracing cannot keep up, these components only turn troubleshooting into guessing within a large opaque call stack. A system that can be developed quickly but cannot be diagnosed has far higher long-term cost than a system that develops slightly more slowly but is transparent and inspectable.

Therefore, the principle for selection is: choose the thinnest framework layer that can meet the current required capabilities, while setting an upper bound on exit costs. The thinnest layer means not introducing capabilities you don't need; the exit-cost upper bound means considering, at decision time, how much it will cost to leave in the future. The inputs to a selection experiment are representative tasks, candidate frameworks, and these five categories of costs; the output is quality, latency, recovery ability, and TCO under the same budget constraint. Time-box experiments should actively inject crashes and human interruptions, and require the ability to locate the original request and state. Finally, we should be clear-headed about the TCO formula: it is a decision framework, not a precise prediction, and the weights and time range of each item must be set by the team based on their own actual estimates.

TCO=Cdevelop+Crun+Cdiagnose+Cmigrate+Clockin

10Migration relies on contract tests, not on re-watching the DemoEvolution

After a major version upgrade, the behaviors most likely to change silently are often not on the conspicuous feature list. An upgrade may change the parameter format of tool calls, change defaults for termination conditions, change retry strategies, or change implicit default injections into prompts. These changes will not crash the system immediately, but they will cause behavior to drift subtly. Relying on re-watching a Demo cannot reveal them—the Demo only shows the main path; failure boundaries and permission boundaries are not included.

A reliable migration approach is to build on contract tests as the foundation. First freeze a set of key traces, that is, solidify important cases that occurred during real runs. Each trace should record: input state, expected tool call sequence, permission denial situations, manual pauses, timeouts, recovery, and the final business state. These seven items together form a contract describing "under these inputs, the system should perform this series of actions and reach this result."

When upgrading, replay these traces with the new framework, comparing events and side effects rather than whether the natural language is verbatim identical. The wording of model outputs can differ—as long as it calls the correct tools, maintains the same permission boundaries, produces the same side effects, and reaches the same business state, wording changes are acceptable. Conversely, if a permission denial is ignored, a state skips a step, or a side effect occurs one extra or one fewer time, then even if the natural language looks exactly the same, the migration has failed.

The order of migration execution also matters. The state schema must be migrated first: upgrade the domain data to the new version and verify it passes before letting the new instance take over. During operation, keep the old and new instances separate—do not run new code while reading old data, and do not let old and new instances share the same state store, to avoid contaminating each other. Then roll out gradually: switch a small amount of traffic to the new framework, observe changes in cost, latency, and failure distribution, and expand gradually after confirming there is no deterioration.

Summarize the inputs and outputs of migration contract testing: inputs are the frozen state, tool sequences, permission denials, manual pauses, timeouts, recovery, and business results; outputs are the differences between the old and new versions in events and side effects. The order is to migrate the schema first, then run old and new instances separately and observe in a gradual rollout. The bottom line for acceptance is: natural language does not have to be verbatim identical, but permission, state, and side-effect boundaries must not change silently.

11Building vs. adopting is not an either-or choice, but buying abstractions layer by layerArchitecture decision

"Build or adopt" is often treated as an either-or choice, but it should actually be a process of buying abstractions layer by layer. A team needs tracing capability but not a state graph, and this does not mean it must adopt a complete Agent Framework — tracing and state graph are two different things and can be bought separately.

Making decisions layer by layer is the core method. Capabilities such as model SDK, tool schema, tracing, queue, and persistent graph can come from different components respectively, and do not need to be bundled into one framework. First buy those cross-cutting capabilities that are hard to build stably yourself, such as an observability backend — maintaining a tracing system yourself is extremely costly, while a mature tracing backend works out of the box; meanwhile keep the simple domain loop written by the team itself, because it carries business semantics and does not need to be handed over to a framework. Only when needs such as state recovery, human interruption, or graph routing appear repeatedly should you introduce the corresponding runtime.

A selection sequence that upgrades based on needs can unfold like this. When the need is just single-round tool calling, the minimal choice is a vendor SDK plus a thin wrapper, and nothing more is needed for now; the upgrade signal is "multi-model compatibility actually happens" — when the team really needs to run a second model, only then consider introducing a model adaptation layer. When the need becomes a multi-step short loop, the minimal choice is a handwritten loop plus trace, and the upgrade signal is "recovery or branching is hard to maintain" — once the recovery logic and branches in the handwritten loop start to get out of control, you should introduce state capability. When the need involves cross-day state, the minimal choice is a persistent workflow or state graph, and the upgrade signal is the appearance of "human tasks and version migration" — only with human breakpoints and schema migration do you truly need a persistent state graph. When the need is multi-role collaboration, the minimal choice is a task queue plus contract, and the upgrade signal is "dynamic delegation has real benefits" — only when tasks really need to be dynamically dispatched to different roles do you introduce a collaboration runtime.

Every layer of decision-making must be documented. The decision record should state the reason for adoption, the rejected options, the exit conditions, and the owner. Without this record, the framework will continuously expand due to inertia rather than value: today a component is introduced for tracing, tomorrow it conveniently brings a state graph, the day after the team starts using the state graph, and no one asks whether these capabilities are still needed. The input of buying abstractions layer by layer is real needs, existing components, and upgrade signals; the output is the thinnest combination of capabilities such as SDK, tracing, queue, and state graph. First buy cross-cutting capabilities, and only thicken the runtime when recovery, interruption, or graph routing repeatedly appear. The mix of building and adopting shows that responsibilities can be split; what must be prevented is the framework continuously expanding by inertia without exit conditions.

NeedMinimal choiceUpgrade signal
Single-round tool callingVendor SDK + thin wrapperMulti-model compatibility actually happens
Multi-step short loopHandwritten loop + traceRecovery/branch hard to maintain
Cross-day statePersistent workflow / state graphHuman tasks and version migration
Multi-role collaborationTask queue + contractDynamic delegation has real benefits

12Connecting the Causal ChainSynthesis

Connect the content of the previous sections into a causal chain, and you can see how this concept travels from an initial problem all the way to verifiable practice. The entire chain starts with handwritten code and ends with canary rollout, with each step forced by the previous one.

First, handwrite the minimal loop and use it to expose the real requirements. Don't rush to choose a framework; first personally write the six actions—assembling context, calling the model, parsing tool intent, executing tools, recording observations, and judging completion or continuation. Only when real business needs are exposed in the handwritten code—such as needing human approval, needing to wait across days, or needing to recover from checkpoints—does the team know what it is actually missing.

Second, list the required capabilities and failure modes. Translate the requirements into a checklist: which tools are needed, which permission boundaries, which retry and cancellation semantics, which state transitions, and explicitly write down the scenarios that can go wrong—crashes, timeouts, duplicate side effects, and concurrent writes. This checklist is the basis for all subsequent decisions.

Third, choose the thinnest candidate framework. Compare layer by layer across the five layers, introduce only capabilities that solve problems on the checklist, and do not introduce layers you don't need. Choose the thinnest layer that meets the current required capabilities, and set an upper bound on exit costs.

Fourth, isolate state with a domain schema. Put orders, approvals, evidence, idempotency keys, and version numbers into a separate, versioned domain state, and derive messages only from state. This way, the business truth does not depend on the framework's message list, and changing frameworks later does not entangle the business database.

Fifth, implement retry, cancellation, and permissions. Distinguish the retry policies of model calls, read-only tools, and write tools; add idempotency keys and result queries to write operations; propagate cancellation signals all the way to model streams, tools, and subtasks; allow tools per tenant item by item, and isolate development and production credentials.

Sixth, preserve raw requests and escape hatches. Keep the provider's raw request and response and extension fields, keep paths that bypass the adapter for direct calls, make adapters pluggable, and prohibit business code from depending on the framework's non-exported internal objects. Unified interfaces do not mean semantic equivalence; after changing adapters, evaluations must be rerun.

Seventh, perform fault injection and compare TCO. In time-box experiments, proactively inject crashes and manual interruptions; compare first implementation time, task success rate, P95 latency, token and tool costs, trace coverage, recovery ability, and upgrade regressions; then put these together with the total cost of ownership formula to confirm that the framework is not trading development speed for long-term diagnostic and migration costs.

Eighth, perform a canary rollout using contract trajectories. Freeze a set of key trajectories—input state, expected tool sequence, permission denial, manual pause, timeout, recovery, and final business state—replay them with the new version and compare events and side effects; first migrate the schema, then run old and new instances separately; after observing cost, latency, and failure distribution during the canary, scale up. Natural language need not be verbatim identical, but permission, state, and side-effect boundaries must not change silently.

This chain starts from “understand the minimal loop first” and ends at “verifiable canary rollout.” It does not require introducing a framework all at once, nor does it treat the framework as the source of correctness; the framework is always only a carrier of mechanistic capabilities such as the run loop, graph, checkpoints, and tracing, while goals, permissions, acceptance, and domain semantics always remain in the team's own hands.

Sources and adaptation notes
Access date: 2026-07-22