16 KiB
status, system, requirements
| status | system | requirements | ||
|---|---|---|---|---|
| draft | tasks |
|
Workflow Move Preview System Design
Purpose and boundaries
Add an advisory projection of the existing move decision. Preserve the routing and best-effort conditional-setting contracts. The tasks system owns the projection; the agent runtime owns actual provider acceptance.
Requirements define the outcome. Lifecycle design defines recipients. Conditional settings defines set, keep, restore-original, original-session eligibility, and partial failure.
Requirement mapping
| Requirement | Sections |
|---|---|
| REQ-TASKS-WORKFLOW-MOVE-PREVIEW-001 | Resolution, API, Freshness and failures |
| REQ-TASKS-WORKFLOW-MOVE-PREVIEW-002 | Presentation, Mobile, Verification |
Resolution
Introduce a read-only decision layer in internal/orchestrator/workflow_move_preview.go.
Extract shared selection decisions from prepareWorkflowStepSession,
shouldKeepCurrentWorkflowStepSession, and workflow_session_target.go.
The existing execution paths consume those decisions but retain their atomic
selection, route-ledger, retry, credential, and terminal-session checks.
Do not call mutating prepare/ensure helpers to implement preview.
Resolve the same authoritative move source as httpMoveTask; do not use the
browser's viewed session as routing authority. Read the destination, source,
sessions, explicit target bindings, profile configuration, and available runtime
configuration. Apply destination start policy and source end policy. No profile
override still follows existing workflow-default resolution. Reuse may select
a matching other session or create one if no eligible candidate exists and the
destination can actually launch a session for this move. When the destination
has no auto-start action, or when skip_step_prompt removes the only launch
turn because it has no instructions, report an idle no_session outcome
instead of claiming that a session will be created. The shared
workflowmove.ShouldAutoStartAgent predicate keeps this projection aligned
with the no-session execution path.
Preserve terminal-session exclusion, explicit target identity, deterministic candidate ordering, and same-profile/new behavior. A zero-session task is reported through the existing launch decision; if that decision cannot be resolved without provisioning, return unknown rather than ensuring a session. Dynamic profile selection without a concrete recipient reports its profile and an unknown model; preview never probes or launches a provider to choose one.
For an existing recipient, use the runtime's effective configuration projection and durable overrides with their existing precedence. Profile defaults alone cannot describe a reused conversation. For new recipients, resolve launch configuration without carrying overrides from the outgoing session.
Factor pure rule selection and configuration projection out of
workflow_session_config.go. Reuse selectConfigureSessionRule,
sessionConfigurationTargetForRule, and existing option normalization.
resolveWorkflowSessionConfigTarget currently emits conversation warnings;
preview must instead collect structured diagnostic codes without calling that
side-effecting path. Share decisions, not warning publication.
Evaluate original-session eligibility, canonical family matching and ambiguity, keep/no-match, set's named fields, and restore's immutable original snapshot. Report skipped rules accurately. Preserve restrictions on model/mode options. Use available model-aware capability snapshots only. Provider acceptance remains uncertain until application; do not synthesize capabilities or restore defaults.
Project applicable context-reset entry actions and the normalized one-shot reset option using execution order. Include whether the configured prompt will run, is suppressed, or waits for turn end/queue admission in expanded details. Do not duplicate reset effects or treat instruction text as a configuration change.
API
Proposed read-only POST /api/v1/tasks/:id/move-preview accepts the destination
workflow_id, workflow_step_id, and the existing entry_options shape.
It shares request validation and task/workspace authorization with the move
handler. It accepts no client-selected source session. POST carries the draft
without putting instructions in a URL. Neither instructions nor raw request
bodies are logged or echoed. Return Cache-Control: no-store.
Wire the handler through task_handlers.go, a narrow orchestrator preview
interface, and existing dependency construction. New DTO fields:
task_id,workflow_step_id,source_session_id,evaluated_at.outcome:reuse_current,reuse_other,create_new,no_session, orunknown.recipient: nullable session ID/name, profile ID/name, agent family.model: before/after IDs and labels, known/unknown flags, and source (runtime,override,profile,step_rule,original).changes: normalized field key, safe label, before/after value, andplanned/unchanged/skipped/unknownapplicability.context_reset,source_disposition: keep/park/complete/unknown,dispatch: prompt/no_prompt/deferred/unknown.notices: closed reason codes with safe parameters, including retained model override, missing snapshot, unsupported option, and stale capability.
Only selectable, non-sensitive model configuration enters this DTO. Never expose
credentials, environment, raw session metadata, prompts, or provider errors.
Unauthorized and missing tasks use existing API errors. Uncertain routing uses
an unknown projection; a no_session projection has no recipient, and no
fabricated session ID is returned for a new session.
This response is additive; no migration or durable preview ledger is needed.
Freshness and failures
use-workflow-move-preview.ts under hooks/domains/kanban/ owns requests through
lib/api/domains/kanban-api.ts. Fetch only while an applicable disclosure is open.
Use the existing hover delay. Every disclosed movable drawer row may enqueue a
request, while a shared queue keeps at most two requests in flight and cancels
rows that close or unmount. Do not request previews for every step across every
mounted task.
Deduplicate pending requests by task, destination, normalized options, and a local
invalidation generation. Abort on close/context change and reject late generations.
Invalidate on task placement/primary-session changes, relevant session runtime updates, profile updates, workflow-step updates, reconnect, and draft changes. Clear on close and fetch fresh on reopen. Debounce draft edits; typing instruction text need not refresh unless its presence changes prompt dispatch semantics.
Semantic revision inputs
getWorkflowMovePreviewRevision in use-workflow-move-preview-revision.ts
shall serialize explicit prediction inputs, not complete store records. This
implements AC-TASKS-WORKFLOW-MOVE-PREVIEW-001.8 while preserving .7. The existing
request hook remains responsible for loading, cancellation, retry, and generation
checks. An irrelevant update retains the same key and successful result. A real
input change clears that result before requesting another prediction. Do not
retain a stale result as current, add polling, or cache completed requests.
Use these projection boundaries for active and background workflow snapshots:
| Input | Included meaning | Excluded bookkeeping |
|---|---|---|
| Task | Identity, placement, primary session/state, archive eligibility, task profile fallback, initial-session and routing metadata | Title, description, position, unrelated metadata, status-summary counters and timestamps |
| Sessions belonging to the task | Membership, identity/name, profile identities, state, primary/passthrough flags, configuration and original-session provenance | Read cursor, command counts, error prose, activity counters, workspace paths |
| Configuration | Runtime configuration, overrides, original effective snapshot, profile snapshot fields used by preview, model/capability/config-option data | Unrelated session metadata and capability-fetch bookkeeping |
| Workflow and steps | Default profile; source end policy; destination profile, target, start policy, entry actions; referenced target-step inputs | Workflow/step colors and unrelated presentation fields |
| Profiles | Prediction-relevant identity, display name, family, model, mode, config options, passthrough and dynamic-routing configuration; scoped profile revision when the normalized option lacks a full field | Global profile version counters and capability error prose without a projected change |
| Context | Connection status, workspace generation, requested task/workflow/step, normalized move options | Unrelated store activity |
Keep both session stores and both task projections observable. Do not choose a
new authority or collapse conflicting snapshots as part of this repair.
Project metadata keys explicitly, including agent_profile_id,
workflow_initial_session, workflow_session_route, runtime_config,
runtime_config_overrides, original_effective_config, and
completion_follow_up where the backend reads them. Trace the preview's helper
calls when maintaining this list; arbitrary metadata is not an invalidation key.
Timestamps require semantic treatment. selectReusableWorkflowSession uses
UpdatedAt to rank eligible same-profile candidates, while previewCurrentSession
uses primary flags and then StartedAt with an ID tie-break. Encode candidate
ordering or selected identity dependencies instead of raw changing timestamps.
Preserve exclusion and tie behavior, including terminal and completion-follow-up
sessions. A timestamp update that changes selection must change the revision;
one that leaves candidate order unchanged must not. Task-session and indexed
session stores can contain two projections of the same logical session. Keep
their raw projections and sensitivity, but calculate current-session,
original-session, and candidate-order dependencies independently per store so
the duplicate is never treated as two candidates or two marked originals.
This is a freshness signal, not a frontend replacement for the server's move
decision.
Profile events update the full settings profile and a normalized picker option.
The revision must read mode and configOptions from the full settings
profile, and include the normalized profile's updatedAt as a scoped fallback
when those fields are unavailable. An unrelated profile event or global profile
version bump must not invalidate a preview that does not reference that profile.
When an initial-target snapshot names an agent_profile_id, include that profile
even if the historical session named by the snapshot is no longer present.
Normalize object key order in projected maps. Sort collections only when their order is not meaningful; preserve rule order, selection order, and ties. Retain explicit empty and missing configuration distinctions where the backend distinguishes them. Never log revision contents.
The correction covers StepMoveControls, StepDisclosureMoveControls, and
WorkflowMovePreviewFooter through their shared hooks. It changes no drawer
geometry, controls, copy, or endpoint contract. The
stability repair plan
defines the implementation and regression matrix.
Keep two-line loading/error states compact. Retry is explicit and does not move the task. Preview failure does not introduce a new move barrier. Show predictions as planned, not guaranteed; details explain that execution rechecks current state. Deferred moves show that settings are checked again at turn end. The actual move continues through existing execution paths and handles provider partial failures. No preview token is required to move and no confirmation flow is added.
Presentation
Add workflow-move-preview.tsx as a shared projection renderer with no fetches.
StepMoveControls in workflow-stepper.tsx owns the draft and passes the same
normalized entry options to preview and move. Thread task/workflow identity down
from the stepper. Integrate the renderer in workflow-step-disclosure.tsx rows.
Collapsed layout is two centered lines below actions and capabilities. Line one distinguishes current, other named, new conversations, and an idle task with no recipient. Line two shows an unchanged effective model or a before/after arrow, followed by an optional additional-change count. A retained model/profile mismatch uses the compact override label from UI-01.
Count actual non-model field changes plus one context-reset effect. Do not count source retirement as a setting change. Deduplicate model and config-option model representations. If only reasoning changes, show the unchanged model with +1. Use the generic New session label for a permitted fresh launch. Use a separate localized no-session label when the move leaves the task idle. Show the model on line two for a fresh launch when known; profile name belongs in details when it differs from the model label.
The API returns stable field, value, and diagnostic reason codes for Kandev-owned settings. The renderer localizes those codes in the active locale and displays provider-defined labels only when they are safe display data. Model and session names remain data.
Center both footer lines and use an icon-only info button with a localized accessible name. A separator divides actions from the muted footer.
The chat and passthrough next-step controls pass the destination directly from
usePlanActions through WorkflowMoveProceedButton to the options form. The
form previews its current draft below the Move action, on desktop and touch.
Mount the preview only while the options surface is open. Use the existing
revision, debounce, cancellation, and concurrency logic.
Use a keyboard/touch-operable details toggle with aria-expanded, not a nested
hover-only tooltip. Details list full session/profile identity, planned changes,
source retirement, dispatch, and applicable notices. Arrow summaries mean planned
changes; accessible text and expanded details explain this. Keep Options, existing
capability icons, progress, move errors, and move pending ownership intact.
Unknowns take priority over misleading before/after arrows. Status changes use
one localized status region without repeatedly announcing unchanged predictions.
Mobile
Reuse CompactWorkflowStepDisclosure and its useTouchDrawer branch, including
its max-h-[80dvh] surface and fixed header. The curated MobilePickerSheet pattern
supplies a discoverable header entry, inset drawer, focus return, and safe areas.
This is a short temporary step choice, so it belongs in the existing drawer.
Each visible step row gains the same two-line summary. Details expand inline inside the drawer's single scroll body; avoid nested popovers or drawers. Move here, Options, Retry, and details have at least 44px touch hit areas. Desktop keeps compact 24px action density. Long names truncate in the summary and wrap in details. Verify 320px, the default mobile viewport, and breakpoint neighbors 767/768px with the applicable pointer mode. No document horizontal scroll or separate persisted mobile selection is introduced.
Verification
Backend fixtures compare preview selection/configuration with actual move outcomes on isolated tasks. Assert preview does not change rows, metadata, route bindings, messages, or runtime call counters. Cover the historical Luna-profile/Astra override scenario plus conditional settings, the two no-session launch gates, task-profile fallback, and deferred moves. Frontend tests cover live store/reconnect invalidation, queued rows beyond the first two, and localized field/value/diagnostic rendering. Browser tests inspect the preview, perform the move, then verify recipient and model through fixture API state. See the plan.
Related decisions
This projection preserves those decisions. Shared read-only resolution avoids frontend routing duplication; an independent frontend approximation would drift on explicit bindings and runtime overrides. A new ADR is unnecessary for this additive feature because these boundaries and rationale fit the paired design.