AgentMLAgentML
GitHubCommunityAgentflare.com
  • Installation
  • Overview
  • Quick Start

State Machines

State machines are the foundation of AgentML. By explicitly defining states, transitions, and behaviors, you create agents with deterministic, auditable, and formally verifiable behavior.

Why State Machines?

Traditional LLM applications rely on emergent behavior from prompts, leading to unpredictable and difficult-to-debug systems. State machines provide:

  • Deterministic Behavior: Same inputs produce same state paths
  • Explicit Control Flow: Clear understanding of agent behavior
  • Formal Verification: Prove correctness of agent logic
  • Better Debugging: Trace exact state transitions
  • Compositional: Build complex agents from simple machines

Basic Concepts

States

A state represents what the agent is currently doing:

<agentml xmlns="github.com/agentflare-ai/agentml"
       datamodel="ecmascript">
 
  <!-- The agent starts in the 'idle' state -->
  <state id="idle">
    <!-- State content here -->
  </state>
 
  <state id="processing">
    <!-- State content here -->
  </state>
 
  <state id="responding">
    <!-- State content here -->
  </state>
</agentml>

Transitions

Transitions move the agent from one state to another when events occur:

<state id="idle">
  <!-- When 'user.message' event occurs, transition to 'processing' -->
  <transition event="user.message" target="processing" />
</state>

Entry and Exit Actions

Execute code when entering or leaving a state:

<state id="processing">
  <!-- Execute when entering this state -->
  <onentry>
    <log expr="'Started processing'" />
    <assign location="startTime" expr="Date.now()" />
  </onentry>
 
  <!-- Execute when leaving this state -->
  <onexit>
    <log expr="'Finished processing in ' + (Date.now() - startTime) + 'ms'" />
  </onexit>
 
  <transition event="complete" target="idle" />
</state>

Complete Example

Here's a simple customer support agent:

<agentml xmlns="github.com/agentflare-ai/agentml"
       datamodel="ecmascript"
       xmlns:gemini="github.com/agentflare-ai/agentml-go/gemini">
 
  <datamodel>
    <data id="user_input" expr="''" />
    <data id="response" expr="''" />
  </datamodel>
 
  <!-- Start in 'idle' state -->
  <state id="idle">
    <onentry>
      <log expr="'Waiting for user input'" />
    </onentry>
 
    <transition event="user.message" target="processing">
      <assign location="user_input" expr="_event.data.message" />
    </transition>
  </state>
 
  <state id="processing">
    <onentry>
      <log expr="'Processing: ' + user_input" />
      <gemini:generate
        model="gemini-2.0-flash-exp"
        location="_event"
        promptexpr="'You are a helpful assistant. User: ' + user_input" />
    </onentry>
 
    <transition event="action.response" target="responding">
      <assign location="response" expr="_event.data.message" />
    </transition>
 
    <transition event="action.error" target="error" />
  </state>
 
  <state id="responding">
    <onentry>
      <log expr="'Bot: ' + response" />
    </onentry>
 
    <transition target="idle" />
  </state>
 
  <state id="error">
    <onentry>
      <log expr="'An error occurred'" />
    </onentry>
 
    <transition target="idle" />
  </state>
</agentml>

Hierarchical States

Nest states within states to manage complexity:

<agentml xmlns="github.com/agentflare-ai/agentml"
       datamodel="ecmascript">
 
  <!-- Top-level state -->
  <state id="authenticated">
    <!-- Nested states -->
    <state id="browsing">
      <transition event="select.product" target="viewing_product" />
    </state>
 
    <state id="viewing_product">
      <transition event="add.to.cart" target="cart" />
      <transition event="back" target="browsing" />
    </state>
 
    <state id="cart">
      <transition event="checkout" target="payment" />
      <transition event="continue.shopping" target="browsing" />
    </state>
 
    <state id="payment">
      <transition event="payment.success" target="order_complete" />
      <transition event="payment.failed" target="cart" />
    </state>
 
    <final id="order_complete" />
 
    <!-- Global transition for authenticated states -->
    <transition event="logout" target="logged_out" />
  </state>
 
  <state id="logged_out">
    <transition event="login.success" target="authenticated" />
  </state>
</agentml>

Benefits of hierarchical states:

  • Shared transitions: Common transitions apply to all nested states
  • Shared entry/exit actions: Execute code for entire state groups
  • Better organization: Group related states together

Parallel States

Execute multiple state machines simultaneously:

