---
name: dispatch-state-machine
title: Dispatch State Machine
description: Per-KB state machine that escalates failing dispatches (active → escalating → dormant) with auto-probe and manual reset. Engine substrate, never injected into agent prompts.
category: _meta
status: active
author: system
metadata:
  priority: 0
---

# Dispatch State Machine (engine substrate)

This skill is **engine config**, not agent strategy. Its body is never injected into
agent system prompts. It exists so the per-KB dispatch failure-handling lives in YAML
(Rule 12) instead of hardcoded constants.

## What it does

Tracks per-KB ingest dispatch state in a SQLite table. Three states:

- **`active`** — happy path. Dispatches run normally.
- **`escalating`** — N consecutive zero-effects observed. Next retry runs at a
  higher model tier (default `reasoning`) so a more capable model gets a shot at
  the failing pattern.
- **`dormant`** — even escalation failed N times. Further dispatch attempts are
  REJECTED at the work-queue handler entry. The KB stays paused until either
  (a) the auto-probe (every N hours) succeeds, OR (b) the user manually resets
  the state via `POST /api/pipeline/dispatch-state/{slug}/reset`.

This closes the production failure mode observed on Wolf's vault on 2026-04-30:
the watcher emitted `watcher_stalled` HIGH events 17 times in 12 hours but the
system kept doing the same failing thing forever. With the state machine,
failures escalate (try harder), then surface as a user-actionable dormant state.

## Knobs (config/state.yaml)

- **`enabled`** (default `true`) — Master switch. When `false`, the state
  machine is a no-op (every transition becomes "stay active"). Useful for
  testing or for users who want raw retry behavior.

- **`zero_effect_threshold_active`** (default `3`) — Consecutive zero-effect
  outcomes that flip `active` → `escalating`. Lower = more sensitive (system
  escalates fast); higher = more tolerant of transient hiccups.

- **`zero_effect_threshold_escalating`** (default `3`) — Consecutive zero-effect
  outcomes IN `escalating` state that flip → `dormant`. The hard stop.

- **`escalating_tier`** (default `reasoning`) — Model tier passed to the agent
  via the work-queue payload's `escalate_tier` field when state is `escalating`.
  `reasoning` is the most capable tier; `capable` is one step down.

- **`dormant_probe_interval_hours`** (default `6`) — How often a dormant KB
  attempts auto-recovery. Each probe runs ONE escalating-tier attempt; success
  flips back to `active`, failure stays `dormant`.

- **`dormant_probe_tier`** (default `reasoning`) — Tier used for the auto-probe
  attempt. Same default as `escalating_tier`.

## Observability

Every state transition emits a structured `dispatch_state_transition` activity
event with:
- `slug`, `scope`, `from_state`, `to_state`
- `consecutive_zero_effects`, `consecutive_escalation_failures`
- `last_error_preview` (truncated to 200 chars)

`dormant` transitions fire at HIGH importance so the UI's PipelineCard surfaces
the state in its traffic light (Phase 4 work).

## Hard rule (non-negotiable)

When state is `dormant`, the work-queue handler MUST refuse to dispatch.
Watcher cycles can keep enqueuing — work-queue dedup collapses repeats to one
pending row per slug — but the handler immediately marks the job as deferred
without invoking the agent. The user must explicitly approve retry.

This is the architectural commitment: **"every state machine that gives up
MUST surface as a user-actionable signal, never a silent dormant state."**
