feat(web): warn about workflow replay cycles (#1720)
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
---
|
||||
spec: docs/specs/workflow-cycle-guardrails/spec.md
|
||||
created: 2026-07-15
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Implementation Plan: Workflow Cycle Guardrails
|
||||
|
||||
## Overview
|
||||
|
||||
Add a deterministic frontend graph analyzer first, then place a shared mutation
|
||||
guard in the workflow card so both draft creation and existing immediate step
|
||||
mutations use the same proposed-shape decision. Build the trace UI on that
|
||||
contract and finish with desktop and mobile Playwright coverage. The existing
|
||||
workflow HTTP contracts and runtime engine remain unchanged.
|
||||
|
||||
---
|
||||
|
||||
## Backend
|
||||
|
||||
No backend changes are planned. The feature is an authoring-time guard over the
|
||||
complete client-visible `WorkflowStep[]`; it intentionally does not make replay
|
||||
cycles a persistence invariant or alter `POST /api/v1/workflow/steps`,
|
||||
`PUT /api/v1/workflow/steps/:id`, step deletion/reorder, or workflow import.
|
||||
|
||||
Relevant contracts verified during planning:
|
||||
|
||||
- `apps/backend/internal/workflow/models/models.go` defines `on_enter`,
|
||||
`on_turn_start`, and `on_turn_complete` action semantics.
|
||||
- `apps/backend/internal/workflow/engine/types.go` resolves the same three move
|
||||
action kinds and applies first-eligible-transition behavior.
|
||||
- `apps/backend/internal/orchestrator/event_handlers_workflow.go` confirms that
|
||||
`on_turn_start` is user-message driven and an entered `auto_start_agent` step
|
||||
starts the workflow prompt.
|
||||
- `apps/backend/internal/orchestrator/task_operations.go` defines the three
|
||||
prompt-source cases that the diagnostic describes.
|
||||
|
||||
---
|
||||
|
||||
## Frontend
|
||||
|
||||
### Replay-cycle analyzer
|
||||
|
||||
Files:
|
||||
|
||||
- `apps/web/lib/workflows/replay-cycle-analysis.ts` (new)
|
||||
- `apps/web/lib/workflows/replay-cycle-analysis.test.ts` (new)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add `analyzeWorkflowReplayCycles(steps: WorkflowStep[])` and exported
|
||||
diagnostic/trace types matching the spec's Analysis Contract.
|
||||
- Normalize order by `position`, resolve `move_to_next`,
|
||||
`move_to_previous`, and valid `move_to_step` targets for both kanban
|
||||
triggers, and find paths that return to each `on_enter.auto_start_agent`
|
||||
step through an `on_turn_complete` replay edge. Treat `on_turn_start` entry
|
||||
into an auto-start step as safe because the runtime skips destination
|
||||
`on_enter` while dispatching the user's message.
|
||||
- Classify a hop as automatic when its source auto-starts on entry and the
|
||||
transition does not require approval. This includes `on_turn_start`
|
||||
transitions fired by an auto-started prompt. A cycle is blocking only when
|
||||
all of its hops are automatic.
|
||||
- Produce stable diagnostic identities, affected step IDs, a deterministic
|
||||
highest-severity/shortest trace per auto-start step, and the prompt-source
|
||||
category.
|
||||
- Ignore unresolved targets and cycles without an auto-start re-entry.
|
||||
|
||||
### Proposed-shape mutation guard
|
||||
|
||||
Files:
|
||||
|
||||
- `apps/web/components/settings/workflow-card-actions.ts`
|
||||
- `apps/web/components/settings/workflow-card-actions.test.ts`
|
||||
- `apps/web/components/settings/workflow-card.tsx`
|
||||
|
||||
Changes:
|
||||
|
||||
- Introduce a workflow-card guard state that compares analyzer diagnostics for
|
||||
the current server-backed shape and a proposed shape.
|
||||
- Route existing step update, add, remove, and reorder handlers through a
|
||||
single guard before their current API call. Build proposed arrays with the
|
||||
same step update/start-step demotion, add, delete/reindex, and reorder
|
||||
semantics already used by the handlers.
|
||||
- For a new draft, run the guard in `handleSaveWorkflow` before
|
||||
`createWorkflowAction` so cancellation sends neither the workflow create nor
|
||||
any follow-up step mutations.
|
||||
- Hold one pending user-mediated operation until confirmation; execute it once
|
||||
on **Apply anyway** or **Create anyway**, and discard it on cancel.
|
||||
- Reject fully automatic proposals without exposing an override. Compare a
|
||||
bounded identity inventory so alternate cycles are not hidden by the
|
||||
preferred display trace, and fall back conservatively when that inventory is
|
||||
exhausted. Always allow changes that remove diagnostics.
|
||||
- Preserve current request errors, temp-step remapping, optimistic reorder
|
||||
feedback, and workflow creation sequencing. Reconcile successful remote
|
||||
mutations from their authoritative responses and roll failed reorders back.
|
||||
|
||||
### Diagnostic presentation
|
||||
|
||||
Files:
|
||||
|
||||
- `apps/web/components/settings/workflow-cycle-diagnostic.tsx` (new)
|
||||
- `apps/web/components/settings/workflow-pipeline-editor.tsx`
|
||||
- `apps/web/components/settings/workflow-card.tsx`
|
||||
|
||||
Changes:
|
||||
|
||||
- Add a reusable diagnostic view for the inline alert and the confirmation or
|
||||
blocking dialog. Render severity, ordered trace, user-mediated hops, and the
|
||||
prompt-source explanation without rendering prompt contents.
|
||||
- Place the inline alert above the pipeline's horizontal scroll area and pass
|
||||
affected step IDs into pipeline nodes for icon/text plus border treatment,
|
||||
so affected steps are not communicated by color alone.
|
||||
- Use an `AlertDialog` for a pending proposal. Blocking diagnostics provide
|
||||
only a return action; warning diagnostics provide Cancel plus the context
|
||||
appropriate **Apply anyway** or **Create anyway** action.
|
||||
- Keep dialog actions and the full trace keyboard accessible. At narrow widths,
|
||||
stack trace hops and actions, wrap long names, maintain touch-sized controls,
|
||||
and prevent horizontal page overflow while leaving the pipeline's contained
|
||||
horizontal scrolling intact.
|
||||
|
||||
No API-client or Zustand state changes are required; workflow cards already own
|
||||
their loaded step arrays and mutation callbacks.
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
- **What:** transition resolution for next, previous, and explicit targets on
|
||||
both triggers, including reordered positions and dangling targets.
|
||||
**File:** `apps/web/lib/workflows/replay-cycle-analysis.test.ts`
|
||||
**How:** table-driven pure unit tests.
|
||||
|
||||
- **What:** fully automatic cycles are blocking only when every source step
|
||||
auto-starts and every hop is unapproved `on_turn_complete`.
|
||||
**File:** `apps/web/lib/workflows/replay-cycle-analysis.test.ts`
|
||||
**How:** graph fixtures that vary auto-start, trigger, and approval config.
|
||||
|
||||
- **What:** user-mediated cycles, generic cycles without auto-start, stable
|
||||
diagnostic identities, deterministic trace selection, and all three prompt
|
||||
sources.
|
||||
**File:** `apps/web/lib/workflows/replay-cycle-analysis.test.ts`
|
||||
**How:** focused unit fixtures.
|
||||
|
||||
- **What:** the built-in Review `on_turn_start` feedback edge into In Progress
|
||||
does not warn or replay auto-start behavior.
|
||||
**File:** `apps/web/lib/workflows/replay-cycle-analysis.test.ts`
|
||||
**How:** regression fixture matching the built-in Kanban transition shape.
|
||||
|
||||
- **What:** an existing step edit that introduces a blocking cycle does not call
|
||||
`updateWorkflowStepAction`; a warning holds the call until confirmation and
|
||||
calls it exactly once; cancel does not call it.
|
||||
**File:** `apps/web/components/settings/workflow-card-actions.test.ts`
|
||||
**How:** mocked action tests around the guard/controller helper extracted from
|
||||
`useWorkflowStepActions`.
|
||||
|
||||
- **What:** add, remove, and reorder use proposed full shapes and existing
|
||||
diagnostics are not repeatedly confirmed.
|
||||
**File:** `apps/web/components/settings/workflow-card-actions.test.ts`
|
||||
**How:** mocked action tests with baseline/proposed diagnostic identities.
|
||||
|
||||
- **What:** draft creation is blocked before `createWorkflowAction`, warning
|
||||
cancellation sends no requests, and confirmation runs the existing creation
|
||||
sequence once.
|
||||
**File:** `apps/web/components/settings/workflow-card-actions.test.ts`
|
||||
**How:** extend the current new-workflow save tests.
|
||||
|
||||
- **What:** the inline and dialog variants render the exact trace, prompt source,
|
||||
affected-step accessibility text, and correct action labels.
|
||||
**File:** `apps/web/components/settings/workflow-cycle-diagnostic.test.tsx`
|
||||
**How:** Vitest/Testing Library component tests.
|
||||
|
||||
---
|
||||
|
||||
## E2E Tests
|
||||
|
||||
- **Scenario:** a persisted existing-workflow edit introduces a user-mediated
|
||||
return path to an auto-start step; no PUT is observable until **Apply
|
||||
anyway**, and the inline alert remains after persistence.
|
||||
**File:** `apps/web/e2e/tests/workflow/workflow-cycle-guardrails.spec.ts`
|
||||
**What to verify:** exact step/trigger trace, task-description prompt source,
|
||||
cancellation behavior, confirmation, and persisted events through the API
|
||||
helper.
|
||||
|
||||
- **Scenario:** a draft workflow contains a fully automatic cycle; saving is
|
||||
blocked and no workflow is created.
|
||||
**File:** `apps/web/e2e/tests/workflow/workflow-cycle-guardrails.spec.ts`
|
||||
**What to verify:** blocking severity, no override action, and workflow absent
|
||||
after reload/API list.
|
||||
|
||||
- **Scenario:** a cycle without auto-start remains allowed.
|
||||
**File:** `apps/web/e2e/tests/workflow/workflow-cycle-guardrails.spec.ts`
|
||||
**What to verify:** no diagnostic and normal persistence.
|
||||
|
||||
- **Scenario:** at a mobile viewport, a user-mediated draft warning exposes the
|
||||
complete trace and prompt source and can be cancelled/confirmed by touch
|
||||
without horizontal page overflow.
|
||||
**File:** `apps/web/e2e/tests/workflow/mobile-workflow-cycle-guardrails.spec.ts`
|
||||
**What to verify:** touch-reachable dialog actions, wrapped/visible trace,
|
||||
successful confirmation, and document width not exceeding viewport width.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Waves
|
||||
|
||||
Wave 1:
|
||||
|
||||
- [x] [task-01-cycle-analyzer](task-01-cycle-analyzer.md) (done)
|
||||
|
||||
Wave 2:
|
||||
|
||||
- [x] [task-02-mutation-guard](task-02-mutation-guard.md) (done)
|
||||
|
||||
Wave 3:
|
||||
|
||||
- [x] [task-03-diagnostic-ui](task-03-diagnostic-ui.md) (done)
|
||||
|
||||
Wave 4:
|
||||
|
||||
- [x] [task-04-e2e-verification](task-04-e2e-verification.md) (done)
|
||||
|
||||
---
|
||||
|
||||
## Verification Commands
|
||||
|
||||
Format first:
|
||||
|
||||
```bash
|
||||
make fmt
|
||||
```
|
||||
|
||||
Focused frontend unit tests:
|
||||
|
||||
```bash
|
||||
(cd apps && pnpm --filter @kandev/web test -- lib/workflows/replay-cycle-analysis.test.ts components/settings/workflow-card-actions.test.ts components/settings/workflow-cycle-diagnostic.test.tsx)
|
||||
```
|
||||
|
||||
Frontend typecheck and lint:
|
||||
|
||||
```bash
|
||||
(cd apps/web && pnpm run typecheck)
|
||||
(cd apps && pnpm --filter @kandev/web lint)
|
||||
```
|
||||
|
||||
Focused desktop and mobile E2E (rebuilds the production web/backend artifacts
|
||||
through the repo E2E harness):
|
||||
|
||||
```bash
|
||||
(cd apps/web && pnpm e2e:run --host tests/workflow/workflow-cycle-guardrails.spec.ts)
|
||||
(cd apps/web && pnpm e2e:run --host --no-build --project mobile-chrome tests/workflow/mobile-workflow-cycle-guardrails.spec.ts)
|
||||
```
|
||||
|
||||
Full repository verification:
|
||||
|
||||
```bash
|
||||
make typecheck test lint
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
- `move_to_next` and `move_to_previous` change targets when steps are reordered;
|
||||
every topology-changing handler must analyze its proposed array, not only
|
||||
event updates.
|
||||
- Existing step controls issue remote mutations immediately and some maintain
|
||||
local debounced input. The pending-operation guard must avoid optimistic UI
|
||||
that disagrees with the server after cancel or failure.
|
||||
- The backend engine supports additional generic triggers and configurations.
|
||||
The diagnostic must state its two-trigger scope and tests must prevent the UI
|
||||
from implying that every possible workflow event has been analyzed.
|
||||
- New workflow persistence is a multi-request sequence. Preflighting before the
|
||||
first request is required so a cancelled warning cannot leave a partial
|
||||
workflow.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None for this implementation. The first iteration deliberately guards the
|
||||
kanban-era `on_turn_start` and `on_turn_complete` paths agreed in the feature
|
||||
spec and leaves direct API/import enforcement for separate work.
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: "01-cycle-analyzer"
|
||||
title: "Workflow replay cycle analyzer"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/workflow-cycle-guardrails/spec.md"
|
||||
---
|
||||
|
||||
# Task 01: Workflow replay cycle analyzer
|
||||
|
||||
## Acceptance
|
||||
|
||||
- A pure deterministic analyzer returns the diagnostic contract from the spec
|
||||
for cycles over `on_turn_start` and `on_turn_complete` move actions.
|
||||
- Fully automatic versus user-mediated classification accounts for source-step
|
||||
auto-start and approval, including automatic `on_turn_start` transitions,
|
||||
and ignores cycles without auto-start
|
||||
re-entry through `on_turn_complete`. An `on_turn_start` edge into an
|
||||
auto-start step is covered as a safe non-replay regression.
|
||||
- Trace choice, bounded identity inventory with conservative truncation,
|
||||
affected steps, dangling targets, reorder behavior, and all three
|
||||
prompt-source categories have focused unit coverage.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
(cd apps && pnpm --filter @kandev/web test -- lib/workflows/replay-cycle-analysis.test.ts)
|
||||
(cd apps/web && pnpm run typecheck)
|
||||
```
|
||||
|
||||
## Files Likely Touched
|
||||
|
||||
- `apps/web/lib/workflows/replay-cycle-analysis.ts`
|
||||
- `apps/web/lib/workflows/replay-cycle-analysis.test.ts`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Spec sections `What`, `Analysis Contract`, and `Scenarios`.
|
||||
- `apps/web/lib/types/workflow-actions.ts` for action shapes.
|
||||
- `apps/backend/internal/workflow/engine/types.go` for transition resolution
|
||||
and trigger semantics.
|
||||
- `apps/backend/internal/orchestrator/event_handlers_workflow.go` for the
|
||||
auto-start/user-turn distinction.
|
||||
|
||||
## Output Contract
|
||||
|
||||
When finished, change this task's `status` to `done`, check it in `plan.md`, and
|
||||
report the analyzer API, files changed, tests run, blockers, and residual risks.
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: "02-mutation-guard"
|
||||
title: "Workflow mutation guard"
|
||||
status: done
|
||||
wave: 2
|
||||
depends_on: ["01-cycle-analyzer"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/workflow-cycle-guardrails/spec.md"
|
||||
---
|
||||
|
||||
# Task 02: Workflow mutation guard
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Existing update/add/delete/reorder handlers analyze their complete proposed
|
||||
shape before sending the current API mutation.
|
||||
- Blocking proposals send no request; warning proposals execute exactly once
|
||||
only after confirmation; existing diagnostic identities and cycle-removing
|
||||
edits are not gated unless identity comparison is conservatively truncated.
|
||||
- Successful remote mutations reconcile from their authoritative responses;
|
||||
reorder remains optimistic while pending and rolls back on request failure.
|
||||
- Draft workflow save preflights before `createWorkflowAction`, with cancel
|
||||
leaving the draft and sending no create or step requests.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
(cd apps && pnpm --filter @kandev/web test -- components/settings/workflow-card-actions.test.ts lib/workflows/replay-cycle-analysis.test.ts)
|
||||
(cd apps/web && pnpm run typecheck)
|
||||
```
|
||||
|
||||
## Files Likely Touched
|
||||
|
||||
- `apps/web/components/settings/workflow-card-actions.ts`
|
||||
- `apps/web/components/settings/workflow-card-actions.test.ts`
|
||||
- `apps/web/components/settings/workflow-card.tsx`
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 01.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Spec scenarios for existing mutations, baseline/proposed comparison, and
|
||||
draft creation.
|
||||
- Analyzer and diagnostic identity contract from Task 01.
|
||||
- Existing `useWorkflowStepActions` and `useWorkflowSaveActions` behavior,
|
||||
including temp-step remapping and request-error feedback.
|
||||
|
||||
## Output Contract
|
||||
|
||||
When finished, change this task's `status` to `done`, check it in `plan.md`, and
|
||||
report guarded operations, request behavior tests, files changed, blockers, and
|
||||
residual risks.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
id: "03-diagnostic-ui"
|
||||
title: "Workflow cycle diagnostic UI"
|
||||
status: done
|
||||
wave: 3
|
||||
depends_on: ["02-mutation-guard"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/workflow-cycle-guardrails/spec.md"
|
||||
---
|
||||
|
||||
# Task 03: Workflow cycle diagnostic UI
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Existing and proposed diagnostics render severity, exact ordered trigger
|
||||
trace, user-required hops, and prompt source through one reusable component.
|
||||
- Blocking dialogs expose no override; warning dialogs use **Apply anyway** or
|
||||
**Create anyway**, and affected pipeline steps are identified without color
|
||||
alone.
|
||||
- Desktop and narrow layouts keep text/actions usable, keyboard/touch
|
||||
accessible, and page overflow contained.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
make fmt
|
||||
(cd apps && pnpm --filter @kandev/web test -- components/settings/workflow-cycle-diagnostic.test.tsx components/settings/workflow-card-actions.test.ts)
|
||||
(cd apps/web && pnpm run typecheck)
|
||||
(cd apps && pnpm --filter @kandev/web lint)
|
||||
```
|
||||
|
||||
## Files Likely Touched
|
||||
|
||||
- `apps/web/components/settings/workflow-cycle-diagnostic.tsx`
|
||||
- `apps/web/components/settings/workflow-cycle-diagnostic.test.tsx`
|
||||
- `apps/web/components/settings/workflow-pipeline-editor.tsx`
|
||||
- `apps/web/components/settings/workflow-card.tsx`
|
||||
- `apps/web/components/settings/workflow-card-dialogs.tsx`
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 02.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Spec prompt-source language, trace requirements, and mobile behavior.
|
||||
- Pending-operation state from Task 02.
|
||||
- Existing `AlertDialog`, workflow pipeline scroll area, and workflow-card
|
||||
dialog patterns.
|
||||
|
||||
## Output Contract
|
||||
|
||||
When finished, change this task's `status` to `done`, check it in `plan.md`, and
|
||||
report responsive behavior, accessibility treatment, files changed, tests run,
|
||||
blockers, and residual risks.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
id: "04-e2e-verification"
|
||||
title: "Workflow cycle guardrail E2E verification"
|
||||
status: done
|
||||
wave: 4
|
||||
depends_on: ["03-diagnostic-ui"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/workflow-cycle-guardrails/spec.md"
|
||||
---
|
||||
|
||||
# Task 04: Workflow cycle guardrail E2E verification
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Desktop Playwright covers warning cancel/confirm, blocking draft creation,
|
||||
exact trace/prompt source, persisted diagnostics, and an allowed generic
|
||||
cycle without auto-start.
|
||||
- A `mobile-*.spec.ts` test completes the warning confirmation flow by touch
|
||||
and asserts the trace/actions fit without horizontal page overflow.
|
||||
- Focused and repository verification commands run after formatting, with any
|
||||
environment-only blocker reported exactly.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
make fmt
|
||||
(cd apps/web && pnpm e2e:run --host tests/workflow/workflow-cycle-guardrails.spec.ts)
|
||||
(cd apps/web && pnpm e2e:run --host --no-build --project mobile-chrome tests/workflow/mobile-workflow-cycle-guardrails.spec.ts)
|
||||
(cd apps/web && pnpm run typecheck)
|
||||
(cd apps && pnpm --filter @kandev/web lint)
|
||||
make typecheck test lint
|
||||
```
|
||||
|
||||
## Files Likely Touched
|
||||
|
||||
- `apps/web/e2e/pages/workflow-settings-page.ts`
|
||||
- `apps/web/e2e/tests/workflow/workflow-cycle-guardrails.spec.ts`
|
||||
- `apps/web/e2e/tests/workflow/mobile-workflow-cycle-guardrails.spec.ts`
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 03.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Every user-visible spec scenario.
|
||||
- Existing workflow settings page object and API helper patterns.
|
||||
- Mobile parity requirement that mobile tests use the `mobile-` filename
|
||||
convention and real touch-accessible actions.
|
||||
|
||||
## Output Contract
|
||||
|
||||
When finished, change this task's `status` to `done`, check it in `plan.md`, and
|
||||
report desktop/mobile scenarios, files changed, exact commands/results,
|
||||
environment blockers, and residual risks.
|
||||
@@ -124,6 +124,7 @@ System pages (Radarr/Sonarr-style) for status, disk usage, database maintenance,
|
||||
|
||||
| Spec | Status |
|
||||
|---|---|
|
||||
| [workflow-cycle-guardrails](workflow-cycle-guardrails/spec.md) | building |
|
||||
| [improve-kandev](improve-kandev/spec.md) | draft |
|
||||
| [homebrew-core](homebrew-core/spec.md) | draft |
|
||||
| [native-kandev-cli](native-kandev-cli/spec.md) | draft |
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
status: building
|
||||
created: 2026-07-15
|
||||
owner: kandev
|
||||
---
|
||||
|
||||
# Workflow Cycle Guardrails
|
||||
|
||||
## Why
|
||||
|
||||
Workflow authors can unintentionally connect transition actions into a cycle
|
||||
that re-enters an auto-start step. The repeated agent prompt looks like a user
|
||||
message and may start work the author did not intend, while the current linear
|
||||
pipeline view makes the complete trigger path easy to miss.
|
||||
|
||||
## What
|
||||
|
||||
- The workflow settings editor analyzes `on_turn_start` and
|
||||
`on_turn_complete` transition actions whenever it displays or proposes a
|
||||
workflow shape.
|
||||
- The analysis resolves `move_to_next`, `move_to_previous`, and `move_to_step`
|
||||
against the workflow's ordered steps and reports only cycles whose
|
||||
`on_turn_complete` edge re-enters a step whose `on_enter` actions include
|
||||
`auto_start_agent`. An `on_turn_start` edge into that step does not replay
|
||||
automation because the runtime deliberately skips destination `on_enter`
|
||||
actions while dispatching the user's prompt.
|
||||
- A cycle is **fully automatic** when every hop can occur without a user action:
|
||||
every transition starts from a step that auto-starts an agent on entry, and
|
||||
no hop requires approval. This includes `on_turn_start` transitions fired by
|
||||
an auto-started prompt. Creating or applying a newly introduced fully
|
||||
automatic cycle is blocked.
|
||||
- A cycle is **user-mediated** when its `on_turn_complete` replay edge re-enters
|
||||
an auto-start step but at least one hop needs a user action: a transition
|
||||
from a step that does not auto-start its own turn, or a transition that
|
||||
requires approval. Creating or applying a newly introduced user-mediated
|
||||
cycle requires explicit confirmation.
|
||||
- Transition guards are treated conservatively as possible paths. A guard does
|
||||
not make a cycle safe merely because its condition is not currently met.
|
||||
- Intentional cycles that do not re-enter an `auto_start_agent` step remain
|
||||
valid and do not produce a warning.
|
||||
- The diagnostic shows an ordered trace beginning and ending at the affected
|
||||
auto-start step. Every hop names the source step, trigger, transition action,
|
||||
and destination step, and indicates where a user action is required.
|
||||
- The diagnostic identifies what the re-entered auto-start step sends:
|
||||
- an empty step prompt replays the task description;
|
||||
- a step prompt containing `{{task_prompt}}` sends the rendered step prompt
|
||||
including the task description;
|
||||
- any other non-empty step prompt replaces the task description.
|
||||
- If several cycles re-enter the same auto-start step, the editor shows the
|
||||
highest-severity diagnostic first, then the shortest trace. Diagnostics for
|
||||
distinct auto-start steps remain separately identifiable.
|
||||
- Existing persisted workflows with a replay cycle display the diagnostic as
|
||||
soon as their steps load. This visibility does not retroactively stop active
|
||||
tasks or prevent edits that remove the cycle.
|
||||
- Existing workflows keep their current immediate step persistence. Before a
|
||||
step update, add, delete, or reorder request is sent, the editor analyzes the
|
||||
proposed complete shape:
|
||||
- a newly introduced fully automatic cycle cancels the request and offers no
|
||||
override;
|
||||
- a newly introduced user-mediated cycle holds the request until the user
|
||||
chooses **Apply anyway** or cancels it;
|
||||
- a change that removes a cycle proceeds without confirmation.
|
||||
- A mutation is considered newly introduced only when its resulting diagnostic
|
||||
is absent from the currently persisted shape. Unrelated edits to a workflow
|
||||
that already has a diagnostic do not repeatedly ask for confirmation.
|
||||
- A draft workflow is analyzed locally before its first persistence. Fully
|
||||
automatic cycles disable creation and explain the blocking trace;
|
||||
user-mediated cycles require **Create anyway** confirmation. Cancelling keeps
|
||||
the draft and sends no workflow or step request.
|
||||
- The same diagnostic component is used for the inline workflow alert and the
|
||||
blocking/confirmation dialog. Affected steps are visually identified in the
|
||||
pipeline without relying on color alone.
|
||||
- On mobile, the alert and trace stack vertically, dialog actions remain
|
||||
reachable by touch, transition triggers and actions use human-readable labels,
|
||||
long step names and prompt-source text wrap, and the page does not gain
|
||||
horizontal overflow. Diagnostic content scrolls independently from the action
|
||||
footer so every explanation can be read without being covered. The existing
|
||||
pipeline may retain its own horizontal scrolling region.
|
||||
- The guardrail is advisory for user-mediated cycles and blocking for fully
|
||||
automatic cycles only in the workflow settings authoring surface. It does
|
||||
not change workflow runtime execution or persisted workflow schemas.
|
||||
|
||||
## Analysis Contract
|
||||
|
||||
The analyzer accepts the complete proposed `WorkflowStep[]` and returns zero or
|
||||
more diagnostics. Step order is ascending `position`, with input order as the
|
||||
stable tie-breaker. A transition that cannot resolve to a step in the same
|
||||
array is ignored by cycle analysis and remains subject to existing validation.
|
||||
|
||||
Each diagnostic contains:
|
||||
|
||||
- `severity`: `blocking` or `warning`;
|
||||
- the re-entered auto-start step ID and name;
|
||||
- the ordered affected step IDs;
|
||||
- trace hops containing source ID/name, `on_turn_start` or
|
||||
`on_turn_complete`, action kind, destination ID/name, and whether that hop
|
||||
requires user involvement;
|
||||
- prompt source: `task_description`, `step_prompt_with_task_description`, or
|
||||
`step_prompt`.
|
||||
|
||||
Diagnostics have stable identities derived from the auto-start step and the
|
||||
ordered trace. The authoring guard compares a bounded inventory of these
|
||||
identities between current and proposed shapes so it can distinguish every
|
||||
discovered introduced cycle from an existing one, even when the preferred
|
||||
display trace is unchanged. The UI still presents one deterministic preferred
|
||||
trace per auto-start step. If the bounded inventory is exhausted, the guard
|
||||
conservatively presents that preferred diagnostic instead of allowing the
|
||||
mutation based on an incomplete identity comparison. The analysis is
|
||||
deterministic and has no persisted state.
|
||||
|
||||
## Failure Modes
|
||||
|
||||
- If workflow steps fail to load, the editor keeps its existing load error and
|
||||
does not claim that the workflow is safe.
|
||||
- If a confirmed remote mutation fails, the existing error toast remains the
|
||||
source of truth and the editor retains its last server-backed shape. A
|
||||
successful add, update, or reorder applies the authoritative mutation
|
||||
response before the guard accepts another topology edit. Confirmation never
|
||||
implies persistence succeeded.
|
||||
- If a workflow already contains a fully automatic cycle, opening settings
|
||||
surfaces the blocking diagnostic but does not interrupt running tasks. The
|
||||
author must remove the cycle to prevent future re-entry.
|
||||
- Invalid or dangling transition targets do not crash the editor and do not
|
||||
create a false replay trace.
|
||||
|
||||
## Scenarios
|
||||
|
||||
- **GIVEN** `Build` and `Review` both auto-start on entry and each moves to the
|
||||
other on turn completion, **WHEN** an author attempts to save the second
|
||||
transition, **THEN** the request is blocked and the trace shows
|
||||
`Build --on_turn_complete--> Review --on_turn_complete--> Build`.
|
||||
- **GIVEN** `In Progress` auto-starts, a path includes an `on_turn_start`
|
||||
transition from a step that does not auto-start, and its final
|
||||
`on_turn_complete` edge returns to `In Progress`, **WHEN** an author applies
|
||||
the final edit, **THEN** the editor requires **Apply anyway** and sends no
|
||||
mutation before confirmation.
|
||||
- **GIVEN** every source in the same path auto-starts, **WHEN** an
|
||||
`on_turn_start` transition is fired by an auto-started prompt, **THEN** that
|
||||
hop is automatic and a newly introduced fully automatic replay cycle is
|
||||
blocked.
|
||||
- **GIVEN** `Review` moves to auto-starting `In Progress` on `on_turn_start`,
|
||||
**WHEN** that feedback cycle is analyzed, **THEN** the editor shows no replay
|
||||
warning because the runtime sends the user's prompt without executing
|
||||
`In Progress.on_enter`.
|
||||
- **GIVEN** a cycle contains only `on_turn_complete` transitions but one source
|
||||
step does not auto-start, **WHEN** the cycle is introduced, **THEN** it is a
|
||||
user-mediated warning because a user must start that step's turn.
|
||||
- **GIVEN** any transition in the cycle, including the replay edge, requires
|
||||
approval, **WHEN** the cycle is introduced, **THEN** it is a user-mediated
|
||||
warning rather than a fully automatic block.
|
||||
- **GIVEN** a draft workflow has a user-mediated replay cycle, **WHEN** the user
|
||||
clicks Save and then cancels the confirmation, **THEN** the draft remains and
|
||||
no workflow or step creation request is sent.
|
||||
- **GIVEN** a draft workflow has a user-mediated replay cycle, **WHEN** the user
|
||||
confirms **Create anyway**, **THEN** the existing workflow creation sequence
|
||||
proceeds once.
|
||||
- **GIVEN** an existing workflow already has a replay-cycle diagnostic, **WHEN**
|
||||
the settings page loads, **THEN** the inline alert shows the exact trace and
|
||||
prompt source before the author edits it.
|
||||
- **GIVEN** an existing workflow already has a replay-cycle diagnostic, **WHEN**
|
||||
the author changes only its name or prompt without changing the diagnostic
|
||||
identity, **THEN** the mutation is not reconfirmed and the inline alert
|
||||
remains accurate.
|
||||
- **GIVEN** an existing replay cycle, **WHEN** an edit removes the return path or
|
||||
removes `auto_start_agent`, **THEN** the edit persists without confirmation
|
||||
and the diagnostic disappears after refresh.
|
||||
- **GIVEN** a workflow cycle that never re-enters an auto-start step, **WHEN**
|
||||
the author creates or edits it, **THEN** the editor shows no replay warning and
|
||||
does not block the mutation.
|
||||
- **GIVEN** a re-entered auto-start step has no prompt, **WHEN** its diagnostic
|
||||
renders, **THEN** the prompt source states that the task description will be
|
||||
replayed.
|
||||
- **GIVEN** a re-entered auto-start step prompt contains `{{task_prompt}}`,
|
||||
**WHEN** its diagnostic renders, **THEN** the prompt source states that the
|
||||
rendered step prompt includes the task description.
|
||||
- **GIVEN** the warning is displayed at a mobile viewport, **WHEN** the user
|
||||
reviews and confirms a user-mediated cycle, **THEN** the full trace and both
|
||||
dialog actions are usable without horizontal page scrolling or hover.
|
||||
- **GIVEN** multiple blocking diagnostics exceed a mobile viewport, **WHEN** the
|
||||
user scrolls through them, **THEN** transition labels remain readable and the
|
||||
final explanation can scroll fully above the always-reachable return action.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Changing workflow runtime transition or auto-start behavior.
|
||||
- Stopping tasks already cycling when the settings page opens.
|
||||
- Rejecting workflow writes made directly through HTTP, WebSocket, YAML import,
|
||||
seed configuration, or another client.
|
||||
- Analyzing generic event-driven transitions such as
|
||||
`on_children_completed`, `on_heartbeat`, or `on_agent_error` in this first
|
||||
iteration.
|
||||
- Warning about cycles that contain no auto-start replay.
|
||||
- Displaying the full task description or fully rendered prompt in settings.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
[Implementation plan](../../plans/workflow-cycle-guardrails/plan.md)
|
||||
Reference in New Issue
Block a user