Eventum Logo

Eventum

Eventtemplate

FSM

Finite state machine transitions, conditions, logic operators, and complete examples.

Transitions

Each transition defines a target state and a condition:

ParameterTypeDescription
tostringTarget template alias.
whenconditionCondition that must evaluate to true for the transition to fire.

Transitions are evaluated in order — the first matching transition wins. If no transition matches, the machine stays in the current state. Use always as a fallback at the end of a transitions list.

transitions:
  # Evaluated first: go to "error" if too many failures
  - to: error
    when:
      gt:
        shared.fail_count: 10
  # Evaluated second: go to "done" if counter hit zero
  - to: done
    when:
      eq:
        shared.remaining: 0
  # Fallback: stay in "active" otherwise
  - to: active
    when:
      always:

Conditions

Conditions inspect template state variables or event metadata. State is organized into three scopes, and field names follow the pattern <scope>.<field>:

ScopePatternDescription
localslocals.counterState local to the current template. Each template has its own locals.
sharedshared.statusShared across all templates within the same generator.
globalsglobals.session_idGlobal state shared across all generators.

Templates set state values during rendering (e.g., {% do shared.set("counter", shared.counter + 1) %}), and transitions read those values to decide the next state.

Comparison checks

Each takes a mapping of <state_field>: <value>:

ConditionDescriptionExample
eqField equals valueeq: { shared.status: "ready" }
gtField greater than valuegt: { locals.retries: 3 }
geField greater than or equal to valuege: { shared.score: 100 }
ltField less than valuelt: { locals.ttl: 0 }
leField less than or equal to valuele: { shared.attempts: 5 }
matchesField matches regex patternmatches: { shared.path: "^/api/.*" }
# Transition when a counter exceeds a threshold
- to: overloaded
  when:
    gt:
      shared.request_count: 1000

# Transition when status matches a pattern
- to: api_handler
  when:
    matches:
      shared.path: "^/api/v[0-9]+/"

Length checks

Check the length of a sequence field:

ConditionDescriptionExample
len_eqLength equals valuelen_eq: { shared.queue: 0 }
len_gtLength greater than valuelen_gt: { shared.items: 10 }
len_geLength greater or equallen_ge: { shared.batch: 50 }
len_ltLength less than valuelen_lt: { shared.buffer: 100 }
len_leLength less or equallen_le: { shared.errors: 3 }
# Transition when a queue is full
- to: flush
  when:
    len_ge:
      shared.event_buffer: 100

# Transition when all items are processed
- to: complete
  when:
    len_eq:
      shared.pending_items: 0

Membership checks

ConditionDescriptionExample
containsSequence field contains valuecontains: { shared.seen_codes: 500 }
inValue is in sequence fieldin: { shared.status: ["error", "fatal"] }
# Transition if a specific error code was encountered
- to: handle_server_error
  when:
    contains:
      shared.error_codes: 500

# Transition if current status is one of several values
- to: retry
  when:
    in:
      shared.status: ["timeout", "rate_limited", "unavailable"]

Timestamp checks

Evaluate the current event timestamp against specified time components. Any subset of components can be provided — unspecified components are ignored.

ConditionDescription
beforeCurrent timestamp is before the specified time.
afterCurrent timestamp is at or after the specified time.

Available components: year, month, day, hour, minute, second, microsecond.

# Business hours: different behavior before and after 9 AM
- to: peak_traffic
  when:
    after:
      hour: 9
      minute: 0

# Switch to end-of-year mode in December
- to: year_end_processing
  when:
    after:
      month: 12
      day: 1

State and tag checks

ConditionDescriptionExample
definedState field existsdefined: locals.user_id
has_tagsEvent has tag(s)has_tags: critical or has_tags: [urgent, high]
alwaysAlways true (unconditional)always:
neverAlways falsenever:
# Transition only if a field has been set during rendering
- to: authenticated
  when:
    defined: shared.auth_token

# Transition based on event tags
- to: alert
  when:
    has_tags: [critical, security]

# Unconditional fallback (always goes to this state)
- to: default_state
  when:
    always:

Logic operators

Combine or negate conditions:

OperatorDescription
orList of conditions (at least 2). True if any is true.
andList of conditions (at least 2). True if all are true.
notSingle condition. Inverts the result.
# AND: both conditions must be true
- to: critical_alert
  when:
    and:
      - gt:
          shared.error_count: 5
      - has_tags: critical

# OR: either condition triggers the transition
- to: throttle
  when:
    or:
      - gt:
          shared.request_count: 10000
      - gt:
          shared.error_rate: 0.5

# NOT: transition when the field is NOT in the expected set
- to: unknown_method
  when:
    not:
      in:
        shared.method: ["GET", "POST", "PUT", "DELETE"]

