flywheel

Apply Plan Agent Route

Flywheel V1.1a — advance a PLAN_READY agent-subject run to APPLIED.

Re-validates the stored plan against the prod agent's CURRENT metadata; drift between preview and apply surfaces here as a 422 response (per V1.1a's design decision §3 — the operator re-runs preview to get a fresh plan).

post/api/v1/voice-agents/{voice_agent_id}/apply-plan/{run_id}

Request

  • Base URL: https://api.getanana.com (the document declares no server; this is the origin it was published from)
  • URL: https://api.getanana.com/api/v1/voice-agents/{voice_agent_id}/apply-plan/{run_id}
  • Auth: none declared

Path parameters

voice_agent_idstring uuid required
run_idstring uuid required

Response

Successful Response

run_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.

Changes

No operation changes recorded.