JAM-40 task lifecycle hardening implementation
JAM-40 task lifecycle hardening implementation
Findings summary
Root cause was not one bug. It was a stack of conflicting write paths:
- UI status moves used full-array writes. Board moves and arrow moves rewrote the entire
tasks.jsonlist throughPUT /api/tasks, so a status change had no canonical server-side lifecycle path. - No lifecycle gate existed server-side. Any client that could write the full list could jump a task to any status, regardless of the intended workflow.
- No read-after-write verification existed before success. A write could be reported as successful without confirming the persisted task state.
- Chat guidance still told agents to mutate status by rewriting the full array. That kept the same reliability risk alive outside the UI.
- Task capture vocabulary and defaults still carried legacy terms.
nextandblockedstill showed up in capture flows, and new captures defaulted tonext, which front-loaded prioritization instead of durable intake.
Implementation shipped
1) Canonical server-side lifecycle transition helper
Implemented in mission-control/lib/tasks-store.ts:
- Added
transitionTask(...)as the canonical lifecycle mutation path. - Added explicit allowed transition graph:
backlog -> up-nextup-next -> backlog | in-progressin-progress -> needs-reviewneeds-review -> in-progress | done- Invalid moves now fail closed with
Invalid transition: <from> -> <to>. - Transition writes now run under the task-store lock.
- After each transition write, the store re-reads
tasks.jsonand verifies the persisted task matches the intended post-transition state before returning success. needs-reviewtransitions auto-assign to Pete.
2) Targeted task mutation APIs
Added targeted routes so normal task edits no longer need full-array PUTs:
PATCH /api/tasks/[taskId]DELETE /api/tasks/[taskId]POST /api/tasks/[taskId]/transition
Notes:
PATCHuses the canonical lifecycle helper whenstatuschanges.POST /transitionis the dedicated status-change path for UI and chat flows.- The old bulk
PUT /api/tasksstill exists for bulk reorder/save cases, but it is no longer the normal lifecycle path.
3) UI status changes now use the canonical path
Updated mission-control/components/tasks/TasksWorkspace.tsx:
- Board status moves now call
POST /api/tasks/[taskId]/transition. - Left/right lifecycle moves now call the same transition endpoint.
- Task editor save now uses targeted
PATCH /api/tasks/[taskId]for edits, which routes status changes through the canonical helper. - Create uses
POST /api/tasksand delete usesDELETE /api/tasks/[taskId]. - UI now applies server-returned task lists instead of assuming a local optimistic full-array save was durable.
4) Chat/task operational model hardened
Updated /Users/vinny/.openclaw/workspace/AGENTS.md:
- Added a mandatory task-vs-chat boundary section.
- Default is now chat-native unless durable work is clearly present.
- Agents are told to create/update Mission Control tasks when work is multi-turn, async, delegated, deliverable-based, or needs later resumption.
- Task lifecycle section now explicitly forbids normal status moves via full-array task rewrites.
- The
move [task] to [column]workflow now uses the new transition helper script instead of raw full-array PUT.
5) Chat move helper added
Added mission-control/scripts/transition-task.mjs:
- Resolves task by
--id,--display-id, or--match. - Calls
POST /api/tasks/[taskId]/transition. - Only reports success after the server returns a verified persisted transition.
- Lists ambiguous matches instead of guessing.
6) Vocabulary alignment and intake default fix
Updated:
mission-control/scripts/capture-task.mjsskills/mission-control-task-capture/scripts/capture-from-chat.mjsskills/mission-control-task-capture/SKILL.md
Changes:
- Canonical statuses in docs and help are now:
backlogup-nextin-progressneeds-reviewdone- Legacy aliases still work:
next->up-nextblocked->backlogplusblocked=true- New task capture defaults now use
backlog, notnext.
This aligns capture with intake instead of implicit prioritization.
Files changed
Workspace guidance / capture
/Users/vinny/.openclaw/workspace/AGENTS.md/Users/vinny/.openclaw/workspace/skills/mission-control-task-capture/SKILL.md/Users/vinny/.openclaw/workspace/skills/mission-control-task-capture/scripts/capture-from-chat.mjs/Users/vinny/.openclaw/workspace/mission-control/scripts/capture-task.mjs
Mission Control code
/Users/vinny/.openclaw/workspace/mission-control/lib/tasks-store.ts/Users/vinny/.openclaw/workspace/mission-control/app/api/tasks/route.ts/Users/vinny/.openclaw/workspace/mission-control/app/api/tasks/[taskId]/route.ts/Users/vinny/.openclaw/workspace/mission-control/app/api/tasks/[taskId]/transition/route.ts/Users/vinny/.openclaw/workspace/mission-control/components/tasks/TasksWorkspace.tsx/Users/vinny/.openclaw/workspace/mission-control/scripts/transition-task.mjs
Validation
Build
npm run buildin/Users/vinny/.openclaw/workspace/mission-controlpassed.
Service restart
npm run service:restartcompleted.npm run service:statusshows LaunchAgent running.curl -sf http://localhost:3100/api/tasksreturned success.
Live lifecycle validation against localhost
Temporary validation tasks were created, transitioned through the live API, then deleted after validation.
Task A
JAM-41created inbacklogbacklog -> up-next: HTTP 200up-next -> in-progress: HTTP 200in-progress -> needs-review: HTTP 200, assignee persisted asPeteneeds-review -> done: HTTP 200
Task B
JAM-42created inneeds-reviewneeds-review -> in-progress: HTTP 200in-progress -> needs-review: HTTP 200, assignee persisted asPete
Invalid transition guard
JAM-43created inbacklogbacklog -> done: HTTP 409 withInvalid transition: backlog -> done
Chat move helper validation
- Ran
node /Users/vinny/.openclaw/workspace/mission-control/scripts/transition-task.mjs --display-id JAM-43 --status up-next - Script reported
Transition verified: JAM-43 -> up-next
Targeted PATCH path validation
- Patched
JAM-43throughPATCH /api/tasks/[taskId]with: - title change
- description change
- assignee change to
David status: in-progress- Response returned HTTP 200 with persisted
status=in-progress
Cleanup
- Temporary validation tasks
JAM-41,JAM-42, andJAM-43were deleted after validation.
Deferred work
Nothing blocking the JAM-40 approval criteria remains.
Non-blocking follow-up if desired:
- Move any remaining reorder-only flows off bulk
PUT /api/tasksas a second pass. - Add automated integration tests around lifecycle transitions and invalid move rejection.
- Consider surfacing invalid transition errors more explicitly in the board UI affordances.
Shipped vs deferred
Shipped
- Canonical server-side lifecycle transition helper
- UI status changes routed through canonical path
- Read-after-write verification before lifecycle success
- Chat move flow routed through canonical path
- Task-vs-chat guidance update
- Status vocabulary alignment in capture flows
- Live validation and service restart
Deferred
- Non-lifecycle reorder refactor
- Automated test suite coverage for these cases
- Extra UI polish around invalid move affordances
Ready for review
Yes.