<agentml xmlns="github.com/agentflare-ai/agentml"
       datamodel="ecmascript">
 
  <!-- Both state machines run in parallel -->
  <parallel id="monitoring">
    <!-- First parallel region: Health checks -->
    <state id="health_monitor">
      <state id="checking">
        <onentry>
          <log expr="'Running health check'" />
        </onentry>
 
        <transition event="health.ok" target="healthy" />
        <transition event="health.degraded" target="unhealthy" />
      </state>
 
      <state id="healthy">
        <transition target="checking">
          <after delay="30s" />
        </transition>
      </state>
 
      <state id="unhealthy">
        <onentry>
          <raise event="alert.health_issue" />
        </onentry>
 
        <transition target="checking">
          <after delay="10s" />
        </transition>
      </state>
    </state>
 
    <!-- Second parallel region: Performance monitoring -->
    <state id="performance_monitor">
      <state id="measuring">
        <onentry>
          <log expr="'Measuring performance'" />
        </onentry>
 
        <transition event="perf.good" target="optimal" />
        <transition event="perf.slow" target="degraded" />
      </state>
 
      <state id="optimal">
        <transition target="measuring">
          <after delay="60s" />
        </transition>
      </state>
 
      <state id="degraded">
        <onentry>
          <raise event="alert.performance_issue" />
        </onentry>
 
        <transition target="measuring">
          <after delay="30s" />
        </transition>
      </state>
    </state>
  </parallel>
</agentml>

Parallel states are useful for:

  • Monitoring systems: Multiple independent checks
  • Multi-modal interfaces: Handle voice and text simultaneously
  • Background tasks: Process data while maintaining UI state

Conditional Transitions

Use conditions to control which transition executes:

<state id="validate">
  <onentry>
    <assign location="input" expr="_event.data.input" />
  </onentry>
 
  <!-- Conditions evaluated in order -->
  <transition cond="input.length === 0" target="empty_input" />
  <transition cond="input.length < 5" target="too_short" />
  <transition cond="input.length > 100" target="too_long" />
  <transition target="valid" />  <!-- Default: no condition -->
</state>

History States

Remember and return to the last active substate:

<state id="application">
  <history id="app_history" type="shallow" />
 
  <state id="editing">
    <state id="text_mode" />
    <state id="voice_mode" />
  </state>
 
  <state id="paused">
    <transition event="resume" target="app_history" />
  </state>
 
  <transition event="pause" target="paused" />
</state>

History types:

  • shallow: Remember top-level state only
  • deep: Remember entire nested state configuration

Final States

Mark the end of a state machine or sub-machine:

<state id="processing_task">
  <state id="step1">
    <transition event="step1.done" target="step2" />
  </state>
 
  <state id="step2">
    <transition event="step2.done" target="complete" />
  </state>
 
  <!-- Final state -->
  <final id="complete">
    <donedata>
      <param name="result" expr="task_result" />
    </donedata>
  </final>
</state>
 
<!-- This transition executes when 'processing_task' reaches final state -->
<transition event="done.state.processing_task" target="next_task">
  <assign location="last_result" expr="_event.data.result" />
</transition>

Delayed Transitions

Execute transitions after a delay:

<state id="waiting">
  <!-- Transition after 5 seconds -->
  <transition target="timeout">
    <after delay="5s" />
  </transition>
 
  <!-- Can still transition immediately on event -->
  <transition event="user.input" target="processing" />
</state>

Delay formats:

  • Milliseconds: 1000ms
  • Seconds: 5s
  • Minutes: 2m
  • Hours: 1h

State Machine Composition

Invoke other state machines as services:

<state id="orchestrator">
  <onentry>
    <!-- Invoke a worker agent -->
    <invoke id="worker1" type="scxml" src="worker_agent.aml">
      <param name="task_id" expr="current_task.id" />
    </invoke>
  </onentry>
 
  <!-- Handle completion -->
  <transition event="done.invoke.worker1" target="aggregate_results">
    <assign location="worker_result" expr="_event.data" />
  </transition>
 
  <!-- Handle errors -->
  <transition event="error.execution.worker1" target="handle_error" />
</state>

This enables:

  • Code reuse: Share common state machines
  • Separation of concerns: Isolate functionality
  • Parallel execution: Multiple workers simultaneously
  • Fault isolation: Errors don't crash parent

Best Practices

  1. Keep states focused: Each state should have one clear purpose
  2. Use meaningful names: awaiting_user_input not state_1
  3. Document transitions: Add comments explaining event flow
  4. Provide fallback transitions: Handle unexpected events gracefully
  5. Use hierarchical states: Group related states for maintainability
  6. Limit nesting depth: Too many levels make debugging difficult
  7. Test state coverage: Ensure all states are reachable
  8. Use final states: Clearly mark completion points
  9. Log state transitions: Aid debugging with <log> statements
  10. Validate early: Check conditions before expensive operations

Debugging State Machines

Validation

Validate your state machine before running:

agentmlx validate agent.aml

Runtime Snapshots

Capture snapshots to see the current state:

agentmlx run agent.aml --save-snapshots ./debug --snapshot-interval 1

Logging

Add logging to track state transitions:

<state id="processing">
  <onentry>
    <log expr="'Entered processing state at ' + Date.now()" />
  </onentry>
 
  <onexit>
    <log expr="'Exiting processing state'" />
  </onexit>
</state>

Next Steps

  • Explore Events & Schemas for data validation
  • Learn about Namespaces for extending functionality
  • Understand Token Efficiency for optimization
  • Read Document Structure for syntax details
Back to Website© 2025 Agentflare, Inc.
© 2025 Agentflare, Inc.