4.2 KiB
ADR-2026-07-28-visible-wip-overflow-queues: Separate Visible Queueing From WIP Admission
Status: accepted Date: 2026-07-28 Area: backend, frontend, protocol, workflow
Context
Kandev originally treated a workflow step's WIP limit as a hard cap on resident task cards. Atomic creation admission fixed overfilling, but it also caused integration fan-out beyond the cap to remain invisible until a later retry. That behavior conflates two separate concerns: accepting and displaying work, and admitting work for active processing.
A feeder step already models an explicit backlog for a limited destination.
Not every workflow has or wants a separate feeder, however. A two-column
Review -> Done workflow should still be able to display seven discovered
reviews while limiting active review work to two.
The design must remain generic across UI, HTTP, WebSocket, MCP, and integration creation. It must also survive restarts without preparing runtime resources for every queued task.
Decision
WIP limits govern admitted tasks, not total visible cards.
When creation targets a full limited step:
- If the destination configures
pull_from_step_id, Kandev places the new task in that feeder and records the intended destination. - If it has no feeder, Kandev places the task in the destination itself but marks it queued and non-admitted.
- If the configured feeder is itself full, Kandev stops after that one hop and returns a capacity conflict. It does not recursively traverse feeder chains.
Queue and admission state are durable task-domain data. A queued create-and-start request also stores a durable launch intent; it does not create a session, workspace, checkout, container, or agent process until promotion. Promotion is transactional, deterministic, idempotent, and reconciled after capacity-changing events and backend startup.
Destination-tagged overflow tasks cannot be consumed by another destination that shares the same feeder. Untagged feeder tasks retain the existing generic pull behavior.
Manual moves and workflow-engine transitions into a full step continue to return capacity conflicts. This decision changes creation overflow only.
That move rule is replaced by ADR-2026-08-12-queue-task-moves-at-wip-capacity. The creation and feeder rules in this decision remain accepted.
Consequences
- All accepted integration work is immediately visible and durable.
- A limited column may contain more cards than its limit; the UI must distinguish
admitted WIP from queued cards and show
admitted/limit. - WIP is no longer derivable from raw resident task count. Repository admission, moves, promotion, migration, and recovery must use explicit admission state.
- Deferred agent starts require a durable, idempotent launch-intent boundary.
- Workflows with feeders retain an explicit backlog presentation. Compact workflows without feeders gain an in-column queue.
- A full configured feeder can still reject creation. The one-hop rule is predictable but remains the draft assumption to revisit if real workflows require recursive routing.
Alternatives considered
- Keep rejecting task creation at capacity. Rejected because work discovered by integrations remains invisible and depends on polling for eventual representation.
- Always create overflow in a hidden system queue. Rejected because it removes work from the user's workflow and adds a second, implicit task location model.
- Allow every resident task to count as WIP but throttle only sessions. Rejected because workflow WIP and agent-profile execution concurrency are distinct controls, and non-agent tasks also require queue semantics.
- Always overfill the destination without explicit queue state. Rejected because the system could not determine which tasks may auto-start or promote safely after restart.
- Recursively walk feeder chains. Deferred because it makes task placement depend on graph traversal and introduces less predictable authoring, ordering, and cycle behavior.
- Redirect manual moves into the queue. Rejected for this change because it would silently change an explicit drag/drop action from a conflict into a successful but non-active move.