Introduction
AI agents are often described as loops: observe, reason, act, repeat. That is useful for explaining agent behavior, but it is not enough for a production workflow.
A real system also needs to answer questions such as: Is the agent planning or executing? Has validation passed? Is human approval required? Did execution fail? Can the operation be retried safely? Can the workflow resume after a process restart?
A state machine gives those questions an explicit model. Instead of inferring lifecycle from scattered Boolean fields or log messages, the application records a current state and allows only defined transitions.
This article builds a small framework-neutral example in C# and shows how to separate AI reasoning from workflow authority.
Environment and prerequisites
Language: C# 12
Runtime target: .NET 8
Framework: None required; the example uses core C# only
Code status: Illustrative code — technical validation required.
Prerequisites: basic familiarity with C# enums, classes, switch expressions, application services, and persistent storage.
By the end, you should be able to model an agent lifecycle, reject illegal transitions, represent approval explicitly, handle retries and failures, and keep the model from deciding its own execution authority.
Define lifecycle state explicitly
Start by naming the states that matter to the workflow.
The following enum describes a simple lifecycle from creation through planning, validation, approval, execution, and completion. It also includes failure and cancellation paths.
public enum AgentWorkflowState
{
// Normal lifecycle states.
Created,
Planning,
Validated,
AwaitingApproval,
Approved,
Executing,
RetryPending,
Completed,
// Exception and terminal states.
ValidationFailed,
ExecutionFailed,
Rejected,
Cancelled
}
The enum answers one question: Which states can this workflow occupy?
It does not yet answer which transitions are legal. Without transition rules, application code could accidentally jump directly from Created to Completed or move a rejected workflow back into execution.
Encode legal transitions
A small transition policy can make the lifecycle explicit.
This example uses a C# switch expression over a tuple containing the current and requested states.
public static class WorkflowTransitions
{
public static bool CanTransition(
AgentWorkflowState current,
AgentWorkflowState next)
{
return (current, next) switch
{
(AgentWorkflowState.Created,
AgentWorkflowState.Planning) => true,
(AgentWorkflowState.Planning,
AgentWorkflowState.Validated) => true,
(AgentWorkflowState.Planning,
AgentWorkflowState.ValidationFailed) => true,
(AgentWorkflowState.Validated,
AgentWorkflowState.AwaitingApproval) => true,
(AgentWorkflowState.AwaitingApproval,
AgentWorkflowState.Approved) => true,
(AgentWorkflowState.AwaitingApproval,
AgentWorkflowState.Rejected) => true,
(AgentWorkflowState.Approved,
AgentWorkflowState.Executing) => true,
(AgentWorkflowState.Executing,
AgentWorkflowState.Completed) => true,
(AgentWorkflowState.Executing,
AgentWorkflowState.ExecutionFailed) => true,
(AgentWorkflowState.ExecutionFailed,
AgentWorkflowState.RetryPending) => true,
(AgentWorkflowState.RetryPending,
AgentWorkflowState.Executing) => true,
// Deny every transition that is not explicitly listed.
_ => false
};
}
}
The final fallback is important. New or unexpected state pairs are denied unless they are added deliberately.
That produces a useful default: a transition is illegal unless the application explicitly allows it.
Keep transitions behind the workflow object
A transition policy is only useful if callers cannot bypass it.
The workflow object can expose state as read-only and require changes to go through one method.
using System;
public sealed class AgentWorkflow
{
public Guid Id { get; } = Guid.NewGuid();
// State changes only through TransitionTo.
public AgentWorkflowState State { get; private set; }
= AgentWorkflowState.Created;
public int RetryCount { get; private set; }
public void TransitionTo(AgentWorkflowState next)
{
if (!WorkflowTransitions.CanTransition(State, next))
{
throw new InvalidOperationException(
$"Transition from {State} to {next} is not allowed.");
}
State = next;
}
public void RegisterRetry()
{
RetryCount++;
}
}This keeps the state mutation in one place. A larger implementation would normally record more context, such as the previous state, timestamp, actor identity, reason, error details, and an operation ID.
Treat approval as a real state
A common shortcut is to add a flag such as bool IsApproved.
That works until approval develops a lifecycle of its own. A workflow may be waiting for review, approved, rejected, expired, or cancelled. It may also need to record who approved a specific version.
Representing approval as explicit state makes queries and rules clearer. For example, a queue can fetch workflows in AwaitingApproval without inferring that condition from several fields.
It also prevents a model from silently turning “I think this is ready” into “this is authorized to execute.”
Model failure and retry separately
Failures are part of the workflow, not always exceptional programming defects.
An external service can be unavailable. Validation can reject a proposal. A human reviewer can reject an action. A retry limit can be reached.
That is why ExecutionFailed and RetryPending are separate states in the example.
A retry rule might start simply:
public static class RetryPolicy
{
public static bool CanRetry(
int retryCount,
int maxRetries,
bool isIdempotent)
{
// Retry only when repeating the operation is safe.
return isIdempotent && retryCount < maxRetries;
}
}This code adds one important constraint: retry only when the operation is safe to repeat.
In a real system, retry policy may also depend on error type, HTTP status, elapsed time, provider health, backoff rules, or an idempotency key. A failed operation that creates external side effects should not be retried blindly.
Persist state for long-running workflows
Agent workflows often outlive one process invocation. Human approval may take minutes or hours, and external dependencies may fail temporarily.
Persisting workflow state makes recovery possible after application restarts, queue interruptions, worker crashes, or delayed approval.
A stored record might include:
WorkflowId
CurrentState
PreviousState
RetryCount
ProposedAction
ApprovedBy
CreatedAt
UpdatedAt
LastError
OperationId
ConcurrencyVersion
The exact schema depends on the application, but the principle is stable: lifecycle state should not exist only in process memory if the workflow must survive process failure.
Concurrency matters too. Two workers should not both read Approved, execute the same action, and independently mark it complete. Optimistic concurrency, a compare-and-set update, or another single-writer strategy can prevent stale transitions.
Do not let the model own state transitions
The most important boundary is architectural.
A model may propose:
“The task is ready for execution.”
That statement is input to the workflow, not permission.
Application code should decide whether Validated -> AwaitingApproval or Approved -> Executing is allowed. The decision can depend on authorization, policy, risk, data freshness, approval state, cost, or other deterministic checks.
The model contributes reasoning. The workflow engine retains authority.
Security and validation checks
Before allowing a transition, validate more than the current state.
For example, an AwaitingApproval -> Approved transition may need to verify:
the caller is authenticated;
the caller has the required role;
the workflow belongs to the caller's tenant or project;
the approved artifact version matches the artifact that will execute;
required evidence is still current;
the action is reversible or has an approved recovery path;
the transition has not already been applied.
The state machine answers “Is this transition structurally legal?” A policy layer answers “Is this actor allowed to perform it under current conditions?”
Those responsibilities should remain separate.
A practical validation checklist
Before treating a workflow state machine as production-ready, verify:
every normal path has explicit transitions;
rejection and cancellation paths are represented;
failure states distinguish retryable from terminal outcomes;
approval references the exact artifact or action version;
retries are bounded and safe for side effects;
workflow state is durably persisted;
concurrent workers cannot execute the same transition twice;
authorization is checked independently from model output;
transitions are logged with actor, reason, and timestamp;
automated tests cover legal and illegal transitions.
These tests are especially valuable because a state machine is mostly a set of invariants. It should be straightforward to prove that legal paths succeed and illegal paths are rejected.
Where Ranknod fits
Ranknod is being built as a governed AI operating system for agencies and growth teams. An explicit workflow-state model is relevant to that design direction because governed execution needs to distinguish between proposed, validated, approved, executing, failed, verified, and completed work.
That is design context, not a claim that the exact C# implementation in this article is deployed inside Ranknod.
Summary
AI agent workflows become easier to reason about when lifecycle state is explicit.
The useful separation is:
AI reasoning
workflow state
execution authority
A state machine handles the middle layer. It records where the work is, limits where it may go next, and gives approvals, failures, retries, and recovery paths a concrete place in the architecture.
For production use, extend the basic pattern with durable persistence, concurrency control, idempotency, authorization, audit events, and automated transition tests.
The model can recommend the next action. The application should decide whether that action is allowed.
Join the conversation! Your thoughts help the community grow.