mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 23:20:53 +01:00
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Idle sleep must wait for accepted work and durable cleanup.
> - The retry dispatcher keeps its due date after it promotes a retry.
> - Finished retries and passive local directories can therefore look
like pending work.
> - This pull request requires completion proof and permits known
passive directory records.
> - It retains the records and keeps real work and unknown state awake.
## Linked Issues or Issue Description
Related: #15897 and #15851. The open runtime-service work in #13413
retains its separate service and ingress guarantees. This patch does not
change those checks.
**What happened?**
Idle sleep treats a consumed retry date as pending work. It also blocks
on every retained execution workspace, including a local directory
without runtime activity.
**Steps to reproduce**
1. Promote a scheduled retry, then finish its run without accounting
debt. Retain its scheduled retry date.
2. Retain a local primary directory or Git worktree without runtime
configuration, commands, or cleanup due.
3. Request an owned idle safety report. The report returns `present` for
this history.
**Paperclip version or commit**
Reproduced on master at 4b59e2a2ec.
**Deployment mode**
Server with PostgreSQL. The regression tests use embedded PostgreSQL.
## What Changed
- Permit terminal runs with a finish time at or after their retry date
and no accounting debt. Apply that proof to linked wake,
answer-delivery, and verified failed-lease history.
- Permit known passive local primary directories and Git worktrees. Keep
due cleanup, commands, runtime configuration, shared project runtime
inheritance, pending reopen, runtime owners, and unfamiliar state awake.
- Retain retry dates, workspace records, files, and metadata. Document
the proof and retention limits.
## Verification
- Passed all 200 tests in `pnpm exec vitest run
server/src/__tests__/idle-sleep-safety.test.ts` against embedded
PostgreSQL.
- Passed the 52 retry dispatcher tests.
- Passed `pnpm -r typecheck` and `pnpm build`. After the provider
correction, server typecheck and build passed again.
- Full CI is green for commit
`0510669439e855b90a4e03a2fc7827503cea507f`, including all test shards,
typecheck, build, and browser end-to-end tests. One browser test
initially raced its comment write; the failed jobs passed on retry
without a source change.
- Local `pnpm test:run` recorded 18,721 passed, 105 skipped, and four
failures in existing AI-login, Git-fixture, and native runner-recovery
tests; it exited before the remaining workspace and serialized groups.
The AI-login test passed alone. Both Git fixtures passed with
command-scoped `init.defaultBranch=master`. The native recovery failure
also occurs on the parent revision on this Mac. These suites pass in CI.
- Greptile Apex scored the final commit 5/5. Its provider-coverage
comment was addressed and the review thread resolved.
- Reviewed the diff for secrets, private data, generated files, and
unrelated changes.
## Risks
- Missing finish times, later retry dates, active children, and
accounting debt continue to block sleep. Failed lease history still
requires an exact provider termination receipt.
- Future cleanup eligibility can wait for the next wake. Due cleanup
blocks sleep. Storage must remain attached across sleep and keep its
backup policy.
- Shared directories with a project workspace and unfamiliar providers,
strategies, modes, or configuration remain conservative blockers.
- No migration, provider teardown, retry scheduling change, or tenant
data deletion.
## Model Used
OpenAI Codex, GPT-6, with repository tools and code execution. The
runtime does not expose a more specific model snapshot or context-window
size.
## 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
- [ ] I have run tests locally and they pass (focused tests pass; the
full local run has the environmental failures documented above)
- [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>
202 lines
12 KiB
Markdown
202 lines
12 KiB
Markdown
# 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 sleep. 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.
|
|
|
|
## 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.
|
|
|
|
### 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.
|