Skip to content

Control an FSM ​

Yes—you can define and adjust the nodes, edges, guards, retry policy and timeouts of an application-owned FSM. Hypha intentionally protects the separate ReAct Harness FSM.

Define the topology ​

ts
import {
  analyzeFSMTopology,
  applyTransition,
  createInitialSnapshot,
  parseFSMProcessSpec,
} from '@codesoul-co/hypha-fsm';

const process = parseFSMProcessSpec({
  id: 'workflow.publication',
  version: '1.0.0',
  name: 'Publication workflow',
  initialState: 'Draft',
  terminalStates: ['Published', 'Rejected'],
  states: [
    { id: 'Draft', kind: 'domain' },
    { id: 'TechnicalReview', kind: 'domain', retryPolicy: { maxAttempts: 2 } },
    { id: 'Published', kind: 'completed' },
    { id: 'Rejected', kind: 'failed' },
  ],
  transitions: [
    { from: 'Draft', to: 'TechnicalReview' },
    { from: 'TechnicalReview', to: 'Published', guard: 'review.approved' },
    { from: 'TechnicalReview', to: 'Rejected', guard: 'review.rejected' },
  ],
});

const analysis = analyzeFSMTopology(process);
if (analysis.unreachableStates.length > 0) {
  throw new Error(`Unreachable: ${analysis.unreachableStates.join(', ')}`);
}

let snapshot = createInitialSnapshot(process, 'run-publication-1');
snapshot = applyTransition(process, snapshot, 'TechnicalReview');

Generate it from a Domain Pack ​

The release example maps the selected WorkflowSpec into a separate process:

ts
function buildApplicationWorkflowFSM(workflow: WorkflowSpec) {
  return parseFSMProcessSpec({
    id: workflow.id,
    version: workflow.version,
    name: workflow.name,
    initialState: workflow.initialState,
    terminalStates: workflow.terminalStates,
    states: workflow.states.map((state) => ({
      id: state.id,
      kind: workflow.terminalStates.includes(state.id) ? 'completed' : 'domain',
      retryPolicy: state.retryPolicy,
      timeoutPolicy: state.timeoutPolicy,
      policyRefs: state.policyRefs,
    })),
    transitions: workflow.transitions.map(({ from, to, guard, description }) => ({
      from, to, guard, description,
    })),
  });
}

What you may adjust ​

AreaApplication FSMHarness ReAct FSM
Node names and product statesYesNo
Allowed edgesYesNo
Guards and policy referencesYesBind policy; do not bypass lifecycle
Retry/timeout declarationsYesConfigure through supported Harness specs
Terminal outcomesYesKeep framework terminal semantics
Direct mutation of current stateNoNo

Runtime concurrency ​

For an actual Server Run, do not apply transitions independently in several clients. Read the current revision, then submit the target transition with owner/revision evidence. The runtime uses revision/fencing semantics to reject stale movement and records accepted movement as Events.

Tests to keep ​

  1. Parsing rejects invalid nodes/edges.
  2. Topology has no unintended unreachable or dead-end state.
  3. Guard denial does not advance revision/state.
  4. Every accepted edge emits the expected evidence.
  5. Replay produces the same final snapshot.

See the runnable run-fsm.ts example.