hermes

Implement a state machine

Models a business process such as an order, booking or approval as an explicit state machine with states, transitions, guards and side effects, then implements it with exhaustive tests.

context

Business processes usually grow as a pile of booleans and status strings (is_paid, is_shipped, cancelled_at, status = 'pending_review') checked in scattered if statements. The result is impossible combinations (shipped but not paid), transitions that skip a step, side effects that fire twice, and two requests that both move the same order from "pending" at the same moment. An explicit state machine makes the legal states and transitions a single table that can be read, tested exhaustively and enforced at the database, with side effects attached to transitions instead of sprinkled around.

task

Model and implement this process as an explicit state machine:

process

Only if [STACK] is given: Stack: Only if [PERSISTENCE] is given: Persistence:

  1. If you were given code, read every place that reads or writes the status fields and flags, and list the combinations that actually occur. Do not assume the process description matches the code; note differences.
  2. Model the machine:
  • States: a closed set with one-line meanings; terminal states marked. Replace combinations of flags with single states where they represent one; keep orthogonal concerns (for example payment versus fulfilment) as separate machines only if they truly vary independently.
  • Events and transitions: a table of from-state, event, guard, to-state and side effects. Every transition not in the table is illegal.
  • Guards: conditions that must hold (for example "payment captured", "actor is an approver"), evaluated with the data at transition time.
  • Side effects: what happens on each transition (emails, charges, events, stock changes), and whether each must run inside the transaction or after commit (through an outbox or a background job), so that a rolled-back transition never sends an email.
  • Timeouts: transitions triggered by time (for example "unpaid after 30 minutes → expired") and what runs them. Draw it as a Mermaid stateDiagram-v2.
  1. Ask about any rule the process does not specify (can a shipped order be cancelled? who can reopen a rejected request?). List them under Open questions with a proposed default; implement the default only if it is the conservative choice (the transition stays illegal), and mark it.
  2. Implement it following the repo's patterns: the transition table as data or as explicit code in one module, a single transition(entity, event, context) entry point that checks the current state and guard, applies the change and records it, and a typed error for illegal transitions. Use a state machine library only if the repo already uses one or the user asked for it.
  3. Make transitions safe under concurrency: a conditional update (UPDATE … SET state = :to WHERE id = :id AND state = :from, or a version column) and a check of the affected row count, so that two concurrent requests cannot both make the same transition. Record each transition in a history table (from, to, event, actor, time) for audit and debugging.
  4. Enforce the closed set of states at the storage level where possible (an enum type or a check constraint).
  5. Write tests: a table-driven test over every state and event pair that checks legal transitions succeed and every illegal one is rejected; each guard's pass and fail case; side effects fire exactly once and only after a successful commit; the concurrent double-transition case; and timeout transitions with a controllable clock.
  6. Replace the scattered flag checks in the code you were given with calls to the state machine, keeping behaviour identical except where you fixed a documented impossible state. Run the tests and report the real results.
constraints
  • Do not change business behaviour silently. Every behaviour difference from the current code is listed with the reason.
  • If existing data contains combinations of flags that map to no state, write the mapping query and stop for a decision before migrating it.
  • Keep the change as small as possible around the state machine; do not refactor unrelated code.
  • Read the relevant code before making a claim about it. Do not guess what a file, function or config contains.
  • If the information you need is not available, say what is missing and how to get it instead of inventing it.
  • Before saying the work is done, run the check that proves it (tests, build, type check or the command the user gave) and report the real result.
  • If you could not run a check, say so plainly and say which one.
output format

State model

The Mermaid state diagram, then the transition table: from, event, guard, to, side effects, in or after transaction.

Open questions

Table: question, proposed default, implemented as.

Changes

One line per file.

Tests

One line per test group and the real result of the run.

Migration notes

How existing rows map to the new states, the data migration, and anything that needs a decision first.

1 required value still a placeholder; the assistant will ask for it.

details

kind
Prompt: a task you run by name to get one finished thing back
domain
Software engineering
category
Implementation
level
Intermediate
made for
Backend engineer, Software engineer, Full-stack engineer
needs
repo-read, file-write, shell
risk
runs-commands
version
v1.0.0 · incubating
reviewed
2026-10-02
works in
Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Antigravity, OpenCode, Windsurf, Zed, Continue, AGENTS.md

Edit on GitHubReport a problem

use in

Hodios CLI
npx @hermes-hq/hodios install implement-state-machine --target claude-code
Agent Skills
npx skills add hermes-hq/hodios-dist --skill implement-state-machine -a claude-code
Add the Hodios marketplace (once)
claude plugin marketplace add hermes-hq/hodios-dist
Install the software-engineering plugin
claude plugin install hodios-software-engineering@hodios

The plugin brings every entry in this domain at once.

pairs well with

All of Implementation
PersonaImplementation

Backend engineer

Acts as a backend engineer focused on correct data handling, clear API contracts, explicit failure modes and services that are easy to operate. Use as a builder or reviewer persona for server code.

backend-engineer
PromptData engineering

Design a relational database schema

Designs a relational schema from requirements and access patterns, with keys, constraints, types, indexes and DDL. Use when starting a new service or feature that stores data.

design-database-schema
PromptTesting

Write property-based tests

Finds the invariants a function must keep and writes property-based tests with generators that shrink well. Use when example-based tests miss edge cases in parsers, encoders or pure logic.

write-property-based-tests
PromptRefactoring

Extract a module

Moves one responsibility out of a large file or class into its own module in small, test-verified steps, without changing behaviour or the public API. Use when a file does too many things.

extract-module
PromptImplementation

Put a change behind a feature flag

Wraps new behaviour behind a feature flag with a safe default, a kill switch, tests for both paths and a cleanup ticket. Use when shipping a risky change incrementally.

add-feature-flag
PromptImplementation

Add rate limiting to an API

Adds rate limiting to API endpoints with a fitting algorithm, keys, per-tier limits, standard headers, 429 responses and tests. Use when protecting endpoints from abuse or overload.

add-rate-limiting