Agent Orchestration
Every LLM session in Slop - whether dispatched by the daemon for an issue worker or triggered on-demand from the console - runs through one shared engine: src/server/runs/driver.ts (runSkillSession). The daemon and UI share a single abstraction; run kind and trigger context differ.
The Two Dispatch Paths
Two paths converge on the same driver. The autonomous path is daemon-driven and multi-phase; the on-demand path is a single skill invocation from the UI.
Daemon tick โโ claimWorker() โ Worker row (status: claimed) โโ spawnRunner(workerId) โ runWorker() โโ Phase 1: implement โ runImplementSession() โโ Phase 2: verify โ runVerifySession() โโ Phase 3: conflict? โ runConflictResolution() โโ Phase 4: CI fail? โ runCiFix() โโ Phase 5: PR? โ runPrReview() / runAddressReview() โโ Phase 6: ship โ ShippingStrategy.finish()
User clicks "Run skill" in console โโ Server Action: runSkillRun(skillName, args, repoId?) โโ SkillRunRegistry.start() โโ runSkillSession({ cwd: repo checkout, prompt: "/skillName args" })
The Driver - runSkillSession
Single entry point for every agent session in the system. Lives at src/server/runs/driver.ts.
| Parameter | Type | Description |
|---|---|---|
cwd | string | Working directory - worktree path for workers, repo checkout for on-demand |
prompt | string | Skill command + arguments, e.g. "/implement-issue reuse-worktree" |
onEvent | (DriverEvent) => void | Stream of driver events (started, message, completed, failed, timeout) |
configRepo | ConfigRepoLike? | Reads global config: model, effort, timeout |
abortSignal | AbortSignal? | Cancellation hook |
timeoutMs | number? | Hard kill timeout. Default: 1h for implement, 20m for verify |
onAgentSpawned | (pid) => void? | Reports OS PID so daemon can reap orphans after restart |
resumeSessionId | string? | Resume a prior session by ID (Claude and Copilot harnesses only) |
overrides | { model?, effort? }? | Per-issue model/effort override - takes precedence over global config |
reapTree | fn? | Custom process reaper (injected in tests) |
copilotClientFactory | fn? | Copilot SDK seam (injected in tests) |
checkAvailability | fn? | Harness availability check seam |
Model โ Harness Resolution
At every dispatch, the driver resolves the model in priority order: overrides.model โ global config โ default "opus". The model string then maps to a harness from the static catalog in src/lib/agent/agent-model.ts.
Environment Filtering
The agent subprocess receives only an explicit allowlist. Everything else (DB URLs, SLOP_SECRET, other secrets) is blocked by default.
System
Locale / Terminal
Auth
SSH / Git
Always Injected
GITHUB_TOKEN and GH_TOKEN are stripped from the Copilot harness env (buildCopilotAgentEnv). A non-entitled GITHUB_TOKEN would shadow the stored OAuth login and block subscription auth. Stripping them lets gh auth cover model authentication.
Security Hooks - PreToolUse
A PreToolUse Bash hook is registered for every driver session. Two rules are enforced:
git stashThe git stash stack is repo-global across all worktrees. A stash/pop in one worktree can corrupt unrelated worktrees. The hook rejects with a detailed error pointing the agent to revert in-place instead.
AskUserQuestionUnattended worker runs inject
UNATTENDED_SYSTEM_PROMPT and treat interactive questions as errors - the agent must operate without human input.
Worker Phases
Each phase creates a Run row and calls the driver. Phases run sequentially. Retries between phases use the normal daemon tick interval (default 30s) - no exponential backoff.
Reads the issue, implements the change, runs tests, commits, optionally opens a PR. The agent runs in an isolated git worktree.
/implement-issuereuse-worktree ยท ultracode ยท internalRuns /verify-gate (which runs /cover + /review internally). Writes a context file to the worktree and parses a verdict from the summary.
/verify-gate.slop-verify-context.json - issue number, docsOnly flag, prior findings, HEAD SHAfindings, not pass - verification fails open-safe.
Triggered when the PR or local branch has conflicts with base after rebase. Budget: maxConflictAttempts (default 5). Exhausted โ status: failed.
/resolve-conflictTriggered when PR CI checks are failing after push. Budget: maxCiAttempts (default 5). Exhausted โ status: failed.
/fix-ci5a Review: Runs /pr-review. Triggered auto when autoReviewMode is on and CI is green, or manually via the Review button. Parses a verdict anchored to the current head SHA - stale verdicts from earlier commits are ignored. Budget: 3 attempts.
5b Address: Runs /address-comments when verdict is request_changes. After addressing, worker returns to waiting_ci for CI to re-run - the loop continues.
Not a Run row - the shipping strategy handles git/GitHub operations directly.
| Mode | Strategy | What happens |
|---|---|---|
local | LocalShippingStrategy | Fast-forward merge into base (rebasing if needed), close issue |
remote | RemoteShippingStrategy | Open PR, arm auto-merge, park in waiting_ci; lifecycle poll drives CI โ merge โ close |
Run Registry - On-Demand Skills
SkillRunRegistry (src/server/runs/skill-run.ts) tracks all user-initiated runs (non-worker) in memory. Returns a runId immediately and runs async.
| Method | Description |
|---|---|
start(skillName, args, overrides?, repoId?) | Starts a skill run, returns runId immediately |
abort(runId) | Aborts a running skill |
awaitDone(runId) | Returns a Promise<void> that resolves when the run finishes |
- slop
- architecture
- security-scan
- perf
- race-hunt
- gotcha-hunt
- suggest-points
- root-cause
- cover
- review
- spec
- research
- issue
- ~/.claude/skills/*
discoverSkills() always scans ~/.claude/skills/ regardless of the configured harness - skills wired for Claude are available even in Codex or Copilot sessions.
Context Passed Into Agent Sessions
Via Prompt String
Skill command + modifier keywords (reuse-worktree, ultracode, internal) + issue number + any user-provided args. Composed at dispatch time.
Via Working Directory
Worker: ~/.slop/worktrees/<owner@name>/<issueNumber>/ - isolated git worktree on a per-issue branch.
On-demand: the watched repo's local checkout (no worktree).
Via Context File
.slop-verify-context.json written to the worktree root before the verify phase. Contains issue number, docsOnly flag, prior findings, and the HEAD SHA from the end of implementation.
Via SLOP_URL
Always injected. Skills that need to read internal issues call GET $SLOP_URL/api/internal-issues?repo=<slug>.
Via UNATTENDED_SYSTEM_PROMPT
Appended to every worker session. Forbids AskUserQuestion, skips interactive interview phases, instructs terse output, overrides any harness instruction that says to ask questions.
Context Handed Off Out of Agent Sessions
Stored on the Worker row. On daemon restart, the driver calls resumeSessionId on the same agent session instead of re-implementing. Claude and Copilot only - Codex never stores a session ID and always restarts fresh from the surviving worktree.
Set the moment the agent subprocess spawns via onAgentSpawned. On daemon restart, any worker whose agentPid is still live is reaped before resuming - no zombie agents accumulate.
Captured live as the agent streams. Includes: summary, filesChanged, durationMs, costUsd, inputTokens, outputTokens, cacheReadTokens, numTurns, implementGateSha.
On-demand runs capture the first 2000 chars of the agent's final output as Run.report. Visible in the Agents tab detail page. The live SSE stream is not capped - full output available while active.
resumeSessionId is set but the resolved harness is Codex (e.g. model changed between restart and resume), the driver throws immediately - the run fails and the worker must be manually restarted.
Status Vocabulary
Worker Statuses
Terminal statuses: merged ยท failed ยท cancelled. Display overrides: issueState === "closed" โ show issue_closed; prState === "merged" โ show merged regardless of internal status.
/implement-issue agent is running/verify-gate agent is running/pr-review agent is running/address-comments agent is running/resolve-conflict agent is running/fix-ci agent is runningfailed label added to issueRun Kinds
Worker implement phase
Worker verify gate
Conflict resolution re-dispatch
CI-failure fix re-dispatch
Base-branch CI fix - standalone worker (no issue link) triggered by operator when base CI is red
PR review (managed or unmanaged)
Address PR comments
LLM story-point scoring
Performance report synthesis
Branch + commit + PR for doc edit
Generic on-demand skill invocation
Named repo-wide audits
Event Flow - Bus โ SSE โ UI
Events flow from the driver through an in-memory bus, get persisted to SQLite, then fan out to the browser via Server-Sent Events. Non-worker/run events (e.g. repo.updated) are broadcast but never persisted.
workerId field is written to the Event table under that worker. Any event carrying a runId is written under that run. Events with neither (e.g. repo.updated) are fan-out only - never persisted.
Memory Monitoring
The daemon's WorkerResourcePoll samples RSS memory each cycle for all live workers.
Sample Process Tree
sampleProcessTree(agentPid) - walks /proc or ps to sum RSS of the agent + all its child processes.
Breach Detection
First breach of memoryThresholdBytes latches Worker.memoryBreachedAt and emits one breach event. Subsequent cycles don't re-emit.
Optional Stop
If stopOnBreach config is set, the worker is stopped on breach.
Diagnostic Issue
Files an LLM-synthesized diagnostic issue in the watched repo (runs/worker-report-issue.ts) containing: peak RSS, breach timeline, and per-process breakdown.
Skill Bootstrap - harness/bootstrap.sh
Run once to wire skills into all three harness homes. Discovers all skills by SKILL.md presence and symlinks each by its directory name. Also backs up conflicting non-symlink targets, prunes stale links, links global config files, and wires Codex/Copilot config.
Skill Categories in harness/skills/
- implement-issue
- verify-gate
- fix-ci
- resolve-conflict
- merge
- issue
- cover
- review
- perf
- security-scan
- race-hunt
- gotcha-hunt
- slop
- research
- spec
- root-cause
- suggest-points
- new-skill
- shape
- bootstrap
- (internal skills)
- BMad workflow integration