Files
PaperClipAI/server
DottaandPaperclip 0b80ea17ef refactor(heartbeat): extract run lifecycle handling (#15672)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The heartbeat service admits, executes, and settles agent runs.
> - Earlier extractions separated workspaces, preparation, state,
retries, recovery, and queue admission.
> - Run status, progress, liveness, and completion handling still live
in the main service.
> - These operations share status predicates, event writes, and
completion policies.
> - This pull request moves them into one lifecycle module and adds
boundary tests.
> - Reviewers can inspect lifecycle policy separately from adapter
execution.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The structure and testability of heartbeat run lifecycle handling.

**Subsystem affected**

server/ — orchestration services.

**Current behavior**

Run status writes, progress, events, liveness, completion handoffs,
issue-comment finalization, and runtime settlement live inside
`heartbeat.ts`.

**Proposed behavior**

Keep these operations in
`server/src/services/heartbeat/run-lifecycle.ts`. Bind the database and
reporting, recovery, and wakeup callbacks with
`createHeartbeatLifecycle`.

**Reason and benefit**

This removes 2,682 lines from `heartbeat.ts`, leaving 11,558 lines. The
lifecycle module has 2,982 lines and keeps related policy together in
one file.

**Breaking changes**

None. Existing function bodies, service methods, public exports, status
conditions, and side effects keep their behavior.

**Additional context**

Continues #15626 and the earlier merged heartbeat extractions. A search
of open and closed issues and PRs found no duplicate lifecycle
extraction. This does not add a roadmap feature.

## What Changed

- Move status transitions, run events and progress, liveness
classification, completion handoffs, plan-resume failure reporting,
issue-comment finalization, and runtime/cost settlement into
`heartbeat/run-lifecycle.ts`.
- Supply reporting, recovery, and admission callbacks through an
explicit dependency interface. Preserve construction order with
forwarding callbacks.
- Keep adapter execution, cancellation, and terminal telemetry reporting
in the main service.
- Add 16 tests for inert construction, continuation gates, late
progress, status races, native ownership, usage metadata, revoked wakes,
event sequencing, and redaction.
- Update the existing publication-order source assertion to read the
extracted event writer. Keep its persist-before-publish check.
- Document the lifecycle module boundary in `doc/DEVELOPING.md`.

## Verification

- Before extraction: 87 tests passed across seven existing suites.
- After extraction: 103 tests passed across eight files, including the
16 new module tests and real PostgreSQL coverage.
  ```sh
pnpm exec vitest run server/src/services/heartbeat/run-lifecycle.test.ts
server/src/__tests__/heartbeat-run-status-payload.test.ts
server/src/__tests__/heartbeat-run-event-sequencing.test.ts
server/src/__tests__/heartbeat-run-terminalize-before-release.test.ts
server/src/__tests__/heartbeat-run-lease-release-terminalization.test.ts
server/src/__tests__/heartbeat-cost-accounting.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts
server/src/__tests__/run-liveness.test.ts
  ```
- Additional recovery, cancellation, plan-resume, summary, and handoff
coverage: **595 tests passed across 14 files**. The module suite appears
in both runs. With the publication-order suite below, combined focused
coverage is **694 distinct tests across 22 files**, with no skips.
  ```sh
pnpm exec vitest run server/src/services/heartbeat/run-lifecycle.test.ts
server/src/services/heartbeat/recovery.test.ts
server/src/services/heartbeat/retries.test.ts
server/src/services/heartbeat/queue.test.ts
server/src/services/recovery/successful-run-handoff.test.ts
server/src/services/recovery/review-path-recovery.test.ts
server/src/services/heartbeat-run-runtime-status.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-native-runner-cancellation.test.ts
server/src/__tests__/heartbeat-native-cleanup-admission.test.ts
server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts
server/src/__tests__/heartbeat-runtime-state.test.ts
server/src/__tests__/heartbeat-run-summary.test.ts
server/src/__tests__/heartbeat-context-summary.test.ts
  ```
- The publication-order and lifecycle suites passed together: **28 tests
across two files**. The first CI run exposed an assertion that still
read the old event-writer source path. That assertion now reads the
lifecycle module.
  ```sh
pnpm exec vitest run
server/src/services/chat-publication-reconciliation.test.ts
server/src/services/heartbeat/run-lifecycle.test.ts
  ```
- After resolving the import conflict with the Pi Runner integration and
rebasing onto `57e977be7`: **44 tests passed across four files**,
including the updated source assertion, lifecycle module, run-status
payloads, and accepted-plan workspace refresh.
  ```sh
pnpm exec vitest run
server/src/services/chat-publication-reconciliation.test.ts
server/src/services/heartbeat/run-lifecycle.test.ts
server/src/__tests__/heartbeat-run-status-payload.test.ts
server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts
  ```
- Full `pnpm -r typecheck` and `pnpm build` passed on final head
`bca87df089c541450932f6b678ca776942d267b2`. Full local `pnpm test:run`
was restarted on this head and stopped after the entire CI suite passed.
It did not finish and is not counted as a full local pass.
- The earlier full local run had one transient public MCP Cloud
bootstrap failure. All **82 public MCP tests passed in isolation**. The
first local run was stopped before the source-assertion fix and rebase.
- All **54 latest-head checks passed**; two optional Storybook checks
were skipped. One general-test shard passed all 1,314 tests but failed
during runner temporary-directory cleanup with `EACCES`. One rerun
passed. [CI
run](https://github.com/paperclipai/paperclip/actions/runs/37940997283).
- Greptile completed on final head
`bca87df089c541450932f6b678ca776942d267b2` with **5/5 and no actionable
findings**. There are no review threads or merge conflicts.
- Structural comparison confirms 63 module function bodies and ten
declarations match the originals. Remaining service function bodies and
all 140 public exports are preserved.

## Risks

Moving closures can break callback construction order or status
settlement. The factory receives explicit callbacks and starts no work
during construction. Compare-and-set predicates, native ownership holds,
revoked-wake guards, event sequencing, redaction, and completion
policies keep their original bodies. This PR has no schema or API
changes.

## Model Used

OpenAI GPT-6 via Codex. The exact serving model ID and context window
were not exposed in this session. Used reasoning, repository inspection,
shell tools, and code execution.

## 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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-09 09:26:45 -05:00
..