Files
kandev/docs/plans/workflow-empty-prompt-dedup/plan.md
T
2026-09-03 10:21:55 +00:00

157 lines
8.7 KiB
Markdown

---
created: 2026-09-02
status: done
requirements:
- REQ-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-003
- REQ-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-004
- REQ-TASKS-MCP-TOOL-NAMES-001
system_design:
- ../../specs/tasks/system-design/workflow-step-agent-start-ownership.md
- ../../specs/tasks/system-design/mcp-tool-name-stability.md
legacy_specs:
- ../../specs/workflow-on-enter-action-dispatch/spec.md
---
# Implementation Plan: Deduplicate Empty Workflow Prompts
## Overview
The backend uses an atomically claimed session prompt boundary before it applies
the task-description fallback. The frontend uses the canonical plan-tool names.
The follow-up work makes immediate agent-start placement independent of agent
mode. This keeps the task in the workflow step that owns automatic starts.
## Scope
### In scope
- Stop repeated task-description prompts on empty automatic-start steps.
- Apply one fallback rule to automatic step entry and explicit workflow-step launch.
- Preserve non-empty step prompts for prompted sessions.
- Preserve the first task-description prompt for unprompted sessions.
- Correct `plan_get` and `plan_update` in the active-plan context.
- Add focused Go, TypeScript, and Playwright regressions.
- Route an immediate plan-mode agent start to the first automatic-start step.
- Prove the routing through desktop and mobile task creation.
- Update the public workflow guidance.
### Out of scope
- Do not change the first-step placement of plan-only prepared sessions when
`start_agent=false`.
- Do not change the configured start-step behavior for no-agent task creation.
- Do not change workflow prompts that contain text.
- Do not add a database column or migration.
- Do not change MCP tool registration or transport names.
## Technical approach
### Durable prompt history
Add a bounded repository query for `task_session_prompt_seq.last_seq` and an atomic initial-fallback claim. The claim and direct user-message creation use the same per-session persistence boundary, so concurrent automatic and explicit admissions cannot both qualify as the first prompt. The claim uses a zero-valued counter row as its reservation marker, so the first visible message still receives prompt ordinal 1.
Expose this query and claim through the repository contract that the orchestrator uses. Keep message deletion behavior unchanged because the counter never decreases.
### Shared workflow prompt decision
Add an orchestrator helper that composes a workflow-entry prompt for one session. For an empty `WorkflowStep.Prompt` only, use the task description only when the atomic initial-fallback claim succeeds. Non-empty step prompts, including `{{task_prompt}}` expansion, retain their existing semantics.
Call the helper from `launchAfterOnEnterDispatch` before its ACP or passthrough split. Call the same helper from `StartSessionForWorkflowStep`.
Keep `buildWorkflowPromptWithTrustedContext` as the string composer. Keep non-empty `WorkflowStep.Prompt` behavior unchanged.
Apply plan-mode and session-config transforms before deciding whether the composed prompt is empty. If the composed ACP prompt is empty, let `autoStartStepPrompt` inspect any queued handoff first. Return without message creation or dispatch only when the merged prompt and attachments are empty. Attachment-only handoffs are admitted and persisted with their metadata.
### Plan-tool names
Change the active-plan context in `apps/web/hooks/use-message-handler.ts`. Use `get_task_plan_kandev` and `update_task_plan_kandev`.
Export the pure context helper for focused unit coverage. Do not localize this model-facing instruction.
### Immediate-launch placement
Change `task.Service.resolveWorkflowStep` so `StartAgent` is evaluated before
`PlanMode`. A request with both values uses `ResolveAutoStartStep`. A request
with only `PlanMode` continues to use `ResolveFirstStep`.
Keep explicit `workflow_step_id` precedence and the existing automatic-start
fallback chain. No transport, API, persistence, or frontend production change
is required.
Update the existing desktop workflow E2E scenario. Add a mobile scenario that
uses the visible plan-mode action. Update the public task-creation reference
and troubleshooting text.
## Tests
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-003.1`: add a Go test for an unprompted session on an empty automatic-start step.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-003.2`: add a Go test for a prompted plan-mode session that later enters an empty automatic-start step.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-003.3`: add a Go test that a non-empty step prompt still dispatches after earlier prompts.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-003.4`: assert that an empty result creates no user row and no runtime prompt, while an attachment-only handoff remains durable input.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-003.5`: add a focused `StartSessionForWorkflowStep` regression.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-003.6`: cover the shared decision before the transport split.
- `AC-TASKS-MCP-TOOL-NAMES-001.3`: add a TypeScript test for canonical plan-tool names.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-004.1`: update service and
transport routing tests for `start_agent=true` with `plan_mode=true`.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-004.2`: retain the existing
automatic-start fallback test.
- `AC-TASKS-WORKFLOW-STEP-AGENT-START-OWNERSHIP-004.3`: retain the explicit-step
precedence test.
## E2E tests
Update `apps/web/e2e/tests/workflow/start-step-vs-auto-start-step.spec.ts`.
Start a plan-mode agent through the desktop task dialog. Assert that the task
lands in the first automatic-start step.
Keep the duplicate-prompt scenario by making Plan the first automatic-start
destination. Move the idle task into a second empty automatic-start step.
Assert that the description appears once and that no extra turn exists.
Add `apps/web/e2e/tests/workflow/mobile-start-step-vs-auto-start-step.spec.ts`.
Use the mobile plan-mode button and assert the same destination. These tests
change behavior only. The current task dialog layout and mobile composition
remain unchanged.
The current mobile task-create footer is the nearest mobile exemplar. It keeps
the plan-mode action visible as a touch-sized button. The desktop split menu
and the mobile button continue to use one submission handler.
## Work orders
- [x] [Task 01: Deduplicate empty workflow prompts](task-01-deduplicate-empty-workflow-prompts.md)
- [x] [Task 02: Correct plan-tool names](task-02-correct-plan-tool-names.md)
- [x] [Task 03: Prove the plan-mode workflow flow](task-03-prove-plan-mode-workflow-flow.md)
- [x] [Task 04: Route immediate plan-mode starts](task-04-route-immediate-plan-mode-starts.md)
## Verification results
- Backend focused race suite: 18 tests passed after the PR fixup, including prompt-admission races, plan-only and attachment-only inputs, passthrough draining, and session-ID reuse cleanup.
- Backend full suite: all packages passed with the task-session internal
configuration handoff variables cleared. The plain command was blocked by
the inherited launcher-selected `/root/.kandev/config.yaml`, which made
isolated config-discovery tests select the operator config.
- Backend `make lint`: 0 issues.
- Backend new-from-`origin/main` `golangci-lint`: no issues.
- Frontend focused hook suite: 27 tests passed.
- Frontend typecheck, targeted ESLint, i18n check, and i18n ratchet: passed.
- Frontend production build and targeted Playwright regression: passed.
- Task 04 focused backend suite: 14 tests passed in 2 packages.
- Task 04 desktop Playwright suite: 3 tests passed, including the retained
duplicate-prompt regression.
- Task 04 mobile Chrome Playwright suite: 1 test passed through the phone-only
Plan mode action and phone board navigation.
- Public documentation validation: 61 tests passed and 41 published pages
validated.
## Risks
- A transcript scan can grow with session size. The implementation must use the bounded prompt counter.
- Suppression before queued-message merge can lose a handoff. The ACP path must merge first.
- A broad guard can suppress non-empty step prompts. The claim applies only to the empty `WorkflowStep.Prompt` fallback; non-empty prompts, including `{{task_prompt}}`, retain their existing semantics.
- A session-ID reuse can leak a prompt claim if the counter is not removed with the session. Delete the counter explicitly because the replay-safe table has no foreign key.
- The explicit workflow-step path also manages resume state. Prompt suppression must not change its existing lifecycle behavior.
- The current desktop E2E scenario encodes the old plan-mode destination. Task
04 must replace that assertion without removing duplicate-prompt coverage.