423 lines
24 KiB
Markdown
423 lines
24 KiB
Markdown
---
|
|
status: draft
|
|
system: tasks
|
|
requirements:
|
|
- REQ-TASKS-COMPLETION-001
|
|
- REQ-TASKS-COMPLETION-002
|
|
- REQ-TASKS-COMPLETION-003
|
|
updated: 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.
|
|
|
|
## Related decisions
|
|
|
|
- [Separate task completion from conversation availability](../../../decisions/2026-09-09-task-completion-conversation-availability.md)
|
|
- [Replayable migrations](../../../decisions/0027-replayable-schema-migrations.md)
|
|
- [Profile-switch session policy](../../../decisions/2026-08-31-workflow-profile-session-switch-policy.md)
|
|
- [Current-turn clarification ownership](../../../decisions/2026-08-14-current-turn-clarification-ownership.md)
|
|
- [Task-owned worktree lifetime](../../../decisions/2026-08-08-task-owned-worktree-lifetime.md)
|
|
- [Environment ownership generations](../../../decisions/2026-09-04-generation-fenced-task-environment-ownership.md)
|
|
|
|
## Implementation plans
|
|
|
|
- [Task completion and follow-ups](../../../plans/task-completion/plan.md)
|
|
- [Workspace restoration after completion](../../../plans/completed-workspace-restoration/plan.md)
|