Files
2026-09-12 16:26:35 +01:00

24 KiB

status, system, requirements, updated
status system requirements updated
draft tasks
REQ-TASKS-COMPLETION-001
REQ-TASKS-COMPLETION-002
REQ-TASKS-COMPLETION-003
2026-09-10

Task Completion System Design

Purpose and boundaries

The task system owns workflow completion and conversation admission. Runtime shutdown releases execution resources; it does not establish permanent loss of conversation access. Agent runtime restoration and workspace recovery retain their current owners and authorization checks.

Requirement Design sections
REQ-TASKS-COMPLETION-001 Step contract, Migration, Entry and notification
REQ-TASKS-COMPLETION-002 Explicit resume, Follow-up ownership, Cleanup and races, User interface
REQ-TASKS-COMPLETION-003 Workspace admission, Workspace runtime continuity, Workspace failure feedback

Step contract

Use CompleteTaskOnEnter bool and complete_task_on_enter in JSON, YAML, and SQL. The naming follows cancel_triggers_turn_complete. Responses and current exports include false explicitly. PATCH uses presence-aware decoding: omission preserves the value, false disables it, and explicit null is rejected. Create defaults an omitted value to false and rejects null or non-booleans.

Completion requires both final-step eligibility and an enabled setting. Persist the setting on its step across reorder operations, but ignore it while that step is non-final. The new final step uses its own saved value; reordering does not transfer or automatically enable completion. The editor derives visibility from the current draft order, and the backend evaluates the committed order. Saving or discarding the order and settings uses existing coordination.

Carry the field through both task and workflow step models, StepDefinition, the embedded workflow loader, task DTO conversion, workflow controller request types, stepevents, MCP schemas and handlers, and all repository projections. workflow_steps.complete_task_on_enter uses the repository's integer boolean convention with a non-null zero default. Both repositories initialize this shared table; neither constructor may erase or bypass migration decisions.

Frontend types, workflow API, boot hydration, WebSocket merges, step drafts, save/rollback, workflow creation, and duplication carry the same boolean. Partial WebSocket updates that omit it retain the saved value.

Portable export advances to version 2. Each version-2 step requires a boolean completion field. Import and sync accept versions 1 and 2. At the version-1 boundary only, omitted values are materialized using the old name-and-final predicate; explicit false always wins. Reject null in either version. Normalize the whole workflow before converting individual steps, because legacy final-position detection needs all steps. Export always emits version 2. Thus current authored definitions never depend on names at runtime.

Sync applies normalized values and includes the field in equality checks. Version-1 sync files remain authoritative legacy input until their authors upgrade them; local editing of synced rows stays disabled. An invalid version-2 file follows existing freeze-and-report sync behavior.

Built-in YAML declares the field explicitly for every step. Preserve each template's old behavior: qualifying final steps are true; other steps are false. Do not make the final PR step in Improve Kandev terminal merely because it is last. Update stored template definitions and workspace bootstrap paths too.

Migration

The workflow repository owns the semantic backfill, after required additive schema changes and before serving requests or starting sync. Task repository projection/bootstrap initialization adds the same column before its own writers need it. It does not independently infer or backfill completion.

Use one transaction and a durable kandev_meta key, workflow_complete_task_on_enter_backfill_v1. Inside the transaction, read the marker, backfill legacy steps and stored template JSON, then record completion. Use the existing portable Go normalization for trimmed case-insensitive names and final position. A final step has no sibling with a greater position, matching GetNextStepByPosition; tied maximum positions retain that definition. Preserve row timestamps and IDs. The transaction rolls back the marker and all backfill writes on failure. Startup fails if this required migration fails.

Replay skips the semantic backfill after the marker. It must not use WHERE complete_task_on_enter = 0 as a recurring migration. Fresh startup writes explicit built-in values and records the marker before ordinary user writes. Template backfill preserves unrelated JSON fields and respects explicit values. Required-store conformance covers actual constructor order, fresh SQLite and PostgreSQL, old-schema upgrade, replay after disabling, and failure/retry. Historical release fixtures remain truthful snapshots; add assertions or a dedicated legacy fixture rather than editing old releases to contain new columns.

Entry and notification

Replace the name test in IsTerminalStep with the explicit boolean while retaining final-position eligibility. A step completes work only when it has no successor and its setting is true. Keep the old name predicate private to legacy conversion/migration. Audit all callers, including task creation, task/service.service_workflow, orchestrator.workflow_store, queued moves, automated transitions, and task_launch_workflow. Every caller must resolve the actual successor or equivalent final-position evidence; passing nil without a lookup is not proof.

Persist step and task-state changes through existing guarded transition and ledger operations. A checked final entry completes a nonterminal task; an unchecked or non-final entry follows existing nonterminal/reopen behavior. Do not convert FAILED or CANCELLED to success. Saving the checkbox does not reevaluate resident tasks. Do not add transition actions or replay on-enter during Resume.

