---
title: "Get Active Cycle Group Route"
method: GET
path: "/api/v1/voice-agent-groups/{voice_agent_group_id}/active-cycle"
tags: ["flywheel"]
---

# Get Active Cycle Group Route

`GET /api/v1/voice-agent-groups/{voice_agent_group_id}/active-cycle`

Flywheel V1.3 — cockpit-mount payload for a voice agent group.

Same shape as the agent variant. ``touched_agent_ids`` is populated
from the distinct ``VoiceAgentVersion.voice_agent_id`` set produced
by the run so the group cockpit can render dot badges on member
tabs and auto-focus the first touched tab on mount.

## Path parameters

- `voice_agent_group_id` string, uuid, required

## Response `200`

Successful Response

- ActiveCycleResponse — V1.3 — minimal cockpit-mount payload for ``GET .../active-cycle``. Distinct from ``FlywheelRunRead`` (the full DB row). The cockpit only needs five fields at mount time to decide readonly-vs-editing + wire the chat pane: - ``run_id`` / ``status`` / ``intent`` / ``subject`` drive ``ModeBadge`` (status ↔ pill) and the cycle-aware header copy. - ``conversation_id`` is the cycle-scoped ``FDEConversation`` the ``ChatPane`` streams into. V1.1a's ``preview_plan`` guarantees one exists per non-terminal run; the helper asserts it. - ``touched_agent_ids`` is empty for agent subjects (the run's own ``voice_agent_id`` is the only agent and is on the run row anyway). For group subjects it is the distinct set of ``VoiceAgentVersion.voice_agent_id`` values produced by the run — V1.3's group cockpit reads it to render dot badges on member-agent tabs and to auto-focus the first touched tab on mount. Other ``FlywheelRun`` fields (``sim_passed``, ``promoted_at``, etc.) come from ``GET /flywheel-runs?limit=N`` per the V0.8 list-runs pattern — the cockpit doesn't need them at the active-cycle moment, only at promote-modal / rail render time. ``plans`` is populated so the cockpit can render prompt diffs without a separate fetch. Populated from ``prompt_edit_plan`` JSONB when the run has reached ``PLAN_READY`` or later; empty list for ``DRAFT`` / ``AGGREGATING`` (plan not yet computed).
  - `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.
  - `intent` string, nullable, required
  - `conversation_id` string, uuid, required
  - `subject` SubjectRead, required — Subject of a Flywheel cycle (V1.1a). Mirrors the ``Subject`` discriminated dataclass used internally — a cycle is scoped to either a standalone ``VoiceAgent`` or a ``VoiceAgentGroup``. The XOR is enforced at the DB layer via ``ck_flywheel_runs_subject_xor`` on ``flywheel_runs``.
    - `kind` 'agent' | 'group', required
    - `id` string, uuid, required
  - `touched_agent_ids` string[], required
  - `plans` PlanEntry[]
    - `voice_agent_id` string, uuid, required
    - `plan` PromptEditPlan, required — The LLM engineer's top-level output: a list of edits + rationale. An empty ``edits`` list is a valid answer — "none of these findings merit an edit." The aggregator treats empty plans as ``SKIPPED_EMPTY_PLAN`` (not ``LLM_FAILED``), leaves findings open, and persists the plan (overall_rationale included) for debugging. ``overall_rationale`` is always required — on the empty-plan path it's the LLM's explanation of why no edit was warranted; on the non-empty path it's an overview the promotion reviewer reads before approving the candidate.
      - `edits` union[], required
        - union
          - EditBaseInstructions — Overwrite the agent's top-level base-prompt content. Targets ``metadata_json["_cached_update_request"]["system_prompt"]["base_prompt"]["content"]`` — the freeform instructions that apply regardless of active scenario.
            - `kind` 'edit_base_instructions', required
            - `new_instructions` string, required
            - `addresses_finding_ids` string[], required
            - `rationale` string, required
          - EditScenarioInstruction — Overwrite a single string-typed instruction field on a scenario. Three string fields on ``ScenarioBase`` are editable (see ``voice_ai/public/scenario_models.py:447, 467, 474``). ``tool_messages`` is deliberately excluded — it's ``ToolMessageConfig | None`` (a structured object, not a string). V0.6 keeps edits minimal; a V1 ``EditToolMessages`` variant can land when finding volume justifies it. Prompt-delimiter safety: ``new_text`` MUST NOT contain ``##`` or newline characters. ``ScenarioBase``'s field validators at ``scenario_models.py:498-532`` reject both; ``apply_and_validate`` catches violations as ``validation_failed``. The system prompt tells the LLM about this constraint to reduce retry loops.
            - `kind` 'edit_scenario_instruction', required
            - `scenario_id` string, uuid, required
            - `field` 'pre_execution_instruction' | 'post_execution_instruction' | 'condition', required
            - `new_text` string, required
            - `addresses_finding_ids` string[], required
            - `rationale` string, required
          - AdjustPreset — Merge structured updates into a scenario's ``ParameterPreset``. ``preset_updates`` is a dict whose keys are restricted to the four allowed ``ParameterPreset`` subfields: ``defaults``, ``fixed``, ``required``, ``allowed``. Any other top-level key is rejected by ``apply_and_validate`` as a hallucination (prevents an LLM from inventing preset sections that don't exist in the schema). Values for each subfield are shallow-merged into the existing preset — i.e. ``preset.defaults.update(preset_updates["defaults"])`` — so individual keys can be overridden without clobbering the full dict. ``ParameterPreset``'s own pydantic validators (e.g. "``allowed`` must not conflict with ``fixed``") fire during the post-apply ``ScenarioBase.model_validate`` step.
            - `kind` 'adjust_preset', required
            - `scenario_id` string, uuid, required
            - `preset_updates` object, required
            - `addresses_finding_ids` string[], required
            - `rationale` string, required
      - `overall_rationale` string, required

## Other responses

- `422` — Validation Error

## Changes

- **2026-09-03** `d65b8fefae34` — 2 info
  - added the optional property `detail/items/ctx` to the response with the `422` status
  - added the optional property `detail/items/input` to the response with the `422` status

[Change history](https://skmtc.dev/getanana/apis/cleon-api/changes/api/v1/voice-agent-groups/:voice_agent_group_id/active-cycle/get.md)

---

[API](https://skmtc.dev/getanana/apis/cleon-api.md) · [All operations](https://skmtc.dev/getanana/apis/cleon-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/getanana/cleon-api/revisions/38166468bfc5/schema)
