## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A lead agent delegates a task, and the assignee works on it in a run > - When someone comments while that run is active, the comment waits as a deferred wake for the next run > - If the run marks the task done, an agent comment with resume intent reopened the task. This started another assignee run and more lead wakes > - When the comment came from the lead that delegated the task, during the run that closed it, the lead gets the completed result anyway > - This pull request keeps the task done in that case. Other agents' feedback and any person's message still reopen it > - The benefit is no repeated work after a task is finished, and no lost person messages ## Linked Issues or Issue Description **What happened?** In the paid chat eval `secrets.least-scope-delivery`, the CEO created Morgan's task. While Morgan's run was active, the CEO posted a redundant instruction comment on it. Morgan's run completed the task. The deferred agent comment then reopened the done task. This caused a second Morgan run and extra CEO wakes, and the eval's run bound was exceeded. #20996 covers a person's owed answers. This change covers agent comments with resume intent. **Steps to reproduce** 1. Let agent A create a task assigned to agent B. 2. While B's run is active, let A comment on the task with resume intent. 3. Let B's run mark the task done. 4. The deferred comment reopens the task, and B runs again. **Expected behavior** The task stays done. A receives the completed result and can follow up. A person's message still reopens the task. ## What Changed - `server/src/modules/wake-queue/application/use-cases.ts`: the "agent feedback after completion" reopen rule now also requires that the comments are not watcher comments posted during the finishing run. Person messages, `issue_reopened_via_comment`, and other agents' feedback do not change. - `server/src/modules/wake-queue/application/ports.ts` and `adapters/postgres.ts`: `isWatcherCommentDuringFinishingRun` is true only when every comment exists, has an agent author and no person author, comes from the task's creator agent or its parent's assignee (not the finishing agent), and was created after the finishing run started. - When this is true, the deferred wake is cancelled with the existing reason "Deferred execution wake no longer applies to a terminal task". The comment stays on the task. - `server/src/__tests__/heartbeat-comment-wake-batching.test.ts`: a new heartbeat case. It is red on master (the task reopens and the assignee runs again) and green here (the task stays done, one run). The existing case, where a non-delegating agent's feedback reopens the task, still passes. - `use-cases.test.ts`: a unit scenario for the new rule. ## Verification - `cd server && npx vitest run src/__tests__/heartbeat-comment-wake-batching.test.ts src/modules/wake-queue` (369 passed; the new case is red on master) - `cd server && npx tsc --noEmit` passes. - Paid E2E run on a combined branch: https://github.com/paperclipai/paperclip/actions/runs/43161593494 (results in a comment below). ## Risks - Behavior change: if a delegating agent asks for new work in a comment during the closing run, the assignee does not get it automatically. The delegator receives the completed task and can comment again or create follow-up work. A person's message is never dropped by this rule. - Comments with no author type (older rows) count as agent comments only when they have an agent author and no person author. - No schema change. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code execution in Claude Code. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
The execution admission module owns project task-execution policy and the
durable admission records that charge project capacity. Today it exposes only
internal policy commands and the deletion guards that project, agent, and
company deletion call. Settlement releases active charges but keeps each
admission's complete project set for later retained-session resolution.
Execution claims still belong to services/heartbeat/queue.ts and native
review dispatch. See execution admission accounting.