Files
kandev/docs/specs/prevent-agent-autostart-on-open/spec.md
T

208 lines
11 KiB
Markdown

---
status: draft
created: 2026-08-11
updated: 2026-08-13
owner: platform
---
> Follow-up (2026-08-13): for recovered-idle sessions the in-chat Start agent
> button was replaced by a hint in the chat composer ("sending a message will
> auto-start the agent"), because the button rendered at the bottom of the
> transcript and could be clipped when the transcript was not scrolled all the
> way down. The never-started (CREATED) task-description Start agent button is
> unchanged. The sections below describe the current behavior.
# Prevent Agent Auto-Start On Open
## Why
Opening a task after a kandev restart silently starts (or resumes) the agent:
a session in a normal active state with no running process is resumed the
moment its task page loads, and a task in the final workflow step can have a
fresh agent auto-started when the step's on-enter action allows it. Users who
want to inspect the task first, or tasks that are effectively done, get an
agent spinning up unprompted. There is no way to keep the agent stopped on
open and start it explicitly instead.
## What
- A new per-user setting, `prevent_auto_start_agent_on_open` (default `false`),
surfaced in Settings → Task Actions as a switch.
- When the setting is ON, opening a task whose session exists but whose agent
is not running (the post-restart / recovered-idle shape) MUST NOT
automatically resume the agent. The task opens with the agent stopped and
the chat composer shows a hint that sending a message will auto-start the
agent; sending a message starts it (the explicit-user-action rule below).
The Start agent button is NOT shown for recovered-idle sessions: the
composer hint replaces it (a footer button could be clipped when the
transcript is not scrolled to the bottom). For FAILED-but-resumable
sessions the existing recovery actions (Resume session / Start fresh
session buttons) remain the manual affordance; the hint is not rendered for
FAILED sessions.
- When the setting is ON, opening a task whose current workflow step is the
final step of its workflow MUST NOT auto-start the agent when the task has
no session. The session is created in the never-started (prepared) state
and the Start agent button is shown under the task description. This button
is unchanged: a task that was explicitly never started keeps its explicit
Start agent affordance (no hint replaces it, because the CREATED transcript
is short and the button cannot be clipped).
- The composer hint is a single informational line above the chat input,
shown exactly when the recovered-idle session is stopped and no other
affordance owns the surface: it is hidden while the session is FAILED
(recovery actions own the surface), while it is STARTING or RUNNING, and
when recovery actions are already visible in the transcript. The hint has
no click action; it only communicates that a message auto-starts the
agent.
- "A message auto-starts the agent" covers every message that reaches the
session's message queue, not only the user's composer sends: messages
received from other tasks (`message_task_kandev`, e.g. a sub-task
reporting back) are queued the same way and the queue dispatch resumes a
stopped-but-resumable agent through the same
`ensureSessionRunning` path. The preference never gates message-driven
starts — it only gates starts triggered by *opening* the task.
- When the setting is OFF (default), opening a task behaves exactly as today.
- The setting gates only automatic starts triggered by *opening* a task. It
MUST NOT affect: workflow step transitions whose on-enter actions include
`auto_start_agent`, explicit user actions (Start agent, Resume, sending a
message), or watcher/automation/PR-triggered starts.
## Data model
The setting lives in the existing per-user settings blob (the `settings`
column of the `users` table, serialized as JSON). No schema migration.
```
users.settings.prevent_auto_start_agent_on_open bool default false
```
It is read by the web SPA from the boot payload and the `GET
/api/v1/user/settings` response, and written via `PATCH /api/v1/user/settings`.
## API surface
### User settings
- `GET /api/v1/user/settings` returns the field as
`prevent_auto_start_agent_on_open` inside `settings`.
- `PATCH /api/v1/user/settings` accepts an optional
`prevent_auto_start_agent_on_open: bool`; omitted means "leave unchanged".
- The SSR boot payload exposes it as `preventAutoStartAgentOnOpen` (camelCase),
matching the other boot-payload settings keys.
### WebSocket `session.ensure`
The `session.ensure` WS request gains an optional `auto_start: bool` override.
- Request shape: `{ task_id: string, auto_start?: boolean }` (plus the
existing optional fields).
- When `auto_start` is explicitly `false`, the backend creates the session in
the workspace-only (prepared / `created_prepare`) mode even when the task's
workflow step would otherwise allow auto-start (`created_start`). The
session is created in the never-started (`CREATED`) state.
- When `auto_start` is absent or `true`, the current behavior is unchanged:
the backend decides start-vs-prepare from the resolved agent profile and the
step's `auto_start_agent` on-enter action.
- The response shape is unchanged.
## State machine
No session states are added or removed. The setting changes only whether an
auto-start or auto-resume is *triggered by opening the task page*:
| Trigger | Setting OFF (default) | Setting ON |
|---|---|---|
| Open task, no session, step allows auto-start | agent starts (`created_start`) | non-final step: unchanged; final step: `created_prepare`, Start agent button shown |
| Open task, session exists, agent not running, session resumable | auto-resume (`session.launch` intent=resume) | no auto-resume; session stays stopped; the chat composer shows a hint that sending a message will auto-start the agent |
| Open task, agent already running | no-op | no-op (unchanged) |
| Workflow step transition with `auto_start_agent` on-enter | agent starts | agent starts (unchanged) |
| User clicks Start agent / Resume / sends a message | agent starts | agent starts (unchanged) |
| Message received from another task (sub-task via `message_task`), agent stopped | agent resumes (queue dispatch runs `ensureSessionRunning`) | agent resumes (unchanged; message-driven starts are never gated by the preference) |
## Failure modes
- The kanban workflow steps are not (yet) loaded in the client store when the
final-step check runs: the task is treated as "not in the final step", so
the ensure gate does not apply and behavior matches the setting OFF state.
Safe default: never silently block a start because of missing metadata.
- The user settings fail to hydrate (SSR fallback): the setting reads `false`
and behavior matches today.
- `session.ensure` with `auto_start: false` on a task whose agent profile
cannot be resolved: the same prepare-only outcome as today's no-profile
path; no error is surfaced beyond the existing behavior.
## Persistence guarantees
The setting is per-user, stored in `users.settings` (JSON blob serialized by
`internal/user/store/sqlite.go`), and survives kandev restarts. It is hydrated
into the web boot payload on every page load. There is no per-workspace or
per-task variant in this feature.
## Scenarios
- **GIVEN** the setting is ON and a task's session is in an active state with a
resume token but no running agent (post-restart shape), **WHEN** the user
opens the task page, **THEN** no `session.launch` resume request is sent on
open, the session stays stopped, and the chat composer shows a hint that
sending a message will auto-start the agent; sending a message starts the
agent.
- **GIVEN** the setting is ON and a task's session is FAILED but resumable,
**WHEN** the user opens the task page, **THEN** no auto-resume is sent on
open and the session keeps the existing recovery actions (Resume session and
Start fresh session buttons). The composer hint is not rendered for FAILED
sessions.
- **GIVEN** the setting is ON and the task's current workflow step is the final
step, **WHEN** the user opens the task and it has no session, **THEN**
`session.ensure` is called with `auto_start: false`, the session is created
in the prepared (`CREATED`) state, and the Start agent button is shown
without any agent process starting.
- **GIVEN** the setting is ON and the task's current workflow step is NOT the
final step, **WHEN** the user opens the task and it has no session, **THEN**
auto-start behavior is unchanged (the agent starts when the step's on-enter
action allows it).
- **GIVEN** the setting is OFF (default), **WHEN** the user opens any task,
**THEN** today's auto-start and auto-resume behavior is unchanged.
- **GIVEN** the setting is ON and a task in the final step already has a
never-started (`CREATED`) session, **WHEN** the task is opened, **THEN** the
Start agent button is shown under the task description and no agent starts.
- **GIVEN** a recovered-idle session whose composer hint is visible, **WHEN**
a message is received from another task (e.g. a sub-task via
`message_task_kandev`), **THEN** the agent resumes automatically (the
message is queued and the queue dispatch runs `ensureSessionRunning`) and
the hint disappears once the session reports STARTING/RUNNING.
- **GIVEN** a recovered-idle session whose composer hint is visible, **WHEN**
the session state becomes STARTING or RUNNING (e.g. the user sent a
message), **THEN** the hint disappears.
- **GIVEN** a recovered-idle session, **WHEN** recovery actions are already
visible in the transcript, **THEN** the composer hint is hidden (recovery
owns the surface).
- **GIVEN** the setting is ON, **WHEN** the user explicitly starts or resumes
the agent (Start agent button, session menu Resume, or sending a message),
**THEN** the agent starts normally.
- **GIVEN** the setting is ON, **WHEN** a workflow step transition with
`auto_start_agent` in its on-enter actions moves the task into that step,
**THEN** the agent still auto-starts.
- **GIVEN** the setting is ON and a task is opened in the kanban preview
panel, **WHEN** the preview opens, **THEN** the same gates apply (no
auto-resume, no final-step auto-start).
## Out of scope
- The office board task pages (simple and advanced mode). They do not
auto-start or auto-resume agents on open today (`useEnsureTaskSession` and
`useSessionResumption` are not wired into those flows); the latent
`ensure_execution` gap in the `session.ensure` WS handler is left as-is.
- Watcher / automation / PR-triggered auto-starts. They are not "opening a
task".
- Workflow on-enter `auto_start_agent` transitions. Only open-time behavior is
gated.
- Server-side enforcement of the preference for non-web clients. The gate is
implemented by the web client not issuing the resume/start request; the
backend contract change is an opt-in request override.
- A per-workspace or per-workflow variant of the setting.
## Open questions
- Whether the option should also gate the office advanced mode's
`ensureExecution` resume once that WS field is actually wired. Currently a
no-op; intentionally out of scope until the office flow auto-starts.