flywheel

Preview Plan Agent Route

Flywheel V1.1a — start a cycle on a standalone voice agent.

Creates a new FlywheelRun (status=DRAFT, intent populated) and its cycle-scoped FDEConversation, transitions to AGGREGATING, runs the aggregator's build_plan step, and returns either a plan_ready response with proposed edits OR a dismissed response with empty plans (no findings, empty plan, LLM failure, or validation failure — distinguished by aggregator_outcome on the run row, queryable via GET /flywheel-runs).

post/api/v1/voice-agents/{voice_agent_id}/preview-plan

Path parameters

voice_agent_idstring uuid required

Request body

intentstring required

Operator-supplied free-text intent for the cycle. Max 500 chars (matches FlywheelRun.intent column width). Empty string accepted (rare; V1.5b cold-open path).

Response

Successful Response

run_idstring uuid required
conversation_idstring uuid required
status'draft' | 'aggregating' | 'plan_ready' | 'applied' | 'sim_running' | 'sim_passed' | 'sim_failed' | 'promoted' | 'rolled_back' | 'dismissed' required

Cycle state machine for FlywheelRun (Flywheel V1.0 cockpit).

Single source of truth for cockpit mode (readonly ↔ editing), promote-button gating, and rail-card vocabulary. Per V1 invariant #3 — never derived from aggregator_outcome; the column is the canonical signal. aggregator_outcome remains as the V0.6/V0.7 audit detail (per V1 invariant #7 — additive over renaming); V1.9 deprecates it at the Pydantic layer.

Allowed transitions (enforced in tests, not in DB):

DRAFT → AGGREGATING → PLAN_READY → APPLIED → SIM_RUNNING
      → (SIM_PASSED | SIM_FAILED) → PROMOTED → ROLLED_BACK
DRAFT | PLAN_READY | APPLIED | SIM_RUNNING
      | SIM_PASSED | SIM_FAILED → DISMISSED

Lifecycle:

  • DRAFT: row created at intent-confirm; no plan yet.
  • AGGREGATING: aggregator running.
  • PLAN_READY: FDE produced a PromptEditPlan; not applied.
  • APPLIED: candidate VoiceAgentVersion written.
  • SIM_RUNNING: simulator kicked off, awaiting webhook.
  • SIM_PASSED / SIM_FAILED: webhook reported verdict.
  • PROMOTED: candidate flipped to prod active_version_id.
  • ROLLED_BACK: a prior promote was reverted in a later cycle.
  • DISMISSED: operator closed the cycle without promoting.

Column-width note: the underlying VARCHAR is VARCHAR(32) (set in the V1.0 migration via explicit ALTER COLUMN ... TYPE VARCHAR(32)), even though SQLAlchemy's Enum(native_enum=False) auto-derives VARCHAR(11) from the longest member name ("aggregating"). Same drift pattern as AggregatorOutcome after V0.7.1 (see migration d4a8b3c5e7f1). The next engineer who runs alembic revision --autogenerate will see SQLAlchemy propose narrowing back to 11 — the column comment on FlywheelRun.status explains why the override is intentional.

findings_citedstring[] required

Changes