# Nested: complex logic
- to: escalate
  when:
    and:
      - gt:
          shared.fail_count: 3
      - or:
          - has_tags: production
          - gt:
              shared.severity: 8

Examples

User session flow

A user goes through login, browsing, and logout. The session tracks page views in shared state and transitions based on accumulated activity.

event:
  template:
    mode: fsm
    templates:
      - login:
          template: templates/login.jinja
          initial: true
          transitions:
            - to: browse
              when:
                always:

      - browse:
          template: templates/browse.jinja
          transitions:
            # After 10+ page views, user proceeds to checkout
            - to: checkout
              when:
                ge:
                  shared.page_views: 10
            # Small chance of early logout (handled in template via random)
            - to: logout
              when:
                eq:
                  shared.should_leave: true
            # Otherwise keep browsing (stay in current state)

      - checkout:
          template: templates/checkout.jinja
          transitions:
            - to: logout
              when:
                always:

      - logout:
          template: templates/logout.jinja
          transitions:
            # Start a new session
            - to: login
              when:
                always:

In templates/browse.jinja, the template updates state:

{%- do shared.set("page_views", shared.get("page_views", 0) + 1) -%}
{%- do shared.set("should_leave", random.random() < 0.05) -%}
{{ timestamp }} GET /page/{{ random.randint(1, 100) }} 200

HTTP request-response with errors

Model a client that sends requests and may get errors, retries, and eventually either succeeds or gives up.

event:
  template:
    mode: fsm
    templates:
      - send_request:
          template: templates/request.jinja
          initial: true
          transitions:
            - to: handle_error
              when:
                gt:
                  shared.status_code: 399
            - to: success
              when:
                le:
                  shared.status_code: 399

      - handle_error:
          template: templates/error.jinja
          transitions:
            # Give up after 3 retries
            - to: give_up
              when:
                ge:
                  shared.retries: 3
            # Retry
            - to: send_request
              when:
                always:

      - success:
          template: templates/success.jinja
          transitions:
            - to: send_request
              when:
                always:

      - give_up:
          template: templates/give_up.jinja
          transitions:
            # Reset and start fresh
            - to: send_request
              when:
                always:

Time-of-day traffic patterns

Use timestamp conditions to switch between traffic patterns depending on the hour.

event:
  template:
    mode: fsm
    templates:
      - night_traffic:
          template: templates/night.jinja
          initial: true
          transitions:
            - to: morning_ramp
              when:
                after:
                  hour: 6

      - morning_ramp:
          template: templates/morning.jinja
          transitions:
            - to: peak_traffic
              when:
                after:
                  hour: 9
            - to: night_traffic
              when:
                before:
                  hour: 6

      - peak_traffic:
          template: templates/peak.jinja
          transitions:
            - to: evening_wind_down
              when:
                after:
                  hour: 17

      - evening_wind_down:
          template: templates/evening.jinja
          transitions:
            - to: night_traffic
              when:
                after:
                  hour: 22

Authentication flow with state checks

Model a system where login creates a token, authenticated requests use it, and the token can expire.

event:
  template:
    mode: fsm
    templates:
      - unauthenticated:
          template: templates/login_attempt.jinja
          initial: true
          transitions:
            # Template sets shared.auth_token on successful login
            - to: authenticated
              when:
                defined: shared.auth_token
            # Stay unauthenticated if login fails

      - authenticated:
          template: templates/api_call.jinja
          transitions:
            # Token expired (template increments request_count)
            - to: unauthenticated
              when:
                gt:
                  shared.request_count: 50
            # Error threshold reached
            - to: locked_out
              when:
                and:
                  - gt:
                      shared.consecutive_errors: 5
                  - has_tags: auth_failure

      - locked_out:
          template: templates/locked.jinja
          transitions:
            # Cool down, then allow retry
            - to: unauthenticated
              when:
                gt:
                  shared.cooldown_ticks: 10

Multi-stage pipeline with queue tracking

Track items flowing through processing stages using length checks.

event:
  template:
    mode: fsm
    templates:
      - ingesting:
          template: templates/ingest.jinja
          initial: true
          transitions:
            # When batch is full, move to processing
            - to: processing
              when:
                len_ge:
                  shared.batch: 100

      - processing:
          template: templates/process.jinja
          transitions:
            # Processing error: some items matched error pattern
            - to: error_handling
              when:
                contains:
                  shared.failed_ids: "FATAL"
            # All processed
            - to: flushing
              when:
                len_eq:
                  shared.batch: 0

      - error_handling:
          template: templates/error_handler.jinja
          transitions:
            - to: processing
              when:
                not:
                  contains:
                    shared.failed_ids: "FATAL"
            - to: flushing
              when:
                always:

      - flushing:
          template: templates/flush.jinja
          transitions:
            - to: ingesting
              when:
                always:

On this page