Worker Operations Home
Internals · Operator Reference

Worker Operations

Manual controls for when automation needs help. Find the right lever, understand its consequences, and get back on track.

Manual Controls Admin Ops PR Verdict Gate Daemon Interface

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
▮▮
pauseWorker
daemon.pauseWorker(id: string)

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.

restartWorker
daemon.restartWorker(id: string)

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.

cancelWorker
daemon.cancelWorker(id: string)

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.

startWorker
daemon.startWorker(id: string)

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.

mergeWorker
daemon.mergeWorker(workerId: string)

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.

retryIssue
daemon.retryIssue(repoId: number, issueNumber: number, issueSource?: string)

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.

⚠️ overrideToMerged

Force-advances a stuck or failed worker to "merged" status without actually merging a PR on GitHub. This is a local state override only.

What it does
  • 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)
What it does NOT do
  • Does not close the GitHub issue -- do this manually
  • Does not merge the PR on GitHub -- do that separately if needed
When to use: the PR was merged manually outside Slop (via the GitHub UI or git) and Slop's state is stale; or the worker is irrecoverably stuck and you want to mark it done and move on.
🔥 wipeDatabase IRREVERSIBLE

Full local state reset. Deletes all operational data and resets all configuration to defaults. Exposed as POST /api/admin/wipe and via daemon.wipeDatabase().

Deleted
  • All Worker rows
  • All Run rows
  • All Event rows
  • All ReadyIssue rows (the ready queue)
  • All ReadyOrder rows
  • All IssueConfig rows
  • All RepoSnapshot rows
  • All config key values (reset to defaults)
  • autoMode, autoMergeMode, autoReviewMode, autoAddressMode set to false
Preserved
  • Repo rows (watched repo registrations)
  • GITHUB_TOKEN in config
IRREVERSIBLE. There is no undo. The database is modified in place and deleted rows are gone. Back up 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.

The stale verdict scenario (why this check exists)
1
Agent pushes commit abc123. The /pr-review skill runs and posts Verdict: pass against abc123.
2
A subsequent skill run (/fix-ci or /address-comments) force-pushes to the branch. HEAD is now def456 - code that was never reviewed.
3
Without validation: the daemon would see the old "pass" verdict and merge def456. This is wrong.
4
With validation: abc123 != def456, so the verdict is rejected. The worker waits in waiting_review until a new review runs against def456.
Every force-push to a PR branch - whether by /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.