Worker Operations
Manual controls for when automation needs help. Find the right lever, understand its consequences, and get back on track.
Manual Worker Controls
The Daemon interface exposes six operations for per-worker control. These are wired through
Server Actions in src/app/actions.ts and surfaced as buttons on the board.
They cover pausing, restarting, canceling, force-starting, merging, and retrying.
| Operation | Status Required | What It Does | Worktree Preserved? |
|---|---|---|---|
pauseWorker(id) |
Any running status | Sets Worker.status = "paused". Current tool call completes before stopping. |
Yes |
restartWorker(id) |
Any | Cancels the current runner and re-dispatches from the current phase. | Yes |
cancelWorker(id) |
Any | Kills the agent PID, sets status to "failed". Issue stays open. |
Yes |
startWorker(id) |
pending |
Claims and starts a specific issue immediately, bypassing queue ordering. Guards + base CI must be green. | Created on start |
mergeWorker(workerId) |
waiting_merge |
Manually triggers merge when autoMergeMode = false. Same preconditions as auto-merge. |
Removed on merge |
retryIssue(repoId, issueNumber, issueSource?) |
Worker in failed |
Cleans up old worker row and creates a new one. Fresh start for the same issue. | Cleaned up first |
Sets Worker.status = "paused" but does not immediately kill the agent process.
The running agent completes whatever tool call it is currently executing, then stops before dispatching the next one.
The worktree is left intact with all changes so far.
When to use: the worker appears to be exploring the wrong area and you want to inspect the
worktree before more changes accumulate. After inspecting, call restartWorker or resume from the board to continue.
Cancels the current runner (the active agent process for this worker) and immediately re-dispatches from the worker's current phase. The worktree is preserved, so the agent resumes with whatever changes were already made.
When to use: the worker is stuck in a loop, making repeated failed attempts within a phase, or the agent is clearly going off the rails and a fresh prompt dispatch from the same phase is more likely to produce a good result.
Permanently terminates the worker. Kills the agent PID and sets Worker.status = "failed".
The worktree is NOT removed. Files the agent wrote are preserved on disk for inspection.
The GitHub issue is NOT closed. It stays open so you can use retryIssue to start fresh after diagnosing the problem.
When to use: the worker has produced something clearly wrong and further progress is not recoverable. Inspect the worktree, fix the underlying cause, then retry.
Manually claims and starts a specific issue, bypassing the normal tick-queue ordering. Two preconditions must pass: the issue's guards must be satisfied, and base CI on the target repo must be green. If either check fails, the operation is rejected.
When to use: a high-priority issue should not wait for the queue, or you have verified manually that all conditions are met and want to force immediate dispatch.
Manually triggers the merge step for a worker in waiting_merge status. This status is reached
when autoMergeMode = false - the worker finishes implementation and waits for operator approval
rather than merging automatically. The same preconditions as auto-merge apply: guards must pass and base CI must be green.
When to use: you have reviewed the PR yourself and are satisfied, but the PR review verdict
did not reach the configured acceptedReviewLevel threshold, or auto-merge is disabled globally.
Creates a new worker for an issue that previously failed. The old Worker row (in failed status)
is cleaned up first, including its worktree. The issueSource parameter distinguishes GitHub issues from
internal tracker issues; it defaults to GitHub if omitted.
When to use: after diagnosing and fixing whatever caused the original failure - a flaky test environment, a broken dependency, a bad prompt - use this to queue the issue again without going through the board UI.
Admin Operations
Two nuclear-option operations in src/server/daemon/ops/admin-ops.ts.
Both bypass the normal worker lifecycle. Use them when normal controls are not enough.
Force-advances a stuck or failed worker to "merged" status without actually merging a PR on GitHub.
This is a local state override only.
- Stamps
Worker.status = "merged" - Kills the runner if one is still active
- Removes the worktree from disk
- Closes the associated internal issue (if source is the internal tracker)
- Does not close the GitHub issue -- do this manually
- Does not merge the PR on GitHub -- do that separately if needed
git)
and Slop's state is stale; or the worker is irrecoverably stuck and you want to mark it done and move on.
Full local state reset. Deletes all operational data and resets all configuration to defaults.
Exposed as POST /api/admin/wipe and via daemon.wipeDatabase().
- All
Workerrows - All
Runrows - All
Eventrows - All
ReadyIssuerows (the ready queue) - All
ReadyOrderrows - All
IssueConfigrows - All
RepoSnapshotrows - All config key values (reset to defaults)
autoMode,autoMergeMode,autoReviewMode,autoAddressModeset tofalse
Reporows (watched repo registrations)GITHUB_TOKENin config
slop.db (or the file named by DATABASE_URL) before running this if the data matters.
Recommended use: starting fresh after a bad experiment, or clearing a corrupted state.
PR Verdict Parsing and the Auto-Merge Gate
Before auto-merging a PR, the daemon checks a verdict written by the /pr-review harness skill.
The verdict is a specially formatted comment on the GitHub PR. The daemon reads it via
getLatestPrReviewVerdict() in src/server/github/client.ts.
Verdict Levels
The regex scans PR review comments for the pattern **Verdict: <level>**.
Four levels are recognized, ordered from most to least permissive:
| Verdict | Color signal | Meaning |
|---|---|---|
| pass | Green | Clean implementation, no findings. |
| approve_with_comments | Green | Minor findings noted, none are blocking. |
| approve_with_nitpicks | Yellow | Nitpicks only; all can be deferred to a follow-up issue. |
| request_changes | Red | Substantive findings that must be addressed before merge. |
The acceptedReviewLevel Config Key
The acceptedReviewLevel configuration key sets the minimum verdict required for the daemon to proceed
with auto-merge. Any verdict at or above the threshold triggers merge; verdicts below it block merge and leave the
worker in waiting_review.
| acceptedReviewLevel setting | pass | approve_with_comments | approve_with_nitpicks | request_changes |
|---|---|---|---|---|
"pass" |
Merge | Block | Block | Block |
"approve_with_comments" |
Merge | Merge | Block | Block |
"approve_with_nitpicks" |
Merge | Merge | Merge | Block |
Commit Hash Validation
Before accepting a verdict, the daemon verifies that the verdict was posted against the PR's current HEAD
commit SHA. getLatestPrReviewVerdict() returns { verdict, commitId }. The daemon then fetches
the live PR HEAD. If the SHAs do not match, the verdict is stale and rejected.
abc123. The /pr-review skill runs and posts Verdict: pass against abc123./fix-ci or /address-comments) force-pushes to the branch. HEAD is now def456 - code that was never reviewed.def456. This is wrong.abc123 != def456, so the verdict is rejected. The worker waits in waiting_review until a new review runs against def456./fix-ci, /address-comments, or manual intervention -
invalidates the current verdict and triggers a new review cycle. This is intentional: the merge gate is only as strong
as the last review that matches what will actually land.
Completion Reports
Infrastructure stub for future analytics
src/server/daemon/completion-report.ts subscribes to worker.completed events
on the internal event bus. The intended behavior (not yet implemented) is to aggregate metrics per completed
worker: total wall-clock duration, number of Run rows, total token cost across all runs, and final status.
Currently the subscriber is a no-op - it receives events and discards them. Nothing is published downstream
and nothing is stored beyond what already exists in the Worker and Run tables.
The subscription wiring is already in place, so enabling the feature requires only adding logic inside the subscriber without touching the daemon's core poll cycle or event bus setup.