## 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>
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 importdrizzle-orm,@paperclipai/db, a service underserver/src/services/, a route underserver/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 passesnowas an explicitDatevalue.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 importdrizzle-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 importdrizzle-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.