Files
PaperClipAI/doc/idle-sleep-safety.md
T
Devin FoleyandPaperclip 35b3345033 fix(server): support issue monitor deadlines in idle sleep reports (#16325)
## Thinking Path

> - Paperclip manages work for companies and AI agents.
> - Hosted workspaces can sleep when their required work has a safe wake
path.
> - The current idle check treats every open issue as background work.
> - Backlog, unassigned and human-waiting records can therefore keep an
idle process running.
> - Future automated monitors still need their saved check time and
execution claims.
> - This PR adds a signed host capability for passive issues and
verified monitor deadlines.
> - The host can save one alarm for the earliest required routine or
monitor time.

## Linked Issues or Issue Description

**What happened?**

A quiet workspace with a backlog or unassigned todo issue reports
background work as present. A future issue monitor also prevents sleep
even when the host can save a durable wake alarm.

**Expected behavior**

Proven passive records can sleep. A validated future monitor can sleep
only after the host saves its alarm and validates the same owned report.
Active, unknown and unresolved work stays awake.

**Steps to reproduce**

1. Create a quiet workspace with an unassigned todo issue and no other
work.
2. Acquire a bounded, signed Cloud idle hold.
3. Request the routine-only safety report with `scheduledWake=1`. The
issue blocks sleep.
4. Request the new capability with `scheduledWake=2`. A passive issue
can now report no work.
5. Add a valid future monitor to an assigned in-progress issue. The
second capability returns its saved deadline for host acknowledgement.

**Paperclip version or commit**

Base: `c30fb7d2b75ea30f72d63656b2a7cfff7fe0929d`.

**Deployment mode**

Cloud-managed authenticated instance. Local scheduling remains in the
existing scheduler.

Related protocol:
[#16215](https://github.com/paperclipai/paperclip/pull/16215). Open
scheduler and monitor PRs were checked for overlap. This change leaves
monitor admission, dispatch and quota recovery with their existing
owners. The roadmap already includes scheduled work; this is a focused
idle-safety extension.

## What Changed

- Add signed scheduling capability `2`; capability `1` retains the
routine-only rules.
- Allow untouched onboarding prompts with idle assignees to sleep using
the same awaiting-user predicate as recovery; typing, answering the
opening card or a prior run removes the exemption.
- Inspect passive issue state and future monitors in the same bounded,
read-only snapshot as other work.
- Check ownership, company scope, known states, claims, policy/deadline
agreement, attempts, timeout and dependency repair.
- Combine routine and monitor deadlines into a known proof kind and
catalog generation.
- Preserve other sleep blockers, including task watchdogs,
conversations, live runs, external credentials and recovery.
- Update the protected route, OpenAPI contract, documentation and
PostgreSQL regression tests.

## Verification

- Full workspace typecheck and build passed. Final server typecheck and
full build passed after the final guards.
- Initial safety, scheduling, protected-route and control-authentication
checks: 326 tests passed.
- Final safety and heartbeat scheduling checks: 253 tests passed.
Includes reporting a future deadline, restoring scheduling from
PostgreSQL and one dispatch across competing schedulers.
- Local general server checks: 20,260 passed; UI: 8,460 passed; CLI: 534
passed, including isolated reruns of two embedded-PostgreSQL startup
failures. All remaining workspace groups and 158 serialized suites
passed. The suite was completed in groups after the isolated
infrastructure retries; the final onboarding change also passed the
focused 674-test recovery/safety run. Earlier unchanged Git fixtures
failed under Xcode's system `init.defaultBranch=main`, reproduced on the
clean base and passed with a test-scoped fixture default; no source or
machine configuration was changed.
- Rebased issue, protected-route and scheduler checks: 115 tests passed;
server typecheck passed.
- The expanded change passed 674 idle-safety, scheduling and recovery
regression tests, including six onboarding admission/invalidation cases.
Final server typecheck and build passed.
- Current-head Apex is 5/5 with no unresolved threads. All 54
current-head checks passed, with two expected skips. Four jobs were
interrupted by runner shutdowns or lost communication on their first
attempts; their targeted reruns passed. Live hosting qualification
awaits the verified master release and matching host capability.

## Risks

This intentionally covers a limited set of passive states and valid
future monitors. Assigned active tasks without a proved waiting path,
unknown states, claimed monitors and quota recovery still block sleep.
It does not make every open issue safe to sleep.

A matching host must recognize `routines_and_issue_monitors`,
acknowledge the durable alarm and revalidate the exact owner, generation
and deadline before stopping. The report alone grants no stop authority.
Older hosts retain capability `1`. Independent database writers remain
outside the admission contract. Existing scheduler claims and execution
authority are unchanged. No schema migration is required.

Roll back host negotiation to capability `1` to restore conservative
admission, while retaining alarm delivery until outstanding alarms have
been handled.

## Model Used

- OpenAI GPT-6 (Codex), with repository editing, command execution and
GitHub tools. The exact deployment ID and context-window metadata are
not exposed in this session.

## 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 not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] 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>
2026-10-14 15:39:46 -07:00

17 KiB

Automatic idle sleep safety

GET /api/instance/task-drain?idleSleepSafety=1&ownerId=<owner> adds an instance-wide idleSleepSafety report for instance administrators and signed Cloud control callers. Ordinary task drains keep their existing behavior. Company members and agents cannot read this report.

Hosting protocol

  1. Hold external admission at the hosting layer. Queue user requests for wake or abort sleep when activity arrives. Do not send traffic into an instance that is being stopped.
  2. Send POST /api/instance/task-drain with { "purpose": "idle", "ttlMs": 120000 }. Idle holds require a TTL from 5 to 300 seconds. Save the returned ownerId and expiresAt. An existing drain returns 409. An unsupported purpose must leave automatic sleep disabled.
  3. Poll the report with that exact owner. Only { "version": 1, "backgroundWork": "none" } permits ordinary idle sleep. The optional routine protocol below requires a durable host alarm as well. Require the same ownerId, draining: true, zero run/wake counters, and enough remaining TTL for the provider stop. present, unknown, missing fields, or errors must keep the instance awake. Do not interpret quiescent alone as authorization.
  4. Revalidate the same hold immediately before stopping the instance. Keep external admission closed through the provider stop. Never use an old report after a restart, expired hold, changed owner, or failed validation.
  5. If sleep is abandoned, send DELETE /api/instance/task-drain?ownerId=<owner>. A missing or stale owner cannot release another controller's idle hold. The TTL clears an abandoned hold if the controller loses its response.

An idle hold rejects new tenant HTTP requests and WebSocket upgrades with 503 and Retry-After: 1. Health checks and authenticated task-drain control remain available. Ordinary deploy drains do not enable this HTTP gate. The host must handle retry/wake at its ingress before enabling automatic sleep. Health and control mutations remain tracked through completion, including signed bootstrap writes. Only the task-drain read skips request tracking. Its authentication finishes under a separate work token before entering the report, so it counts concurrent writes without blocking on its own scan.

Scheduled routine wake protocol

Verified signed Cloud control callers may opt into GET /api/instance/task-drain?idleSleepSafety=1&scheduledWake=1&ownerId=<owner>. Tenant tokens, browser instance administrators, local implicit actors, and agents cannot request this extension. Ordinary reports retain their existing behavior.

If all other background-work checks pass, active routines may return idleSleepSafety: { version: 2, backgroundWork: "none", scheduledWake }. The proof has version: 1, kind: "routines", a SHA-256 generation, and the earliest nextWakeAt across every company. Only enabled, unarchived schedule triggers with valid cron, timezone and future next_run_at qualify. Unsupported triggers, setup work, due work, unknown states and more than 10,000 active triggers keep the instance awake. Inactive triggers do not create obligations. Active routines without enabled triggers have no timed work. Definitions are read in the same repeatable-read transaction as the other durable checks.

The host must commit one durable alarm for the exact stack under its live sleep lease before stopping compute. It must then re-read the same owned report and require the exact generation and deadline. Generation binds the process, hold owner and routine/trigger catalog, so restart or schedule edits invalidate it. No routine definitions or tenant identifiers appear in the proof. Failure to persist or validate the alarm abandons sleep. Cloud acknowledges persistence inside its controller; this report alone never authorizes scheduled sleep.

Cloud must start waking early enough for measured startup latency and retry failed or missed wakes across controller restart. After wake, the existing routine scheduler owns execution, atomic claims and catch-up rules. Local scheduling is unchanged for self-hosted instances and whenever Cloud declines sleep. This first extension covers routines only. Agent timers, plugin jobs, retries, integrations and unsupported queues retain their existing blockers. All mutations must enter through the host admission/wake path. Independent database writers are outside this single-process protocol.

Verify a full scheduled sleep/wake/execution cycle on staging before enabling this host capability. The tests cover ownership, future deadlines, changed generations and conservative fallback.

Passive issues and scheduled monitors

Signed Cloud control callers can request scheduledWake=2 to include issue monitoring alongside routines. Earlier hosts requesting scheduledWake=1 retain the routine-only rules, including the conservative open-issue check. Browser administrators and tenant credentials cannot request either extension.

With the second capability, backlog tasks, unassigned todo tasks, explicit blocked tasks and human-owned tasks may be passive records. Their presence alone does not require compute. An assigned onboarding first task is also passive with an idle assignee before its first run, user-authored comment or user-resolved opening card. This uses the same awaiting-user predicate as recovery; a seeded greeting does not start work. User input still enters through the host wake path. Execution locks, automated pending review, conversation state, unknown state and dependency repair remain blockers. A blocked task whose blockers have all finished stays awake for reconciliation. Enabled task watchdogs and unsupported recovery keep their existing checks. Incoming changes still require the hosting admission and wake path.

An active issue monitor needs a saved future monitor_next_check_at matching its validated execution policy, a company-scoped agent, no outstanding dispatch claim, and remaining attempts and timeout. Due, malformed, quota-recovery, unowned and inconsistent monitors keep the workspace awake. Inspection does not clear schedules, advance attempts or dispatch an agent.

The combined report uses version: 2 and a scheduled proof with version: 1 and kind: "routines_and_issue_monitors". Its deadline is the earliest required routine or monitor time across all companies. Its generation binds the current process, idle owner and inspected catalogs. Hosts must acknowledge this known proof kind, persist the alarm and validate the unchanged owned report before stopping compute. Existing hosts reject the new kind. Monitor dispatch and startup catch-up continue through the existing scheduler and atomic claims; self-hosted scheduling is unchanged. This capability does not cover all future work in the instance: every other safety check continues to apply.

Retry history

Retry promotion keeps scheduled_retry_at on a run for inspection. That date is consumed history when the run is terminal, has a finish time at or after the retry date, and has no accounting work pending. The idle report uses the same proof for failed ephemeral leases, coalesced wakes, and acknowledged question responses. A live retry child still blocks through the instance-wide run check. Unfinished runs, retry dates after the finish time, and pending accounting stay awake. The inspection does not clear dates or change retry scheduling.

Retained local workspaces

Local directories for primary projects and Git worktrees do not require an awake process by themselves. The report permits their retained rows only in known modes and states, with no runtime configuration, service state, commands, pending reopen, or cleanup already eligible. Saved environment references alone are passive. Shared workspaces with a project workspace remain blockers because they can inherit runtime configuration. Unfamiliar providers, strategies, configuration and malformed metadata also remain blockers.

This does not delete files, alter retention, or clear workspace records. Future cleanup eligibility is an earliest allowed time; eligible cleanup can wait for the next wake. Due cleanup, unfinished operations, runtime leases and runtime services still keep the instance awake through their separate checks. The host must keep workspace storage attached across sleep and preserve its backup policy.

Accepted work and cleanup

HTTP admission is installed before body parsing, authentication, webhooks and MCP ingress. The Express adapter tracks handler promises in nested routers and error middleware. Sending a response or losing the client does not complete an async handler. A callback-only handler that abandons its response without ending it remains a sleep blocker. An async route that settles after a disconnect releases its request token; normal cancellation does not permanently block sleep. New routes must be registered before this adapter is installed. Fire-and-forget work must have durable queue state or use trackIdleWork; a response is never a substitute for tracking that work. Accepted live-event WebSocket authentication remains counted until its promise settles, including after the raw socket closes. Detached startup reconciliation for built-in agents, managed homes and persisted runtime services also remains counted after readiness until its writes settle.

Scheduler work already in flight remains counted until its promise settles. Idle holds pause new scheduler admissions. Database backup promises remain counted through success or failure. Configured periodic backups block sleep by default. The opt-in checkpoint mode below preserves a final backup instead of running the timer while asleep. A generation counter invalidates a scan if tracked work both starts and finishes during inspection. Reports remain unknown until startup recovery and HTTP tracking are installed.

Terminal workspace recovery also registers a local sleep probe. Incomplete startup pages and failed recovery remain blockers until a successful pass. Active cleanup and unresolved producer transactions retain their admission holds. Cooldown and inspection-backoff timers are maintenance hints over retained domain rows. They can wait for the next wake and are restored at startup; they do not create external alarm coverage or exempt other persisted work from this report.

Backup checkpoints for idle instances

PAPERCLIP_DB_BACKUP_IDLE_CHECKPOINT_ENABLED=1 opts an instance with automatic backups enabled into this mode. Keep its database and persistent backup volume attached across sleep. All writers must use the instance's admission protocol; this mode cannot cover independent writers to the same database. On managed instances, checkpoint-enabled safety reads require a verified signed Cloud control actor. Tenant instance-admin elevation does not grant platform backup authority. Self-hosted instance administrators retain operator access.

Each owned safety read first checks local work, plugin drains and durable work. Known local blockers return before opening a database transaction. If every other check passes, the server takes a fresh backup with the existing backup engine and the retention policy from Instance Settings. It reads the full gzip archive, checks its trailer and nonempty content, and syncs the archive and directory before returning none. The final pre-stop revalidation takes another fresh backup. Reads can therefore take longer; the host must bound its request and still leave enough lease time for a confirmed provider stop.

There is no cached backup exemption. Failed, concurrent or untracked backups, other work during the scan, changed owners and expired holds cannot authorize sleep. Client timeouts do not clear the backup's in-flight token. Slow backups may retain an awake instance until a later sleep attempt.

A durable .idle-backup-wake-required marker is written before the dump. No recurring duplicate archives are produced while the instance is asleep. The last pre-sleep archive and existing history stay on persistent storage; no retention pruning runs during sleep. On restart, the server attempts a fresh backup before enabling idle eligibility, then resumes the normal interval. Only a verified new backup clears the marker. Failure keeps it for the next scheduled retry. Disabling automatic backups explicitly still disables this startup backup. Disabling only checkpoint mode restores the sleep blocker and continues to honor an existing restart marker.

This does not replace filesystem backups, encryption-key recovery or off-provider disaster recovery. Validate restoration of a staging checkpoint before enabling the flag on customer instances.

The report inspects accounting and sandbox-cleanup spools directly. Any entry, including malformed JSON, temporary files and failed write probes, blocks sleep. Read errors and invalid spool paths return unknown. Recovery readers that skip bad records cannot certify an empty spool.

An orphan cleanup token starts before its spool write and remains held through buffering, queue splices, flush retries and buffer overflow. It ends only after the database owns cleanup. Queued usage receipts stay counted until capture settles. If receipt capture and the database failure fence both fail, a token continues to block sleep until the failure fence is durably written. A query that updates zero rows does not clear that debt. Unrecoverable cleanup requires operator reconciliation; this API does not discard it to save compute.

Persisted work and limits

The report checks all companies in one bounded read-only database transaction. Queued and orphaned runs, timers, retries, accounting debt, unfinished issues, cleanup, active routines, integrations, external ingress credentials, and plugin work block sleep. Completed ordinary run history does not. Less common work sources conservatively block on any retained records. Active issue watchdogs block sleep even when their watched issue is complete and no review has started. Missing migrations, malformed configuration and database errors produce unknown without exposing SQL or tenant data.

Active runtime_auth connections with purpose ai are saved credentials, not independent workers. Their saved configuration does not block sleep. Login sessions and runs still pass the existing durable checks. Detached subscription observations and quota reads remain counted through their actual completion, including token refresh after a quota response times out. Credential-recovery journals, partial writes and probes block sleep across restart. Empty company directories left after successful recovery do not; invalid paths and read failures remain unknown. Credentials and connection records are retained. MCP, chat and other connections still block until they have a wake contract.

A failed ephemeral lease is history only with successful cleanup, a release timestamp, and an exact provider termination receipt bound to its company, run, lease and resource. Its run must be finished without retry or accounting debt. Pending cleanup claims and export/stop-and-retain intents still block. Failure labels or cleanup success without that proof do not authorize sleep. Lease history is retained; this check performs no provider teardown or data updates.

Finished login-session history permits sleep only after its promotion claim is cleared and any retained provider reference has a matching released, expired, or verified failed ephemeral lease with successful cleanup and a release timestamp in the same company and environment. The failed lease must satisfy the same termination and run checks above. Missing cleanup evidence, cleanup_pending, and live login flows remain blockers. A finished coalesced wake is history only when its exact company/agent's linked run has finished without retry or accounting debt. Image-setup history still blocks: promotion can succeed before teardown is confirmed, so a terminal label does not prove that its provider resource is gone.

Completed question-answer deliveries permit sleep only after acknowledgement and completion of their exact target run in the same company, without retry or accounting debt. A fallback without a target run requires its exact question's finished durable wake receipt in the same company. A coalesced fallback also requires its exact company/agent's linked run to finish without retry or accounting debt. The question-history index covers every wake status. Pending deliveries, missing completion evidence, and unfinished handoffs still block. Failed deliveries without a pending target are history; other work checks still apply.

Every enabled plugin blocks sleep. A version label does not prove that arbitrary worker code has no background activity. No plugin approval or configuration bypass is supported. Scheduled and externally triggered work needs a durable host wake mechanism before this restriction can be relaxed.

This is a single-process admission protocol. A restart clears the owner. The host must serialize sleep with deploy/wake operations and bind its final check to the instance it stops. An unexpected restart after the final check can still race an external provider stop; a process-local report cannot fence that remote operation by itself. Multiple replicas require shared fencing. The protocol does not make existing in-memory-only debt survive an unplanned process crash.

Related public PR #13413 adds runtime-service controller requirements and uses the same purpose: "idle" / ownerId vocabulary. This report conservatively blocks all retained runtime-service state instead of approving such services.