Parent rollups and dependencies use committed task state. Remove name- or step-membership fallback from annotateTerminalChildSteps and terminal receipt handling. Preserve DependencyStatusForTask's existing state-only resolution. Completion events follow the successful state write, never a speculative target lookup. Preserve existing operation deduplication, failure/cancellation rollups, archived-child filtering, and success-only dependency launches. Upgrade does not rewrite resident tasks or fire old completion events; pre-existing persisted completion stays intact.

Explicit resume

Reuse session.recover with action: resume. Admit completed recovery only through this explicit operation or a session-pinned follow-up send. Carry a narrow internal completed-resume permission through LaunchSession and executor.ResumeOptions; automatic session.ensure, status reads, generic open-time launch, and startup recovery never infer that permission.

Authorize the task/session pair and prompt access before side effects. Under the existing lifecycle/cancel guards, reload the exact session and task, verify archive and runtime ownership, and claim the current completed state/version. Require the expected execution generation as well as state where a callback can replace the session during recovery. Do not weaken global terminal guards.

Extend allowsSessionStartingRecovery only for an explicitly claimed completed resume. Permit absent executors_running after cleanup; retain recoverable task environment and profile checks. Reuse stale-execution cleanup, provider token restoration, and native/CLI/history fallback. Persist STARTING and clear CompletedAt through the guarded writer before credentials are issued. Retain prior execution and turn history; do not clear ACP/provider identity.

At readiness, settle into the existing input-capable state without sending a synthetic workflow or task-description prompt. Preserve current initial-message backfill only for genuinely absent launch history. The next user send starts one new conversational turn after the old one. No new session ID or queue incarnation is allocated by Resume.

message_target_session.go continues to prefer live primary, then live fallback. Completed siblings must not shadow a live session. With no live session, a completed primary, or the newest completed session if no primary exists, can be selected and pinned. An explicit completed session_id always targets that session. Authorization and validation precede the same resume admission, which runs before ProcessOnTurnStart and message persistence. FAILED/CANCELLED dispatch continues to reject with its existing error code.

Follow-up ownership

A message on a completed task is conversation work. Runtime running, waiting, failed, and finished events do not reopen that task or replay workflow entry, turn-start, turn-complete, completion-signal, or parent notification effects. Existing explicit task moves/state APIs remain the way to reopen workflow work.

For an explicitly resumed completed session on a still-active task, persist a session metadata marker completion_follow_up in the guarded resume claim. It records conversational-only ownership independently of the mutable session state. Such a session stays excluded from automatic profile reuse and cannot become primary merely because it resumes. Automatic workflow triggers and runtime task-state reconciliation skip it. An explicit workflow handoff may clear the marker only while establishing that session's workflow ownership. An ordinary send, page activation, or automatic reuse lookup cannot clear it.

Pinned user and MCP sends, queued dispatch, streaming completion, agent exit, and failure publication consume the same ownership predicate. This protects a retired profile conversation even after its state becomes WAITING_FOR_INPUT. Preserve primary IDs, profile routing, step history, and current queue policy.

Cleanup and races

For successful task completion, stop collapsing an ordinary child conversation to COMPLETED in setSessionWaitingForInputIfRequestedWithHook. Settle it with the existing root-task waiting behavior. Keep task status completed and retain the fail-closed idle resource reclamation. Do not change failed/cancelled task receipt rules through this branch.

Use current-turn clarification lookup; any active clarification retains its existing barrier and late-answer path. Historical answered or expired bundles remain history. A lookup error cannot justify closing the conversation.

Cleanup and resumed execution must use the same lifecycle ownership boundary. An old receipt, stop callback, stream frame, or reclaim operation carries its execution identity; after a newer resume claim it cannot clear queues or stop the replacement runtime. Preserve retired-execution tombstones. Release guards before waiting for callbacks that need those guards.

Queue admission and readiness use the existing queue-incarnation and dispatch reservation contract. Never clear pending messages solely because the old execution completed. Duplicate Resume joins or returns the existing attempt. A later stop/archive/delete wins over stale recovery writes. A failed launch uses existing recoverable error state without changing completed task state; retain the follow-up marker for retry and preserve queued/unsent content.

User interface

SessionStoppedBanner completed mode has primary Resume and secondary New Agent. Reuse useSessionRecoveryActions and existing recovery error feedback. Show busy state, disable duplicate actions, and restore the composer from the authoritative session update. Missing profile feedback stays visible. New Agent retains its dialog and intentional new-conversation behavior.

Keep the completed-chat open-time gate in session-resumption hooks. Extend recovery hook tests for response success/failure, session switching while a request is pending, and busy cleanup. Do not let a late response modify another chat's recovery feedback or selected session.

