Files
PaperClipAI/server/src/modules
DottaandPaperclip cb2d6e80d3 perf: stop polling idle Muse maintenance (#17000)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Muse connects an external worker to a Paperclip agent.
> - Its maintenance loop scans the database every five seconds, even
without bindings.
> - These empty scans prevent an otherwise idle database from sleeping.
> - The shared delivery coordinator can react to committed writes and
saved deadlines.
> - This change uses that coordinator and retains recovery for
unfinished assignments.
> - Idle Muse maintenance can now stop querying the database.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Muse qualification, OAuth authority, and terminal assignment
maintenance.

**Current behavior**

A claimed instance runs two empty scans every five seconds even when
Muse has no bindings. This produces about 34,560 unnecessary queries per
day. Warm standby suppression from #16974 does not cover claimed
instances.

**Proposed behavior**

Recover once at startup. Wake after relevant transactions settle.
Schedule qualification expiry at its saved deadline. Retain five-second
recovery only for failures and unfinished assignments.

**Reason and benefit**

Remove recurring database traffic from idle Muse maintenance while
preserving authority checks and recovery. Reuse the database work signal
foundation from #15625 without adding tables.

## What Changed

- Register Muse maintenance with the shared delivery coordinator and its
idle admission rules.
- Signal pairing, OAuth attachment and revocation, qualification
changes, and new assignments before transaction writes. The existing
transaction layer waits for outer commit.
- Restore qualification deadlines from persisted bindings. Retry failed
sweeps and unfinished assignments.
- Add PostgreSQL tests for idle quiet, nested commit, rollback,
deadlines, standby, drain, and failure recovery. Verify signals from
actual Muse producers. Preserve startup, newly committed work, and
failed sweep retries across idle drain without SQL polling.
- Document the remaining recovery cadence and sleep limitations.

## Verification

- `pnpm -r typecheck`: passed before rebase. Server typechecking also
passed after rebase and the drain fix.
- `pnpm build`: passed before rebase.
- Final change: 33 maintenance, delivery coordinator, and scheduler
tests passed. Maintenance used a fresh disposable PostgreSQL 17
database.
- Muse control-plane tests: 254 passed against the same disposable
database.
- `pnpm test:run`: attempted, then stopped after repeated embedded
PostgreSQL startup failures from this host's semaphore limit.
Native-session, Muse, and AI-connection suites could not create local
clusters. The complete suite runs in CI.
- `git diff --check`: passed.
- Final commit `e106cbf20d`: all CI gates passed, including complete
test suites, E2E, build, production bundle boot, public-install smoke
test, and canary dry run. Greptile and Paperclip source reviews both
rated 5/5, with no unresolved findings.

The focused suites accept `PAPERCLIP_MUSE_MAINTENANCE_TEST_DATABASE_URL`
and `PAPERCLIP_MUSE_CONTROL_TEST_DATABASE_URL`. Use a disposable test
database; fixtures remove test data.

## Risks

- Unfinished assignments retain five-second recovery. This change
removes idle polling, not all Muse polling.
- Signals are process-local. Independent database writers need explicit
integration. Startup recovers persisted state.
- Qualification deadlines depend on the running process. Existing
durable Muse sleep blockers remain unchanged.
- The receiver keeps its batching timer. An empty buffer already makes
no database calls.
- No schema migration or API change.

## Model Used

OpenAI Codex, GPT-6 family, with code execution and repository tools.
The exact model version is 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 (focused suites; full local
run has the environment limitation 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>
2026-10-20 14:20:59 -05:00
..

Feature modules

A feature module under server/src/modules/<name>/ uses three layers. Each layer has one rule: which layer below it, it may import from.

adapters  →  application  →  domain
  • domain/ holds pure business rules. A domain file takes plain data in and returns plain data out. A domain file must not import drizzle-orm, @paperclipai/db, a service under server/src/services/, a route under server/src/routes/, or a Node.js I/O module (node:child_process, node:fs, node:net). A domain function must not read the system clock; the caller passes now as an explicit Date value.
  • application/ holds use cases and the ports they need. A use case takes its ports as constructor arguments and calls domain functions for policy decisions. An application file must not import drizzle-orm, a SQL client, a concrete adapter, or the server's HTTP error helpers. The outer service or route translates application errors into transport responses.
  • adapters/ holds the concrete implementations of the ports: Postgres queries, transactions, and process control. An adapter file may import drizzle-orm, @paperclipai/db, and Node.js I/O modules.

A module exposes its commands through index.ts, which composes the adapters and the use cases behind a factory function. Code outside the module imports that entry point, never a file inside domain/, application/, or adapters/ directly.

pnpm check:module-boundaries enforces these rules for production source files. It also checks access to the company deletion entry point below.

Company deletion

The existing company deletion service owns the database transaction and deletion order. A module can expose a separate company-deletion.ts entry point. Only services/company-deletion.ts can import this entry point. The entry point exports an implementation of CompanyDeletionParticipant from lib/company-deletion.ts. Do not export it through the normal module index.

The service must complete external cleanup before it starts the transaction. It must lock the company before it deletes dependent records or calls a module. Creation commands must use the same company lock. The service removes dependent records before it calls the module. The module checks its deletion conditions and deletes the records it owns. Use the supplied transaction. Do not start another transaction or call external systems. Throw an error if deletion cannot proceed. An error must roll back all database changes in the deletion transaction.

Call each module explicitly in dependency order. No module registry is required.

The agent lifecycle module also has a restricted configuration.ts entry point. It saves configuration and invalidates verification in the caller's transaction. The lifecycle guide lists its permitted callers. Ordinary configuration updates use the root lifecycle command.

The agent lifecycle module owns agent creation, pause, resume, and termination. Use its commands for these changes.