## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The task lifecycle and recovery subsystems decide when agent work is still productive, stalled, or ready for review. > - Existing recovery paths can observe stopped or incomplete work, but there was no first-class per-task watchdog model with scoped review permissions. > - Watchdog follow-ups also need strict boundaries so recovery/status-only runs cannot mutate approvals or perform deliverable work. > - This pull request adds the task watchdog data model, API/service layer, scheduler/review flow, adapter wake context, UI configuration surfaces, and docs. > - The branch has been rebased onto current `paperclipai/paperclip` `master`; the watchdog migration is now ordered after master's latest migrations as `0104_issue_watchdogs`. > - The benefit is a more explicit task-review loop that preserves Paperclip's single-assignee and governance invariants while making stalled work easier to route. ## Linked Issues or Issue Description No linked GitHub issue. Paperclip task: [PAP-11275](/PAP/issues/PAP-11275). ## Problem or motivation Task recovery needs a first-class watchdog path that can inspect stopped work and create scoped follow-ups without bypassing normal task ownership. Board/UI users need a way to configure watchdogs on tasks and see watchdog-related live work. Recovery/status-only runs must remain limited to status reporting and must not create approvals, link approvals, or submit approval comments. ## Proposed solution Add a task-watchdog data model, scheduler/classifier, scoped mutation guard, adapter wake context, API/UI configuration surfaces, and documentation so watchdog agents can review stopped task subtrees under explicit boundaries. ## Alternatives considered Reuse the existing recovery-action flow only. That would keep stopped-work detection implicit, make per-task watchdog assignment harder to expose in the UI, and would not provide a durable scoped-review issue for stalled task trees. ## Roadmap alignment This is Paperclip control-plane lifecycle infrastructure for task execution and recovery. I checked `ROADMAP.md`; this PR does not duplicate an existing planned core item. ## What Changed - Added issue watchdog schema, migration, shared contracts, validators, CRUD API, and service support. - Added task watchdog scheduler/classifier behavior, scoped mutation enforcement, adapter wake context, and default watchdog mandate guidance. - Added UI surfaces for configuring watchdogs on new/existing tasks, viewing watchdog activity, and exposing the experimental setting. - Added docs for the user-facing task watchdog workflow and implementation semantics. - Gated new-task watchdog setup behind `enableTaskWatchdogs` and blocked cheap status-only recovery runs from approval mutations. - Rebased onto current `master` and renumbered the idempotent watchdog migration from the branch-local `0102_issue_watchdogs` slot to `0104_issue_watchdogs`. - Addressed Greptile feedback by loading watchdog classifier input with a recursive subtree query and centralizing the watchdog origin-kind constant. - Added and updated focused server/UI tests for watchdog routes, scheduler/classifier behavior, scope boundaries, live task visibility, settings, and new issue dialog behavior. ## Verification - `pnpm vitest run server/src/__tests__/task-watchdogs-scheduler.test.ts server/src/__tests__/task-watchdogs-classifier.test.ts` - `pnpm vitest run server/src/__tests__/approval-routes-idempotency.test.ts server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts` - `pnpm vitest run ui/src/components/NewIssueDialog.test.tsx` - `pnpm --filter @paperclipai/server typecheck` - `git diff --check` - Verified the PR diff does not include `pnpm-lock.yaml` or `.github/workflows`. ## Risks - Medium risk: this introduces a new task lifecycle surface touching DB schema, server routes/services, adapter wake context, and UI task configuration. - Watchdog scheduling behavior depends on the new experimental setting and runtime context checks behaving consistently across local and production agents. - The watchdog migration is idempotent (`IF NOT EXISTS` / duplicate-object guards) so users who tried the previous branch-local migration number should not get duplicate-object failures. - CI and the second Greptile pass are pending after the latest review-fix push. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex, GPT-5-class coding agent in the Paperclip workspace. Exact runtime model id and context window were not exposed to the agent; tool use and local command execution were enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots — N/A per Paperclip task instruction: do not add screenshots/images to this PR unless they are specifically part of the work. - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
14 KiB
Task Watchdog
Table of contents
- Why it exists
- Mental model
- Configuration
- How a scan works
- What the watchdog agent does
- Scope enforcement
- Origin and badges
- When not to use a watchdog
- Reference
A task watchdog is an agent you assign to verify a stopped issue tree and put it back into motion when stopping was a mistake. You configure it on a single issue, and it watches that issue plus its non-watchdog descendants. When every leaf in that subtree comes to rest — done, cancelled, blocked, in review, or waiting on an interaction — and there is no live continuation path, Paperclip wakes the watchdog agent to read the evidence and decide whether the stop is legitimate.
Watchdogs are opt-in per issue. There is no global "watch everything" mode.
Why it exists
Agents can stop work for the wrong reasons: misreading a blocker, accepting a stale plan confirmation, declaring "done" without proof, leaving an issue in_review with no real reviewer, or running into a recoverable failure and giving up. None of those failures wake anyone on their own — the tree just sits.
A task watchdog gives you a second pass on stopped work without rerunning the original assignee. It is verification-shaped, not execution-shaped: the watchdog reads what other agents claimed, checks it against the evidence in the thread, and either accepts the stop or restores a live path.
It is not an output-silence monitor for active runs. That is a separate mechanism — the silent active-run watchdog described in doc/execution-semantics.md §12. Task watchdog only fires when the whole watched subtree has come to rest.
Mental model
Three concepts share the word "watchdog" inside Paperclip. Keep them separate:
| Concept | What it watches | When it fires |
|---|---|---|
| Task watchdog (this doc) | A configured issue + its non-watchdog descendants | The whole watched subtree has stopped and the stop is new |
| Silent active-run watchdog | A single still-running process | The process has produced no output for the threshold window |
| Liveness recovery | Any agent-owned in_progress issue with no live path |
Stalled work detected during the periodic recovery scan |
The task watchdog is configured by you (or by an agent on your behalf). The other two run automatically on every project.
Configuration
A watchdog has three fields:
| Field | Required | Notes |
|---|---|---|
| Watched issue | yes | The issue you attach the watchdog to. Configured implicitly via the issue you edit. |
| Watchdog agent | yes | Any same-company, invokable agent. Cannot be paused, terminated, or budget-blocked. |
| Instructions | no | Free-form text (trimmed; empty becomes null). Can narrow focus; cannot expand authority. |
A single watched issue holds at most one active watchdog. Re-assigning the agent or editing instructions invalidates the previously reviewed state and forces a fresh evaluation on the next scan.
From the UI
Two surfaces edit the watchdog:
- New issue dialog — the three-dot menu reveals a Watchdog row. Pick an agent and (optionally) type instructions. The chip in the dialog footer shows the chosen agent and a snippet of the instructions. The watchdog is created together with the issue.
- Issue properties — the Watchdog row sits next to Monitor. Empty state reads
Set watchdog; configured state shows the watchdog agent icon and name plus a truncated instructions preview. Click the row to open the editor popover with an agent picker, an instructions textarea ("What should the watchdog watch for and how should it keep work moving?"), a Remove button, and a Set watchdog / Update button. When a watchdog run has produced a child review task, the row shows a small badge linking to that task.
From the API
GET /api/issues/:issueId/watchdog
PUT /api/issues/:issueId/watchdog { "agentId": "...", "instructions": "..." | null }
DELETE /api/issues/:issueId/watchdog
PUT is upsert. DELETE disables the row (it is not hard-deleted; the table keeps the history for audit). All three routes require write access to the watched issue and produce activity records (issue.watchdog_created, issue.watchdog_updated, issue.watchdog_removed) with the run id and actor.
How a scan works
Paperclip runs a watchdog reconciliation tick at server startup, at the end of each heartbeat cycle, and on demand after any mutation that could change the watched subtree (status, blockers, assignment, interactions). The tick is per-company and only walks active rows.
For each active watchdog the tick:
- Walks the watched subtree. Starts at the configured issue and follows
parent_iddownward, excluding every issue whoseoriginKind = 'task_watchdog'and everything below it. This excludes the watchdog's own review tasks so it cannot trigger itself. - Checks for live paths. If any included issue has a live run (
queued,running,scheduled_retry), a queued wake request, or a scheduled retry, the subtree is live and the watchdog does not fire. - Computes a stop fingerprint. A SHA-256 hash over the stopped leaves' identifiers, statuses, blockers, pending interactions, and the current watchdog configuration. The configuration is part of the fingerprint, so changing the agent or instructions invalidates the previously reviewed state.
- Compares against
lastReviewedFingerprint. Match → suppress (the watchdog already saw this exact stopped state). New → proceed. - Ensures a review task exists. Creates (or reopens) one child issue with
originKind = 'task_watchdog'andoriginId = watchedIssueId. Idempotent per watchdog — only one review task is ever live at a time. - Wakes the watchdog agent. Sends a wake with
wakeReason = task_watchdog_stopped_subtree, the stop fingerprint, the leaf summaries, the default mandate, and any custom instructions. The idempotency key is(watchdogId, stopFingerprint), so retries cannot stack duplicate wakes.
When the subtree changes between scans (someone restarts work, adds a blocker, or accepts an interaction) the stop fingerprint changes too, and the watchdog will be woken again for the new state — even if the previous run already disposed of an earlier fingerprint.
What the watchdog agent does
On wake, the watchdog agent reads a fixed default mandate plus your custom instructions. The mandate explicitly tells it to:
- Treat every stopped leaf as a claim that must be verified against comments, documents, work products, screenshots, tests, blockers, and review state. Do not accept "I could not" or "waiting for approval" as automatically valid.
- Leave genuinely-complete leaves alone, with a short note on what was checked.
- If a leaf is not genuinely complete, restore a live path: reopen the issue, reassign, comment actionable instructions, create a follow-up child issue inside the watched subtree, or accept an eligible task-level plan confirmation.
- If the blocker is real, leave a valid waiting disposition that names the unblock owner and the next action.
The mandate also enforces safety constraints that custom instructions cannot override:
- Stay inside the watched subtree. No cross-company mutations, no mutations outside the watched issue and its non-watchdog descendants.
- No impersonating board-only approvals, accepting spend or hiring decisions, accepting security-sensitive interactions, or bypassing execution-policy stages that require a typed reviewer or approver.
- No creating another watchdog for the watched subtree. No waking itself. Exactly one reusable review task per watched issue.
- Custom instructions can narrow focus or veto specific shortcuts. They cannot grant authority the server does not already give the watchdog.
The formal authority contract (the full list of allowed and disallowed mutations, and the eligibility test for accepting plan confirmations) is in doc/SPEC-implementation.md §9.9.
Writing custom instructions
Custom instructions are most useful when they tell the watchdog what evidence to look at and what shortcuts to refuse. Examples:
Before accepting any leaf as done, check that there is a corresponding green CI run linked in the comments. If there isn't, reopen the leaf and ask for one.
Do not accept a
request_confirmationplan that proposes more than five subtasks without first asking me to review. Leave the issue in review and ping me.
If a leaf is blocked on the marketing team, accept the wait but make sure the unblock owner is named in the blocker reason.
What custom instructions cannot do: grant authority outside the watched subtree, approve board-level decisions, expand the interaction kinds the watchdog can resolve, or override safety constraints. The server enforces this regardless of what the instructions say.
Scope enforcement
Every watchdog-originated mutation is gated by a server-side scope check derived from the agent run's contextSnapshot.taskWatchdog field. The check resolves to a { kind: "watchdog", watchdogId, companyId, watchedIssueId, watchdogIssueId } envelope and rejects:
- mutations on issues outside the watched subtree (parent-chain walk, depth-limited)
- mutations on issues whose company id does not match the watchdog's company
- attempts to resolve interactions other than eligible task-level
request_confirmationplan confirmations (see SPEC §9.9 for eligibility) - changes to the watchdog configuration itself (a watchdog cannot edit its own row or create another watchdog)
- direct edits to active-run output or execution-policy decisions that require a typed participant
The check is wired into the issue update, status change, blocker, assignment, and interaction routes. Any disallowed mutation is rejected at the route layer; the watchdog agent must take a different path (comment, in-subtree follow-up issue, leave a valid waiting state, escalate to a human owner).
Origin and badges
Watchdog-generated review tasks carry originKind = 'task_watchdog' and originId = watchedIssueId. The UI surfaces this in three places:
- The properties row on the watched issue shows a small task badge linking to the active review task whenever one exists.
- The review task itself carries an origin badge distinguishing it from manually created child issues.
- The board's audit activity feed labels every watchdog-driven mutation with the watchdog id, source issue id, watchdog issue id, run id, and stop fingerprint.
These origin markers are also what excludes the review task from future scans. The walk-down ignores them, so the watchdog cannot scan or trigger on its own review tasks.
When not to use a watchdog
A task watchdog is useful when:
- the work has many leaves and you want a second pass before trusting "all green"
- the original assignee tends to declare done too fast, or accept plans you would not accept
- the tree is important enough that a missed false-stop is worth an extra agent run
It is not the right tool for:
- monitoring a single running process for silence — that is the silent active-run watchdog, automatic, no configuration
- liveness recovery on stalled agent-owned issues without an explicit recovery surface — that is automatic too
- board-level approvals or anything security-sensitive — the watchdog cannot resolve those
- replacing a human reviewer on a typed execution-policy stage — the watchdog cannot bypass typed participants
If what you actually want is "wake me when this is done," use a routine or an issue-thread interaction with continuationPolicy: wake_assignee, not a watchdog.
Reference
| Topic | File |
|---|---|
| Authority contract (formal) | doc/SPEC-implementation.md §9.9 |
| Execution semantics (formal) | doc/execution-semantics.md §11 |
| Silent active-run watchdog | doc/execution-semantics.md §12 |
| Database schema | packages/db/src/schema/issue_watchdogs.ts |
| Server service | server/src/services/task-watchdogs.ts |
| Scope enforcement | server/src/services/task-watchdog-scope.ts |
| Wake context + default mandate | packages/adapter-utils/src/server-utils.ts (WATCHDOG_DEFAULT_MANDATE) |
| HTTP routes | server/src/routes/issues.ts (GET/PUT/DELETE /issues/:id/watchdog) |
| Properties UI | ui/src/components/IssueProperties.tsx (Watchdog row) |
| New-issue dialog UI | ui/src/components/NewIssueDialog.tsx |
And that's all folks!
,.
(_|,.
,' /, )_______ _
__j o``-' `.'-)'
(") \'
`-j |
`-._( /
|_\ |--^. /
/_]'|_| /_)_/
/_]' /_]'