Show the completion checkbox only in the final step's entry/general settings, outside the conditional turn-complete controls. It remains available when the final step has no actions. Put the description inside an adjacent info icon using the existing HelpTip interaction: hover or focus on desktop, tap on touch. Do not render the description permanently below the checkbox. Suggested text: "Marks the task complete when it enters this final step. You can still continue the conversation." Give the icon a localized accessible name. Use coordinated dirty/save/discard state and read-only sync treatment. Localize all copy.

Mobile composition

The existing task-layout.tsx phone layout and inline stopped-session banner are the chat exemplar. Keep the transcript's single scroll owner and the composer area's safe-area spacing. Stack Resume then New Agent on phones; retain compact side-by-side buttons on fine-pointer desktop. Phone/coarse pointer actions have at least 44 px active height; no unconditional desktop min-h-11 sizing is introduced.

The step editor's CancelCompletionToggle supplies the checkbox/label pattern; the existing mobile workflow step selector supplies focused navigation. Use a 44 px tappable label and a touch-accessible info icon. Disclose wrapping helper text through the existing touch help surface; do not require hover or add a new standalone modal. Opening help must not toggle the checkbox. An inline setting fits this short choice. Both viewports share draft and save logic. Preserve keyboard focus, one settings scroll region, no horizontal page overflow, and access to Save changes above safe-area insets.

Workspace admission

Task completion, conversation availability, and workspace access have separate admission rules. launchRestoreWorkspace is a workspace operation. It does not receive AllowCompletedSessionResume, transition the session to STARTING, or call agent configuration/start. Explicit Resume retains the preceding design.

The lifecycle manager's createExecution builds workspace infrastructure only. Replace its calls to ensureLaunchSessionStillActive with a workspace-specific admission check. Keep the active-session check on agent launch and promotion. Do not remove COMPLETED from global terminal-state helpers or introduce a client-controlled bypass flag.

Workspace admission resolves the session's canonical TaskEnvironment and checks the current task, session binding, environment identity, owner generation, archive state, and cleanup admission. COMPLETED, FAILED, or CANCELLED alone is not proof that the task has relinquished its workspace. Missing records, database errors, invalid ownership, or an admitted cleanup fail closed. Reuse the existing task cleanup barrier and generation-aware repository helpers; do not replace them with a process-local mutex or a path-exists check.

Keep authorization before cache reuse as well as before creation. Retain session.exec and environment access checks for execution-capable surfaces; viewing conversation history does not grant shell or workspace access. The environment's actual owner controls destructive cleanup. A borrowing session cannot authorize changes to another owner's resources.

Use the same admission rule for EnsureWorkspaceExecutionForSession, GetOrEnsureExecution, and GetOrEnsureExecutionForEnvironment. The session route remains a lookup handle; it must resolve to the same canonical environment. An existing environment execution can be reused without replacing its session binding, running agent, or workflow owner.

For workspace creation, capture admission identity before external operations, revalidate after runtime creation, persist registration through the existing cleanup-aware writer, then revalidate before readiness publication. If cleanup, deletion, ownership transfer, or a newer execution invalidates the claim, roll back only the runtime created by that attempt. Never tear down a winning sibling or resumed execution. A completed session present at admission differs from a concurrent stop or cancellation of the attempt; preserve the existing lifecycle and cancellation guards rather than inferring cancellation from state alone.

Repository validation uses canonical inventory. Every repository required by the environment's existing validation contract must pass; one healthy repository does not excuse another missing or unsafe required repository. Keep the current handling of historical deleted repository rows. Restoration attaches retained workspaces and starts access infrastructure; it does not invoke branch replacement or silently rematerialize an absent checkout. Audit reconcileExecutionWorkspace and reconcileWorkspaceWorktrees accordingly. Explicit unarchive or branch recovery keeps its separate authority.

Workspace runtime continuity

Retain the common session-keyed singleflight and duplicate-registration rollback used by session and environment entry points. Coordinate restoration with the existing explicit-resume lifecycle guard. If Resume wins, workspace consumers use its execution; if restoration wins, Resume may use the established promotion or replacement path. Only Resume can start the agent. Do not add another lock order or an environment-only singleflight bucket that races session requests.

publishCreatedExecution emits agentctl Starting before Ready/Error. Preserve that ordering and workspace stream attachment. These events update workspace readiness, not task completion, session state, turns, or queue dispatch. Failure before registration must still reach the initiating request's workspace error state; waiting for an agentctl error event cannot cover that failure.

Workspace registration can replace a stopped executors_running row. Preserve recoverable provider identity and metadata before doing so. Do not store a workspace runtime's empty provider fields over the completed conversation's resume data. Prove restore, restart, then explicit Resume uses the same provider conversation. Existing session metadata and persistence helpers remain the source; no schema migration or new durable execution-purpose field is planned.

