Files
PaperClipAI/server/src/modules
DottaandClaude Opus 5.5 a372271a40 fix(wake-queue): keep a task done for its delegator's comment posted during the closing run (#21032)
## 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>
2026-11-11 23:53:43 -06: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.

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.