---
title: "Apply Plan Agent Route"
method: POST
path: "/api/v1/voice-agents/{voice_agent_id}/apply-plan/{run_id}"
tags: ["flywheel"]
deprecated: true
---

# Apply Plan Agent Route

`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

> **Deprecated.**

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

## Path parameters

- `voice_agent_id` string, uuid, required
- `run_id` string, uuid, required

## Response `200`

Successful Response

- ApplyPlanResponse: V1.1a ``POST /apply-plan/{run_id}`` response. ``candidate_versions`` is uniformly a list (single-element for agent-subject and V1.1a's narrowed group-subject path). Future multi-agent group plans grow the list without a shape change.
  - `run_id` string, 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.
  - `candidate_versions` CandidateVersionEntry[], required
    - `voice_agent_id` string, uuid, required
    - `version_id` string, uuid, required
    - `candidate_agent_id` string, uuid, required

## Other responses

- `400`: Apply blocked: subject ineligible, run does not target this URL's subject, or run is not in status 'plan_ready'
- `404`: FlywheelRun not found in this tenant. Distinct from 400 on subject mismatch so cross-tenant callers cannot enumerate run IDs by diffing error codes.
- `422`: Plan re-validation failed at apply time — the agent's metadata_json drifted between preview and apply. Operator remediation: re-run preview against the current state.

---

[API](https://skmtc.dev/getanana/apis/cleon-api.md) · [All operations](https://skmtc.dev/getanana/apis/cleon-api/llms.txt) · [OpenAPI document](https://skmtc.dev/getanana/apis/cleon-api/revisions/4d9d38769a75?raw)