Startup may release terminal-session runtime resources under existing cleanup policy. The next authorized workspace access must be able to restore them without reviving the agent. Old stop, reclaim, and readiness callbacks must not alter a newer execution. Retain idle resource reclamation; the fix does not keep every completed task's runtime permanently alive.

Workspace failure feedback

Carry the known operation (restore_workspace) through the frontend request boundary. Do not infer it by parsing the English error message. Workspace attempt state is shared by consumers of the same environment and includes task/environment identity, an attempt revision, pending/ready/error status, a safe cause, bounded diagnostic detail, and retry eligibility. Reuse environment state and request guards; do not persist this transient feedback as an agent launch failure or create independent retry loops in each panel.

Keep this state in the existing session-runtime slice beside environment-keyed Git and shell state. Add pure attempt-state helpers and actions, with defaults, environment mapping, and purge behavior covered by tests. The domain hook owns request coordination. Do not treat a session ID as proof of environment identity when its canonical mapping is unavailable, or let purging a historical session clear a newer attempt owned by another session in the shared environment.

useSessionResumption and use-session-resumption-operations publish restore results to that workspace state. task-page-inner.tsx excludes workspace-only failures from EnsureSessionErrorBanner and SessionRecoveryFeedback above the layout. Genuine session ensure/start/resume failures retain their existing presentation and precedence. Do not hide errors globally for completed tasks.

Extend WorkspaceUnavailable for a restoration cause and Retry, rather than using its setup-failure wording for all cases. Render the shared feedback at workspace content boundaries, including Files, Changes, and workspace terminals. On a failed initial load, replace the preparation indicator. If cached content exists, keep it visible with an inline stale/unavailable notice; do not present stale data as a successful live refresh. The chat's completed banner, Resume, and New Agent remain independent. Failure in agent Resume remains chat-owned.

Use a neutral icon, compact title, one short cause, and inline Retry/Details. Suggested localized copy is "Workspace unavailable" and "Couldn't reconnect to this task's workspace." Details are collapsed by default and must already be sanitized and bounded before rendering. A disclosure is not permission to expose credentials, raw command output, or private paths. Classify workspace restoration failures correctly in backend logs instead of labelling every terminal-state rejection as shutdown. Use existing structured diagnostics with task/session/environment identity and outcome; no new metrics are required.

Retry repeats only workspace restoration and clears matching feedback only after success. Pending disables duplicate actions. Permission/archive/deleted or invalid-inventory failures do not retry on a timer. Preserve existing explicit recovery options where applicable. Task switches and newer attempts invalidate late results. Readiness reconciliation after reconnect must not overwrite a newer error with an old ready event.

Workspace mobile composition

The curated exemplar is task-layout.tsx and its dedicated mobile/session-mobile-layout.tsx: phone navigation selects one workspace panel. The Files panel opens mobile-file-viewer-panel.tsx for file content. Place the error inline in that selected panel. This is a persistent content failure with a short action, so it needs neither a global banner nor a new drawer.

Desktop keeps compact actions within its workspace pane. Phone/coarse-pointer Retry and Details have at least 44 px hit areas; apply those dimensions only to the appropriate responsive variants. Details wrap in the panel's single vertical scroll region. Preserve the mobile layout's dynamic viewport and safe-area navigation, keyboard focus, file-viewer back action, and saved desktop layout. Shared state and retry behavior remain identical across viewports.

Verification and observability

Use barrier-based Go tests for resume/stop, reclaim/resume, stale receipt/new execution, double Resume, and readiness/queue admission. Assert the preserved task, primary session, provider token, history, queue order, and emitted events. Test both database dialects at migration boundaries. Use existing structured logs with task/session/execution identity and resume outcome; do not log tokens.

Desktop and mobile Playwright tests save/reload the final-step checkbox, complete checked final steps, send from unchecked final steps, and Resume a persisted completed chat. Reorder tests prove the control moves to the current final step and a previously checked non-final step cannot complete work. Help tests prove hover/focus/tap disclosure and that opening help does not toggle the value. Assert a second agent response, unchanged session count and history, and no task reopen or duplicate completion. Include a retired non-primary session and active clarification regression. Run against freshly built production assets.

For workspace restoration, use a real retained checkout and a completed session with no live execution. Prove passive Files/Git/terminal access, unchanged conversation state, and later explicit Resume on desktop and phone. Test all three terminal states, shared environments, cache reuse, missing mixed repository inventory, permissions, cleanup races, and provider identity preservation at the appropriate unit/integration boundary. Inject one bounded restore failure for the UI recovery test; do not mock successful workspace restoration in the happy path. Exact regression names and commands belong to the linked fix package.

Implementation plans