State Machines: From Loops to Graphs (Explained) ✍ vixhaℓ🕐 2026-07-21📦 21.1 KB 🟢 已读 𝕏 文章列表 文章阐述了状态机理论及其在软件开发中的应用。作者指出Agent框架从链条到循环再到图形的演变,本质上是有限状态机(FSM)的体现。通过将分散的逻辑迁移到明确的转换函数中,状态机能消除不可能状态,避免竞态条件,并提供类型级安全保障。文章展示了基于查找表和Switch语句的实现方式,强调了其在代码审查、测试及系统可靠性方面的优势。 状态机FSM编程范式Agent架构设计 # State Machines: From Loops to Graphs (Explained) **作者**: vixhaℓ **日期**: 2026-07-20T18:36:17.000Z **来源**: [https://x.com/TheVixhal/status/2079274210367775052](https://x.com/TheVixhal/status/2079274210367775052) ---  Every few months the agent-building world adopts a new abstraction, moving from chains to loops and now to graphs, and each of these turns out to be the same underlying idea with a different name. That idea is the finite state machine, a formalism that has existed in textbooks since the 1950s, that specifies TCP and the control unit of your CPU, and that already exists in most codebases in an accidental form built out of Boolean flags and scattered conditionals. Since implementing one deliberately costs almost nothing extra and comes with strong guarantees, it is worth understanding the pattern properly, so this article covers the theory, the implementation, and the reason agent frameworks are converging on it without acknowledging it. # The core A state machine answers a single question, which is that given the current state, when an event occurs, what is the next state? Written as a function, it looks like this: ``` transition(state, event) -> nextState ``` Every program already behaves this way, because a React component, a request handler, a game loop, and an LLM agent all hold some state, receive some event, and move to a new state as a result. Since this structure is present in every program whether or not anyone planned it, the only real decision a developer makes is where the transition logic lives, and usually it ends up scattered across event handlers and conditionals where nobody can see the complete picture. Writing it as one explicit function gathers the complete behavior of the system into one place, and every benefit described in the rest of this article follows from that single move. # The formal definition A finite state machine is formally a 5-tuple: M = (S, Σ, δ, s₀, F) where: - S is a finite set of states - Σ is a finite set of events, called the input alphabet - δ : S × Σ → S is the transition function - s₀ ∈ S is the initial state - F ⊆ S is the set of final states Because S is finite and δ can be written down as a table, the complete behavior of the system can be enumerated, meaning every reachable state and every possible sequence of transitions can be listed and inspected. Arbitrary code lacks this property because its state space is unbounded and its control flow depends on runtime values, and this difference is exactly why protocols, regex engines, and hardware get specified as state machines, since a finite specification can be checked exhaustively and, in safety-critical settings, proven correct. One distinction from the theory appears often enough in practice to be worth knowing. If δ(state, event) yields exactly one next state, the machine is called a deterministic finite automaton (DFA), and if it can yield a set of possible next states, it is called a nondeterministic finite automaton (NFA). Nondeterminism sounds abstract, but a regex engine compiles every pattern into an NFA and then either simulates it directly or converts it into a DFA through subset construction, which means that writing an expression like a+b*c amounts to programming a state machine and letting a compiler construct it for you. Every framework discussed later in this article, including the agent orchestration libraries, is a wrapper around this δ function, and keeping that in mind makes new tooling much easier to evaluate. # The problem it solves: impossible states Consider how data fetching usually gets modeled: ``` let isLoading = false; let isError = false; let isSuccess = false; let data = null; let error = null; ``` Three booleans produce 2³ = 8 combinations, of which perhaps three are meaningful, and a combination like isLoading && isError describes no coherent situation at all, yet the type system permits it and eventually a race condition will construct it, producing a spinner rendered on top of an error message on top of stale data. The problem grows exponentially because each additional flag doubles the state space while the number of valid states grows much more slowly, so a component with five flags has 32 possible combinations and maybe five legitimate ones. The usual response is to write defensive conditions like if (isLoading && !isError && ...) that try to fence off the valid region by hand, and in a growing codebase some case always gets missed. Modeling the same feature as a state machine begins with replacing the flags with an enumeration: ``` type State = 'idle' | 'loading' | 'success' | 'failure'; ``` Under this representation the combination of loading and error cannot even be written down, so the entire category of contradictory-flag bugs disappears at the type level rather than being caught in tests. Functional programmers describe this as making illegal states unrepresentable, and a state machine applies the same principle to how a system evolves over time rather than to a single data structure. Two guarantees follow immediately from the structure: 1. The system is always in exactly one state. 2. Movement is only possible along transitions you defined. The practical weight of the second guarantee is easy to underestimate. In flag-based code any handler can fire at any moment and mutate anything, while in a state machine an event that has no transition defined from the current state simply does nothing. When a user double-clicks a submit button, the second SUBMIT event arrives while the machine is in loading, and since loading defines no SUBMIT transition, the machine ignores it, which means the duplicate-submission bug never existed and no debounce, lock, or disabled-button flag was ever needed to prevent it. # Implementation The transition function is data, and the cleanest implementations treat it that way, which in most languages means a plain lookup table: ``` const machine = { initial: 'idle', states: { idle: { FETCH: 'loading', }, loading: { RESOLVE: 'success', REJECT: 'failure', CANCEL: 'idle', }, success: { REFETCH: 'loading', }, failure: { RETRY: 'loading', }, }, }; function transition(state, event) { return machine.states[state]?.[event] ?? state; // undefined event: stay put } ``` That one function is a complete state machine interpreter, and the tabular shape carries several practical consequences. The full behavior of a feature fits on one screen, a diff of this object in code review shows behavior changes directly rather than requiring the reviewer to simulate control flow, and the object can be serialized, stored in a database, sent over the network, or rendered into a diagram automatically. Testing also changes character, because states multiplied by events forms a finite table, so a test suite can walk the entire table instead of sampling paths through the code and hoping the important ones were covered. The same machine can also be written as a switch statement: ``` function transition(state: State, event: Event): State { switch (state) { case 'idle': return event.type === 'FETCH' ? 'loading' : state; case 'loading': switch (event.type) { case 'RESOLVE': return 'success'; case 'REJECT': return 'failure'; case 'CANCEL': return 'idle'; default: return state; } case 'success': return event.type === 'REFETCH' ? 'loading' : state; case 'failure': return event.type === 'RETRY' ? 'loading' : state; } } ``` This version is more verbose, but in TypeScript or Rust the compiler enforces exhaustiveness, so adding a new state and forgetting to handle it fails the build instead of failing in production. Rust takes the idea furthest through the typestate pattern, in which each state becomes a distinct type and an invalid transition fails to compile at all. Anyone who has written a Redux reducer or used useReducer has already written a function of the form (state, action) => nextState, so this pattern is more familiar than it might sound. The difference is that most reducers allow any action to modify anything from any state, which quietly reintroduces the boolean-flag problem with better syntax, whereas a reducer becomes a genuine state machine once it checks the current state before considering the event, and that single change in ordering is what carries all of the guarantees described above. # Finite state vs. context A reasonable objection at this point is that real application state includes form inputs, arrays, and timestamps, none of which can be enumerated, so the finiteness requirement appears to rule out real programs. The standard answer is to split state into two parts that play different roles. The finite state captures the mode the system is in, using values like idle, loading, editing, or disconnected, and this part stays qualitative, small, and enumerable. The context, also called extended state, holds everything quantitative, such as form values, retry counts, and fetched data, and this part is allowed to be unbounded because the machine never enumerates it. The machine governs the mode while the context travels alongside it, and a machine extended this way is formally called an extended state machine, with a transition function that returns more information: ``` transition(state, context, event) -> (nextState, nextContext, actions) ``` The following example implements retries with a cap of three attempts: ``` function transition(state, ctx, event) { if (state === 'failure' && event.type === 'RETRY') { if (ctx.retries < 3) { return ['loading', { ...ctx, retries: ctx.retries + 1 }, ['fetch']]; } return ['gaveUp', ctx, ['notifyUser']]; } // ... } ``` Three standard concepts appear in this code and deserve names. A guard is a condition attached to a transition, and the check ctx.retries < 3 is one, allowing the same RETRY event to lead to loading while attempts remain and to gaveUp once they run out. An action is a side effect that a transition triggers, and the returned ['fetch'] is one, with the important detail being that the machine only decides which effects should occur and returns them, while an interpreter outside the function actually executes them, which keeps the transition function pure and makes testing it possible without any mocks. Redux middleware and Elm's pattern of returning commands from its update function follow the same separation. Entry and exit actions round out the set, and these are effects attached to entering or leaving a state rather than to any particular edge, so that entering loading starts a spinner and a timeout while exiting loading cancels both, regardless of which of the four possible edges was taken in or out. Exit actions in particular eliminate the family of bugs around leaked timers, dangling subscriptions, and forgotten cleanup, because when the cancellation is attached to the exit of loading, every path out of that state runs it and no code path can leave the timer running. Achieving the same reliability with flags requires remembering the cleanup in every handler that could leave loading mode, and in practice one handler always forgets. # Mealy and Moore machines The classical literature distinguishes two variants of the machine according to where outputs attach. In a Moore machine the output depends on the current state alone, as in a traffic light that stays lit for as long as the machine remains in the green state, so outputs effectively live on the nodes of the diagram. In a Mealy machine the output depends on the state and the event together, as in a vending machine that dispenses when a COIN event arrives during the idle state, so outputs live on the edges instead. The two variants are equivalent in expressive power and can be converted into each other, and real systems mix both, since entry and exit actions are Moore-flavored while transition actions are Mealy-flavored. Beyond helping with older papers and hardware documentation, the distinction supplies a genuinely useful design question, namely whether a given effect should happen because the system is in a state or because of how it arrived there, and since the two answers behave differently under edge cases, conflating them produces subtle bugs. # Where this already runs Several major pieces of infrastructure are specified as state machines, and looking at them shows how far the pattern predates its current rediscovery. TCP defines every connection through its state diagram, whose states include LISTEN, SYN_SENT, ESTABLISHED, FIN_WAIT_1, and TIME_WAIT. RFC 793 specified the protocol as a state diagram back in 1981 because prose was too ambiguous for something this critical, and to this day disagreements between implementations get settled by pointing at the machine. Regex engines run on DFAs, which process each input character in constant time with no backtracking, and grep owes its speed directly to this property of the underlying model rather than to any implementation cleverness. Game AI has used the pattern since the arcade era, with enemy behavior following transitions such as patrol to chase to search and back to patrol, driven by events like seeing or losing the player. The ghosts in Pac-Man are four small state machines, and their famously distinct personalities come entirely from four different transition tables. In hardware the pattern is foundational, since a CPU is a clocked state machine and both Verilog and VHDL treat FSMs as a primary design idiom. Traffic lights, elevators, and vending machines became the textbook examples precisely because failures in those systems cause physical harm, so their logic had to be built on something checkable. UI code contains the same structure implicitly, because every form passes through stages like pristine, editing, validating, submitting, and finally succeeded or failed, and dropdowns, media players, and drag interactions have the same character. The familiar bug where a modal closes but its dark overlay remains on screen occurs when the system occupies a state combination its designers never intended, which is precisely the situation an explicit machine makes unrepresentable. # Agents An agent graph describes how an agent moves between steps as it plans, calls tools, evaluates results, retries, asks a human, and eventually finishes. The field started with linear pipelines, moved to DAGs after admitting that agents fail and branch, and then had to allow cycles after admitting that agents also retry until verified and loop through rounds of human feedback, which leaves a directed cyclic graph with labeled edges, a start node, and terminal nodes. Mapping those parts onto the 5-tuple, the nodes are S, the edge labels are Σ, the edges are δ, the start node is s₀, and the terminal nodes are F, so the agent graph reconstructs the finite state machine under a new name. A realistic agent written explicitly as a machine looks like this: ``` const agent = { initial: 'planning', states: { planning: { PLAN_READY: 'executing', NEEDS_INFO: 'awaitingHuman', }, executing: { TOOL_RESULT: 'evaluating', TOOL_ERROR: 'executing', // self-loop: retry the tool BUDGET_EXCEEDED: 'failed', }, evaluating: { VERIFIED: 'done', NOT_GOOD_ENOUGH: 'planning', // a cycle, which a DAG cannot express UNSAFE: 'awaitingHuman', }, awaitingHuman: { APPROVED: 'executing', REJECTED: 'failed', CLARIFIED: 'planning', }, done: {}, // final failed: {}, // final }, }; ``` Making the machine explicit gives agents several properties that matter in production. Retry logic becomes a guarded edge, since the behavior of retrying until verified reduces to the single transition from evaluating back to planning with a guard requiring attempts to stay under a maximum, so runaway loops are ruled out by the structure itself instead of by a counter that someone hopefully remembered to add to a while loop. Control flow also stays out of the model's hands, which matters because LLM output is nondeterministic and letting it drive control flow directly produces agents that declare themselves finished when they are not. Under the machine, model output first gets classified into an event such as VERIFIED or UNSAFE, and the machine then checks whether that event has a legal transition from the current state, so a model can hallucinate an event but a hallucinated event with no matching edge goes nowhere, and the safety-critical route from UNSAFE to awaitingHuman ends up enforced by a lookup table rather than by instructions in a system prompt. Long-running agents become persistable as well, because the pair of current state and context is one small serializable value, so an agent waiting three days for human approval reduces to writing the pair ('awaitingHuman', ctx) into a database row, terminating the process, and rehydrating when the approval webhook fires. Durable workflow engines such as Temporal and AWS Step Functions are built on exactly this idea, and Step Functions goes as far as requiring the machine to be written out as JSON. Debugging gets simpler for the same reason, since the question of what the agent is doing becomes a single-column query and the question of how it got there becomes the event history, whereas answering the same questions about a free-form loop requires reconstructing behavior from traces. Multi-agent systems pair the machine naturally with the actor model, in which each agent is an actor holding private state and communicating only through messages, while each actor's internal behavior is a state machine whose events are the incoming messages. Erlang built this exact combination in the 1980s, its OTP framework ships a state-machine actor primitive called gen_statem, and the result has run telecom switches at extreme uptime for decades, which means multi-agent architecture is also a rediscovery, though one that belongs to a separate article. # Statecharts Flat state machines fail to scale in one specific way, which shows up as soon as a system has independent concerns. A text editor where bold, italic, and underline can each be toggled independently needs flat states like bold_italic, bold_underline, and bold_italic_underline to cover the combinations, so the combinatorial explosion from the boolean-flag section returns, now with strings instead of booleans. David Harel addressed this in 1987 with statecharts, introduced in his paper "Statecharts: A Visual Formalism for Complex Systems," which remains genuinely readable today. The formalism adds three constructs to the plain machine: - Hierarchy allows states to contain child states, so if connected contains syncing and idle, a single DISCONNECT transition defined on the parent covers every child and the duplication of shared transitions ends. - Parallel regions let independent concerns run side by side within one machine, so bold, italic, and underline become three parallel two-state regions, giving 2 + 2 + 2 = 6 states instead of 2 × 2 × 2 = 8 combined ones, and the gap widens rapidly as more regions are added. - History states make a parent remember which child was last active, so re-entering the parent resumes there, which is the mechanism behind a pause menu that returns exactly where you left off. Statecharts remain state machines formally and carry the same guarantees, since these constructs only compress the description of large machines rather than changing the model, and the state diagrams in UML are statecharts by another name. # When not to use this Genuinely sequential logic that runs A then B then C with no waiting, no external events, and no failure branches worth modeling belongs in a plain function, because state machines earn their cost only when events and time enter the picture. Truly continuous state, as in a physics simulation or a numerical solver, gains nothing from the framing either, because there are no discrete modes to model, and at the other end of the scale a two-state toggle needs nothing beyond useState(false). A reliable trigger in practice is the moment a second boolean appears that interacts with the first, or the moment a comment appears that reads "but only if we're currently...", because at that point the code has already grown the shape of a machine and would benefit from becoming one explicitly. # Wrapping up Loops and graphs both turned out to be state machines once their states, events, and transitions were named, and whatever abstraction arrives after them can be judged the same way. If it can answer those three questions, it is a state machine that can be reasoned about, and if it cannot, it is scattered control flow with better marketing. ## 相关链接 - [𝙩𝙮≃𝙛{𝕩}^A𝕀²·ℙarad𝕚g𝕞 reposted](https://x.com/TaNGSoFT) - [vixhaℓ](https://x.com/TheVixhal) - [@TheVixhal](https://x.com/TheVixhal) - [15K](https://x.com/TheVixhal/status/2079274210367775052/analytics) - [Upgrade to Premium](https://x.com/i/premium_sign_up) - [2:36 AM · Jul 21, 2026](https://x.com/TheVixhal/status/2079274210367775052) - [15.5K Views](https://x.com/TheVixhal/status/2079274210367775052/analytics) - [View quotes](https://x.com/TheVixhal/status/2079274210367775052/quotes) --- *导出时间: 2026/7/21 09:36:01* --- ## 中文翻译 # 状态机:从循环到图解(详解) **作者**: vixhaℓ **日期**: 2026-07-20T18:36:17.000Z **来源**: [https://x.com/TheVixhal/status/2079274210367775052](https://x.com/TheVixhal/status/2079274210367775052) ---  每隔几个月,智能体构建领域就会采纳一种新的抽象概念,从链到循环,再到现在的图。事实证明,这些不过是同一个底层思想换了个不同的名字罢了。 这个思想就是有限状态机。这种形式化方法自20世纪50年代以来就存在于教科书里,它规定了TCP协议和你CPU中的控制单元,而且在大多数代码库中,它已经以一种偶然的形式存在,由布尔标志和分散的条件语句拼凑而成。 由于刻意实现一个状态机几乎不需要额外的成本,而且能带来强有力的保证,因此值得正确理解这种模式。所以,本文涵盖了理论、实现,以及为什么智能体框架正在趋同于这种模式却不肯承认。 # 核心 状态机只回答一个问题:给定当前状态,当一个事件发生时,下一个状态是什么?写成函数的形式如下: ``` transition(state, event) -> nextState ``` 每个程序的行为本质上都是这样的,因为React组件、请求处理器、游戏循环和LLM智能体都持有某种状态,接收某些事件,并因此转移到新状态。 由于无论是否有人计划,这种结构都存在于每个程序中,因此开发者唯一真正的决定就是将转换逻辑放在哪里,而通常它最终会分散在事件处理器和条件语句中,导致没有人能看到全貌。 将其写成一个显式的函数可以将系统的完整行为集中到一个地方,本文其余部分描述的每一个好处都源于这一举动。 # 形式化定义 有限状态机在形式上是一个5元组: M = (S, Σ, δ, s₀, F) 其中: - S 是有限的状态集合 - Σ 是有限的事件集合,称为输入字母表 - δ : S × Σ → S 是转换函数 - s₀ ∈ S 是初始状态 - F ⊆ S 是最终状态的集合 因为 S 是有限的,且 δ 可以写成表格的形式,所以系统的完整行为可以被枚举,这意味着每一个可达状态和每一种可能的转换序列都可以被列出和检查。 任意代码缺乏这种属性,因为它的状态空间是无界的,且控制流依赖于运行时值,而这种差异正是为什么协议、正则表达式引擎和硬件都被指定为状态机的原因——因为有限的规范可以被详尽地检查,并且在安全关键的设置中,可以被证明是正确的。 理论上的一个区别在实践中出现得足够频繁,值得了解。如果 δ(state, event) 恰好产生一个下一个状态,该机器被称为确定性有限自动机(DFA);如果它可以产生一组可能的下一个状态,则被称为非确定性有限自动机(NFA)。 非确定性听起来很抽象,但正则表达式引擎会将每个模式编译成NFA,然后要么直接模拟它,要么通过子集构造将其转换为DFA,这意味着编写一个像 `a+b*c` 这样的表达式就相当于编写一个状态机,并让编译器为你构造它。 本文后面讨论的每个框架,包括智能体编排库,都是这个 δ 函数的包装器,记住这一点会让评估新工具变得容易得多。 # 它解决的问题:不可能状态 考虑一下数据获取通常是如何建模的: ``` let isLoading = false; let isError = false; let isSuccess = false; let data = null; let error = null; ``` 三个布尔值产生 2³ = 8 种组合,其中也许只有三种是有意义的,而像 `isLoading && isError` 这样的组合根本描述不出任何连贯的情况,但类型系统却允许它的存在,最终竞态条件会构造出它,导致在过时数据之上渲染出一个错误消息,并在上面再渲染一个加载转圈。 这个问题呈指数级增长,因为每个额外的标志都会使状态空间翻倍,而有效状态的数量增长却慢得多,所以一个有五个标志的组件有32种可能的组合,而可能只有五个是合法的。 通常的应对措施是编写防御性条件,如 `if (isLoading && !isError && ...)`,试图手动圈出有效区域,而在不断增长的代码库中,总会有某个情况被遗漏。 将同一功能建模为状态机,首先要用枚举替换标志: ``` type State = 'idle' | 'loading' | 'success' | 'failure'; ``` 在这种表示法下,加载和错误的组合根本无法被写出来,所以整类矛盾的标志错误在类型层面就消失了,而不必等到测试中才被发现。 函数式程序员将这描述为“使非法状态无法表示”,而状态机将同样的原则应用于系统随时间的演变方式,而不是单个数据结构。 两个保证直接源于这种结构: 1. 系统总是恰好处于一个状态。 2. 只能沿着你定义的转换进行移动。 第二个保证的实际分量很容易被低估。在基于标志的代码中,任何处理器都可以在任何时刻触发并修改任何东西,而在状态机中,如果在当前状态下没有为某个事件定义转换,该事件就什么都不做。 当用户双击提交按钮时,第二个 SUBMIT 事件在机器处于 loading 状态时到达,由于 loading 没有定义 SUBMIT 转换,机器会忽略它。这意味着重复提交的bug根本就不存在,也不需要任何防抖、锁或禁用按钮标志来防止它。 # 实现 转换函数就是数据,最清晰的实现也是这样对待它的,在大多数语言中这意味着一个简单的查找表: ``` const machine = { initial: 'idle', states: { idle: { FETCH: 'loading', }, loading: { RESOLVE: 'success', REJECT: 'failure', CANCEL: 'idle', }, success: { REFETCH: 'loading', }, failure: { RETRY: 'loading', }, }, }; function transition(state, event) { return machine.states[state]?.[event] ?? state; // undefined event: stay put } ``` 这唯一的一个函数就是一个完整的状态机解释器,而这种表格形状带来了几个实际后果。 一个功能的完整行为可以容纳在一屏内,代码审查中该对象的差异直接显示了行为的变化,而不需要审查者模拟控制流,而且该对象可以被序列化、存储在数据库中、通过网络发送或自动渲染成图表。 测试的性质也发生了变化,因为状态乘以事件形成了一个有限的表格,所以测试套件可以遍历整个表格,而不是通过代码路径采样并希望覆盖了重要的路径。 同样的机器也可以写成 switch 语句: ``` function transition(state: State, event: Event): State { switch (state) { case 'idle': return event.type === 'FETCH' ? 'loading' : state; case 'loading': switch (event.type) { case 'RESOLVE': return 'success'; case 'REJECT': return 'failure'; case 'CANCEL': return 'idle'; default: return state; } case 'success': return event.type === 'REFETCH' ? 'loading' : state; case 'failure': return event.type === 'RETRY' ? 'loading' : state; } } ``` 这个版本更冗长,但在 TypeScript 或 Rust 中,编译器会强制执行穷尽性检查,所以添加一个新状态却忘记处理它会导致构建失败,而不是在生产中失败。 Rust 通过类型状态模式将这一思想发挥得最远,其中每个状态变成一个不同的类型,而无效的转换根本无法编译。 任何写过 Redux reducer 或使用过 useReducer 的人都已经写过形式为 `(state, action) => nextState` 的函数,所以这种模式比听起来更熟悉。 区别在于,大多数 reducer 允许任何动作从任何状态修改任何东西,这悄悄地用更好的语法重新引入了布尔标志问题,而一旦 reducer 在考虑事件之前检查当前状态,它就变成了真正的状态机,正是这单一的顺序改变承载了上述所有保证。 # 有限状态 vs. 上下文 此时一个合理的反对意见是,实际的应用状态包括表单输入、数组和时间戳,这些都无法枚举,所以有限性的要求似乎排除了实际程序。 标准的答案是将状态分成扮演不同角色的两部分。有限状态捕获系统所处的模式,使用诸如 idle、loading、editing 或 disconnected 之类的值,这部分保持定性、小型且可枚举。 上下文,也称为扩展状态,保存所有定量的东西,如表单值、重试计数和获取的数据,这部分允许是无界的,因为机器从不枚举它。 机器控制模式,而上下文随之同行,以这种方式扩展的机器在形式上被称为扩展状态机,其转换函数返回更多信息: ``` transition(state, context, event) -> (nextState, nextContext, actions) ``` 下面的例子实现了上限为三次尝试的重试: ``` function transition(state, ctx, event) { if (state === 'failure' && event.type === 'RETRY') { if (ctx.retries < 3) { return ['loading', { ...ctx, retries: ctx.retries + 1 }, ['fetch']]; } return ['gaveUp', ctx, ['notifyUser']]; } // ... } ``` 这段代码中出现了三个标准概念,值得命名。守卫是附加在转换上的条件,`ctx.retries < 3` 就是一个,它允许同一个 RETRY 事件在剩余尝试次数时导向 loading,而在次数用尽时导向 gaveUp。 动作是转换触发的副作用,返回的 `['fetch']` 就是一个,重要的细节是机器只决定应该发生哪些副作用并返回它们,而函数外的解释器实际执行它们,这保持了转换函数的纯净,使得可以在没有任何 mock 的情况下测试它。 Redux 中间件和 Elm 从其 update 函数返回命令的模式遵循同样的分离。进入和退出动作补全了这一集合,这些是附加在进入或离开某个状态上的副作用,而不是任何特定的边,因此进入 loading 会启动一个转圈和一个超时,而退出 loading 会取消两者,无论进出的是四条可能边中的哪一条。 特别是退出动作消除了围绕泄漏计时器、悬空订阅和遗忘清理的一整类 bug,因为当取消附加在 loading 的退出上时,离开该状态的每条路径都会运行它,没有代码路径能让计时器继续运行。 用标志实现同样的可靠性需要在每一个可能离开 loading 模式的处理器中记住清理,而在实践中,总有一个处理器会忘记。 # Mealy 机和 Moore 机 经典文献根据输出附加的位置区分了机器的两种变体。在 Moore 机中,输出仅取决于当前状态,就像交通灯只要机器保持在 green 状态就会一直亮着,所以输出实际上存在于图的节点上。 在 Mealy 机中,输出取决于状态和事件,就像自动售货机在 idle 状态期间收到 COIN 事件时出货,所以输出存在于边上。 这两种变体在表达能力上是等价的,可以相互转换,而真实系统混合了两者,因为进入和退出动作具有 Moore 风格,而转换动作具有 Mealy 风格。 除了有助于理解旧论文和硬件文档外,这种区别提供了一个真正有用的设计问题,即给定的副作用应该是因为系统处于某种状态而发生,还是因为系统到达该状态的方式而发生,由于这两个答案在边缘情况下的行为不同,将它们混淆会产生微妙的 bug。 # 它已经在何处运行 几个主要的基础设施部分被指定为状态机,观察它们可以发现这种模式比当前的重新发现早了多久。 TCP 通过其状态图定义每个连接,其状态包括 LISTEN、SYN_SENT、ESTABLISHED、FIN_WAIT_1 和 TIME_WAIT。RFC 793 早在1981年就将协议指定为状态图,因为对于如此关键的事物,散文描述太模糊了,直至今日,实现之间的分歧都要通过指向机器来解决。 正则表达式引擎在 DFA 上运行,DFA 以恒定时间处理每个输入字符,无需回溯,而 grep 的速度直接归功于底层模型的这一属性,而不是任何实现的聪明才智。 游戏 AI 自街机时代以来就使用了这种模式,敌人行为遵循从巡逻到追击再到搜索并回到巡逻的转换,由看见或失去玩家等事件驱动。吃豆人中的幽灵是四个小状态机,它们著名的独特性格完全源于四个不同的转换表。 在硬件中,这种模式是基础性的,因为 CPU 是一个时钟状态机,而 Verilog 和 VHDL 都将 FSM 视为主要设计习惯用法。交通灯、电梯和自动售货机成为教科书例子,正是因为这些系统的故障会造成物理伤害,所以它们的逻辑必须建立在可检查的基础上。 UI 代码隐式地包含了相同的结构,因为每个表单都会经历 pristine、editing、validating、submitting,最后 succeeded 或 failed 等阶段,而下拉菜单、媒体播放器和拖放交互具有相同的特征。 常见的模态框关闭但其深色遮罩仍保留在屏幕上的 bug,发生在系统处于其设计者从未预期的状态组合时,这正是显式机器使其无法表示的情况。 # 智能体 智能体图描述了智能体在规划、调用工具、评估结果、重试、询问人类并最终完成的过程中如何在步骤之间移动。 该领域从线性管道开始,在承认智能体会失败和分支后转向 DAG,然后在承认智能体也会重试直到验证并通过多轮人类反馈循环后,不得不允许循环,这留下了一个带有标记边、起始节点和终端节点的有向循环图。 将这些部分映射到5元组上,节点是 S,边标记是 Σ,边是 δ,起始节点是 s₀,终端节点是 F,所以智能体图以新名称重构了有限状态机。 显式编写为机器的 realistic agent 如下所示: ``` const agent = { initial: 'planning', states: { planning: { PLAN_READY: 'executing', NEEDS_INFO: 'awaitingHuman', }, executing: { TOOL_RESULT: 'evaluating', TOOL_ERROR: 'executing', // self-loop: retry the tool BUDGET_EXCEEDED: 'failed', }, evaluating: { VERIFIED: 'done', NOT_GOOD_ENOUGH: 'planning', // a cycle, which a DAG cannot express UNSAFE: 'awaitingHuman', }, awaitingHuman: { APPROVED: 'executing', REJECTED: 'failed', CLARIFIED: 'planning', }, done: {}, // final failed: {}, // final }, }; ``` 使机器显式化赋予了智能体几个在生产中很重要的属性。 重试逻辑变成了一条受保护的边,因为“重试直到验证”的行为简化为单一的转换……
A Agent 工程的四个深坑:Demo 到生产没有捷径 作者分享了将 AI Agent 从 Demo 推向生产环境过程中遇到的四个“深坑”:Function Calling 的不可预测性需加代码校验兜底;多步任务必须引入状态机和 checkpoint 防止重复执行或死无对证;记忆管理需分层以解决 token 爆炸和“丢失中间”问题;权限控制是最大风险,需防范 prompt injection 并建立分级审计机制。 技术 › Agent ✍ 老金🕐 2026-05-16 AgentLLM架构设计生产环境Function Calling状态机陷阱防御性编程
从 从任务板到文件协议:两个 Agent 协作系统的边界与互补 本文探讨了多 Agent 协作系统中的架构设计问题。作者对比了基于 IPC 的“任务板”模式与自研的“文件协议+状态机”模式,阐述了为何选择文件系统作为任务真理源以实现跨 Session 的可恢复性与可审计性。文章提出了一种混合架构,利用 IPC 处理高频进程心跳,同时保留文件层作为核心状态与证据链的存储,最终实现了稳定、解耦且支持异步长任务的自动工程流。 技术 › Agent ✍ 周.乙🕐 2026-05-02 Agent多Agent协作架构设计文件协议状态机IPCDevOpsOpenAI Symphony工程实践自动化
A Agents 201: The Unit Shrank 文章探讨了 AI Agent 架构的演变。作者认为传统的单体 Agent 因 Token 成本过高和缺乏专业化而失效,基本单元应从“Agent”转变为更小的“Worker”。文章介绍了由 Worker、Function 和 Trigger 组成的全新编排模型,实现了更高效的模块化开发、灵活的架构模式以及安全运行时。 技术 › Agent ✍ Rohit Ghumare🕐 2026-04-24 Agent架构设计LLM多智能体Token成本模块化Sandbox编程范式DevOps
做 做完一次总体设计后,我重新理解了企业级知识库 文章基于企业级智能知识库项目的实践,探讨了企业知识库的真正门槛。指出其不仅是文档存储,更是一套涵盖构建、治理、使用和优化的闭环运行机制。重点分析了知识库与知识空间的区别、知识生命周期管理、可信溯源及权限控制,强调知识库应作为支撑未来 Agent 的企业级 AI 知识引擎。 技术 › LLM ✍ 土猛的员外🕐 2026-07-29 企业知识库RAGAgent知识工程架构设计
这 这项目给Claude Code塞了28个心智模型和批判性思维技能 该项目为Claude Code集成了28个心智模型和批判性思维技能,包括第一性原理、贝叶斯推理、系统思考等。用户可按需调用,帮助结构化决策和策略分析。测试显示技能未稳定提升准确率,但作者认为作为思考脚手架仍优于裸奔。 技术 › Claude Code ✍ Amto (@XAMTO_AI)🕐 2026-07-29 心智模型批判性思维第一性原理贝叶斯推理系统思考OODA循环Agent决策辅助架构设计风险评估
R Run Your Harness Outside of the Sandbox (Why and How) 本文探讨了2026年以来关于Agent运行位置的争论,指出行业趋势是将Agent运行在沙箱之外。作者详细解释了沙箱内运行的三个主要问题:爆炸半径、信任边界和沙箱的间歇性运行,并提出了将沙箱作为工具暴露的正确架构,最后提供了基于Vercel AI SDK的实现示例和生产环境中的挑战。 技术 › Agent ✍ Nathan Flurry🕐 2026-07-28 Agent沙箱架构设计DevOps后端LLM安全性生产环境Vercel AI SDK状态管理
大 大脑对长周期 Agent 的启示 文章通过对比人脑的双速学习机制(海马体与皮层),指出当前 AI Agent 仅依赖大模型权重的局限性。真正的长周期智能需要将预测模型与经验状态结合。现有的外部记忆存储实则是模拟海马体的快速捕获功能,未来的关键在于实现高效的记忆筛选机制。 技术 › Agent ✍ Yohei🕐 2026-07-28 Agent架构设计脑科学长周期记忆CLSM理论
A Agent工程架构解析:Harness、Loop与Graph的区别 本文深入解析Agent工程中常被混淆的三个架构层级:Harness工程构建模型运行环境与基础能力;Loop工程设计工作反馈循环,通过验证与迭代提升质量;Graph工程则显式定义工作流拓扑,控制节点分支与状态转换。文章强调理清环境、反馈与流的关系对构建生产级Agent至关重要。 技术 › Harness Engineering ✍ beamnxw🕐 2026-07-26 Agent架构设计工程化工作流LangChainOpenAIAgent HarnessLoop Engineering
构 构建智能代理的三层架构:Loop、Graph与Harness 本文提出解决Agent重复读取数据和Token浪费的三层架构方案:Loop负责单元工作的收集-行动-验证闭环;Graph通过分发和并行管理复杂任务;Harness作为运行时环境提供工具和上下文隔离。文中提供了具体的代码实现思路和实战演示。 技术 › Agent ✍ Archive🕐 2026-07-25 AgentLoopGraphHarness架构设计上下文管理Token优化验证机制代码实现
如 如何构建一个在你睡眠时发现商业机会的 Agent 图 文章介绍了一个由多个专业 Agent 组成的图系统,用于自动化商业机会扫描、分析和验证。该系统通过并行处理和独立验证机制,解决了人工研究效率低的问题,仅在发现高质量机会时唤醒用户。 技术 › Agent ✍ NeilXbt🕐 2026-07-25 Agent多智能体架构设计自动化商业分析Graph
T The complete Graph Engineering playbook for Claude Code 本文介绍了Graph Engineering(图工程)的概念,阐述如何利用Claude Code构建分布式Agent系统。通过将任务分解为线性、分发、规约和验证等节点,实现高效、可靠的AI工作流,优化计算成本和结果质量。 技术 › Claude Code ✍ Gyomei🕐 2026-07-24 图工程AI工作流ClaudeAgent架构设计编程分布式系统自动化
H How to master graph engineering 本课程教授如何构建 AI 智能体图,涵盖图的基本概念、关键模式(如菱形模式)、停止规则及人工审批环节。包含三个实战案例:深度研究台、SEO 内容生成器和市场推广套件,旨在提升业务效率并控制成本。 技术 › Agent ✍ Machina🕐 2026-07-23 AgentGraphLLMClaudeWorkflow工程化自动化架构设计效率实战