Proposed spec version: FAB-PLUGIN-SPEC-v2.11.0 Previous: FAB-PLUGIN-SPEC-v2.10.0 Date: 2026-03-27 Author: Claudia (Operations Director) Status: PROPOSED — awaiting Rupert approval Revision: R2 — incorporates Rupert's feedback
Add a new acceptance status between agent_done and done. After orchestrator validation, tasks enter a human review gate where the user can Accept or Refuse the work.
Current flow:
agent_done → [orchestrator validates] → done
New flow:
agent_done → [orchestrator validates] → acceptance → [human accepts] → done
→ [human refuses] → agent_done (+ rework subtask)
| From | Current targets | New targets | Change |
|---|---|---|---|
agent_done |
draft, |
draft, acceptance, assigned, cancelled | done replaced by acceptance; planning removed (was never valid — corrected) |
acceptance (new) |
— | done, agent_done, cancelled | New row. Only human can set done or cancelled. |
| All others | (unchanged) | (unchanged) | — |
Key rules:
agent_done → doneis removed. The only path todonefromagent_doneis throughacceptance.agent_done → planningis removed (was invalid historically — corrected now).acceptance → draftandacceptance → planningare not allowed. Acceptance is a human gate — only Accept (→ done), Refuse (→ agent_done), or Cancel (→ cancelled) are valid.acceptance → cancelledis human-only — only a human caller can cancel a task in acceptance.
draft → planning → assigned → in_progress → blocked → agent_done → acceptance → done → archived → cancelled
acceptance appears as a new filter pill in the dashboard status bar, alongside existing statuses. Tasks in acceptance are visible in their own Kanban column.
When the Accept or Refuse button is clicked, the task disappears immediately from the acceptance column (optimistic UI update before server confirmation).
After orchestrator validates a task, it sets status: "acceptance" instead of status: "done".
// Before:
fab_task({ status: "done", validationNote: "..." })
// After:
fab_task({ status: "acceptance", validationNote: "..." })When status === 'acceptance', show two action buttons:
- ✅ Accept (green/success style) — calls
POST /api/tasks/:id/accept - ❌ Refuse (red/danger style) — calls
POST /api/tasks/:id/refuse
Both buttons hidden for all other statuses.
Sets status: "done". Logs audit event with action: "acceptance-accepted", caller: "human".
- Sets
status: "agent_done" - Adds subtask: "Task not accepted by the user. Rework with the agent"
- Logs audit event with
action: "acceptance-refused",caller: "human" - A validator task is created for the orchestrator to analyse the refusal reason and add specific rework subtasks for the agent
No change. Still promotes fully-done tasks to agent_done. The acceptance status is downstream.
No change to dispatch logic. acceptance is not a dispatchable status (dispatch only picks up assigned).
Must exclude acceptance from stale detection. Human review may take hours or days — this is not a stale task.
Add acceptance to the excluded statuses list alongside done, archived, cancelled.
Update the validation section: orchestrator sets acceptance instead of done after validating agent_done tasks.
acceptance is a new valid status value. The Slack List column mapping handles it as text — no schema change needed, just a new value in the status column.
No change to the trigger condition — it watches for status = 'done', which now only happens after human acceptance. This is correct: PRs should only merge after the human has accepted the work.
When the human clicks Refuse:
- Dashboard API sets
status: agent_done+ adds subtask: "Task not accepted by the user. Rework with the agent" - Completion detector sees
agent_donewith incomplete subtasks → no action (waits for subtask completion) - Orchestrator creates a validator task to analyse the refusal reason and determine what needs fixing
- Validator adds detailed rework subtasks explaining what needs fixing by the agent
addSubtaskson anagent_donetask triggers re-dispatch to the agent (existing behaviour)- Agent reworks → marks subtasks done → completion detector →
agent_done→ orchestrator validates →acceptance→ human reviews again
Loop breaker: Existing MAX_VALIDATION_SPAWNS = 5 cap prevents infinite cycles.
| Action | Who can do it |
|---|---|
Set acceptance |
Orchestrator only (after validation) |
| Accept (→ done) | Human only (via dashboard) |
| Refuse (→ agent_done) | Human only (via dashboard) |
| Cancel from acceptance | Human only (via dashboard) |
Agents and orchestrator cannot Accept, Refuse, or Cancel tasks in acceptance status.
| Case | Handling |
|---|---|
Task stuck in acceptance for days |
Stale sweep ignores it — human-paced |
| Human wants to cancel during acceptance | acceptance → cancelled is valid (human-only) |
| Orchestrator wants to demote from acceptance | Not allowed — acceptance → draft/planning are invalid transitions |
| Multiple refusal cycles | MAX_VALIDATION_SPAWNS (5) caps retries |
| Refuse without explanation | Rework subtask is still added with generic text; validator will add specifics |
No migration required. The status column is TEXT — adding a new value needs no DDL change. The TypeScript Task type union needs updating.
- State machine:
acceptancetransitions (to done, to agent_done, to cancelled — all valid) - State machine:
acceptance → draftis rejected - State machine:
acceptance → planningis rejected - State machine:
agent_done → doneis rejected (must go through acceptance) - State machine:
agent_done → planningis rejected (corrected historical error) - Refuse handler: adds rework subtask + sets agent_done
- Accept handler: sets done
- Permission: non-human caller cannot Accept or Refuse
acceptancestatus appears as filter pill in dashboardacceptancecolumn visible in Kanban board- Accept button visible only on
acceptancetasks - Refuse button visible only on
acceptancetasks - Accept click → task disappears immediately from acceptance column, moves to done
- Refuse click → task disappears immediately from acceptance column, moves to agent_done with rework subtask
- Refuse without explanation → generic rework subtask added
- Refuse with explanation → explanation captured
- Buttons hidden on all other statuses (negative test)
- Multiple refusal cycles work correctly (refuse → rework → agent_done → acceptance again)
- Cancel from acceptance → task moves to cancelled
| File | Change |
|---|---|
src/task/handlers/update.ts |
VALID_TRANSITIONS: add acceptance row; agent_done targets: remove done + planning, add acceptance; add permission check for acceptance actions |
src/task/db.ts |
Task type: add acceptance to status union |
src/task/services/spawn-validation.ts |
Set acceptance instead of done |
src/task/services/stale-sweep.ts |
Exclude acceptance from stale detection |
packages/dashboard/src/types/fab.ts |
Add acceptance to status type |
packages/dashboard/src/components/TaskDetailSidebar.tsx |
Accept/Refuse buttons |
packages/dashboard/src/components/KanbanBoard.tsx |
New acceptance column |
packages/dashboard/src/api/fab.ts |
New API calls for accept/refuse |
scripts/dashboard-server.mjs |
New endpoints: /api/tasks/:id/accept, /api/tasks/:id/refuse |
skills/task-orchestrator/SKILL.md |
Update validation flow |
tests/task/handlers/update.test.ts |
New transition tests (including negative tests) |
New: tests/task/acceptance.test.ts |
Accept/refuse handler + permission tests |
New: packages/dashboard/e2e/acceptance.spec.ts |
E2E tests (multiple scenarios) |
On deployment, run a one-time migration:
Set all tasks currently in done status to acceptance so that Rupert can review them all again.
This is a deliberate retroactive review — all previously-completed tasks pass through the new human acceptance gate.
UPDATE tasks SET status = 'acceptance' WHERE status = 'done';Log this in task_log for each affected task with action: "migration-to-acceptance".
v2.10.0 → v2.11.0 (minor bump — additive status, no breaking schema change)