Skip to main content

Step Watcher

StepWatcher is the conversation state machine. It manages the current step, tracks timers and round counts, handles branching, and returns a StepDecision after each user turn. Defined in lib/agent/step-watcher.ts.

StepDecision

type StepAction = "continue" | "advance" | "end" | "webhook" | "evaluate"

type StepDecision = {
  action: StepAction
  instructions?: string    // Text for the next step
  isLiteral?: boolean      // Speak verbatim vs. use as LLM instruction
  step?: ConversationStep  // The newly active step (after advance)
  webhookUrl?: string      // For webhook/end actions
  evaluationStep?: ConversationStep  // For evaluate action
}

Lifecycle

begin()

Called at session start. Finds the Start step, sets it as current, starts any timers, and returns { action: "advance" } with the opening instruction.

onUserTurn()

Called after every user message. Returns a StepDecision based on the current step type:
Step typeDecision logic
startAdvance to next step
burstIncrement round count; advance when rounds complete
timerCheck elapsed time; advance if duration exceeded
opentalkContinue; return evaluate if next is If/Else or Categorize
ifelseReturn evaluate
categorizeReturn evaluate
endReturn end (peek for webhook)
webhookReturn webhook

advanceToBranch(evaluationStep, branchKey)

Called by the agent after an LLM evaluation resolves a branch. Takes the evaluation step and the selected branch key ("true"/"false" or "cat_0", "cat_1", etc.) and advances to the corresponding next step.

Step progression for burst

User speaks → onUserTurn() → roundsCompleted++
  → if rounds < target: return { action: "continue" }
  → if rounds >= target: return { action: "advance", step: nextStep }

Branching

User speaks → onUserTurn() returns { action: "evaluate", evaluationStep }
Agent runs LLM evaluation
Agent calls advanceToBranch(evaluationStep, "true")
  → looks up nextStepIds["true"] → finds next step ID
  → returns { action: "advance", step: resolvedStep }

Timer behavior

When a Timer step starts, StepWatcher records stepStartedAt and sets a JavaScript timeout. If the timer expires before the user triggers advancement, the step auto-advances on the next user turn check.