Commit Graph
5 Commits
Author SHA1 Message Date
DottaandPaperclip 6fe6d6d29c fix(server): bound retention reconstruction and preserve producer transactions
Batch review and confirmation eligibility per company, scope decision and budget hints, and keep child blocker relation intents inside their owning transaction. Cover ordinary child, budget and execution producers plus full candidate-page query bounds; update startup/import fixtures for source-driven retention.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-30 17:10:15 -05:00
DottaandPaperclip 0f3017c96d perf(server): archive attention from source deadlines
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-30 16:44:21 -05:00
DottaandPaperclip e9e84bb8a1 fix(server): recover decision effects from saved claim deadlines (#17482)
## Thinking Path

> - Paperclip manages agent work and the decisions that authorize it.
> - A committed decision can leave effects unfinished after an
interrupted execution.
> - Recovery must preserve that debt without scanning settled history
forever.
> - Claims already store the state and time needed to schedule recovery.
> - A failed claim must get another attempt even while newer claims
arrive.
> - Malformed claims need a durable failure outcome instead of an
endless retry.
> - This PR uses committed signals, saved deadlines, and bounded
recovery rotations.

## Linked Issues or Issue Description

**What happened?**

Periodic decision maintenance queries for unfinished effects even when
none remain. A cursor that only wraps after a short page can postpone an
old failure indefinitely under continuous new work. Missing saved
deciders or chosen options can also leave non-executable claims scanning
forever.

**Expected behavior**

Open decisions and completed history produce no recurring
effect-recovery SQL. Committed claims wake recovery, and restart
reconstructs their deadlines. Genuine failures retain retries. Known
malformed outcomes settle with an audit and preserve completed effects.
Recovery must not invent authority, repeat committed effects, or lose
continuation delivery.

**Steps to reproduce**

Create an old failing claim with a one-row recovery page, then keep
adding newer due claims. Before the rotation fix, the old failure is
never revisited. Separately, recover a claim with a missing decider or
chosen option: the old implementation skips or throws on every attempt
without settling it. Creating an ordinary open decision also caused an
unnecessary effect scan.

Stacked on #17479. Other decision expiry, continuation, target and
retention duties have separate PRs in the stack.

## What Changed

- Coalesce sequential committed notifications for up to one second
without delaying already saved deadlines. This bounds instance-wide
recovery reads and adds no idle polling.

- Register effect recovery with the shared scheduler and
transaction-aware notifications. Derive deadlines from the existing
claim timestamp and recovery grace.
- Remove effect recovery from recurring aggregate maintenance when the
worker owns it. Preserve the explicit combined recovery entry point.
- Page within a frozen final timestamp/ID and retain PostgreSQL
precision at both boundaries. Failed claims and healthy later claims
both make progress.
- Load the current claim and derive recovery authority inside the shared
execution flight. Preserve foreground joining, current authorization and
per-effect locks.
- Recheck known malformed claims under a row lock. Save a failed
outcome, or partial when an executed effect receipt exists, with
`effectRecoveryError` and an atomic `decision.recovery_failed` audit.
Publish activity after commit and use normal continuation delivery.
- Avoid effect-recovery notifications for open-decision creation. Claim
and settlement writes retain their existing notification boundary.
- Add generated migration `0390_salty_killraven.sql`, the concurrent,
idempotent partial index for running-claim lookup. No queue table is
added.
- Cover quietness, transaction and lifecycle boundaries, retry fairness,
malformed outcomes, and concurrent repair. Document the contract in
`doc/idle-decision-effect-recovery.md`.

## Verification

Current candidate normally integrates landed expiry `3421366903`,
including expiry-only creation signals and all 21 independent master
changes. The test revision is
`dba8a79d1e1a5b97b8b5f6dac0f7c7837536531a`; the publication adds only
this evidence documentation.

- **143 current cases pass** across all 26 recovery, 54 decision, 20
expiry, 42 startup and one scoped-deadline cases. Server types/build
pass with the rebuilt Runner vendor (1,524 inputs and 1,563 outputs).
These results follow the landed dependency reconciliation; prior 69- and
141-case results remain historical evidence for their stated revisions.
- Generated migration `0390_salty_killraven.sql` adds the existing
running-claim partial index through the concurrent executor. All 388
parent journal entries are unchanged; five generated snapshots remain.
Schema drift, numbering/safety and actual migration apply/replay pass.
- Existing regressions exercise failed-claim rotation under sustained
arrivals, malformed decider/option outcomes, atomic audit failure,
row-lock repair races, current authority, foreground effect joining,
coalesced tenant notifications, transaction commit/rollback,
drain/resume, paging with PostgreSQL timestamp precision, and
shutdown/restart. Completed effects are not repeated.
- The first integrated run passed 140 cases and failed an overlap test
that assumed the effect deadline was the scheduler's earliest deadline.
Continuation coalescing now creates an earlier registered wake. The test
services that earlier wake before asserting the original effect grace
deadline; it still requires one shared effect flight, two lock attempts
including the test gate, one committed comment and one continuation. No
production timing was relaxed.
- Earlier red/green fairness, malformed-claim, notification
amplification and fixture failures remain recorded in [the owning
audit](doc/idle-decision-effect-recovery.md). Extra upstream
embedded-only tests attempted during parent integration could not
initialize locally; CI must exercise that environment.

- Previous candidate `f54a707d74` received both 5/5 reviews but its CI
server shard 6 was interrupted (run 40597921712). The step was
cancelled, with no failing assertion or suite summary, and the verify
gate also failed. No retry or passing claim was made for that run. The
necessary parent repair is included here; fresh CI and both reviews are
required.

Commands: server typecheck/build and Vitest for
`decision-effect-recovery-work`, `decisions-service`,
`decision-expiry-work`, `server-startup-feedback-export`, and
`scoped-deadline-work`; DB migration checks, schema snapshot drift and
real migration apply/replay. No full local repository-suite or deployed
sleep/cost claim is made. Fresh current-head CI and Greptile/Ponytail
are required before merge.

## Risks

Missing deciders and missing chosen options now produce durable failed
or partial outcomes instead of remaining running forever. Their audit
and receipts remain available; no effect executes under invented
authority. Only these known malformed states are settled this way.
Database, audit, and other execution failures retain retries.
Continuations retain their existing at-least-once delivery contract.

The supported writer model remains process-local notifications plus
restart reconstruction. Independent SQL writers and multiple replicas
gain no new wake protocol. Cloud admission and durable external alarms
are unchanged. These tests establish worker behavior, not deployed host
suspension or fleet savings.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning and code execution. The exact
serving model identifier and context window were not exposed.

## 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
- [ ] All Paperclip CI gates are green
- [ ] 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-27 18:24:55 -05:00
DottaandPaperclip 3421366903 fix(server): schedule decision TTL expiry from saved deadlines (#17479)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Open decisions have a saved expiry time.
> - The main maintenance tick scans those times even when none is due.
> - Committed creation and resolution can maintain the actual next
deadline.
> - The existing scheduler can run expiry only when that deadline
arrives.
> - This pull request gives decision TTL expiry its own worker.
> - Terminal history becomes quiet while expiry and continuation
delivery remain recoverable.

## Linked Issues or Issue Description

Targets master after #17478 merged. This is slice 10 of the recurring
server work migration. It owns decision TTL expiry. Target invalidation,
interrupted effects, and retention remain separate migrations. The full
ledger is in `doc/plans/2026-10-24-recurring-server-sleep-stack.md`.

**What existing behavior does this improve?**

Time-based decision expiry in the server.

**Current behavior**

The combined decision maintenance sweep queries expired open decisions
on each tick. It mixes that query with target invalidation, effect
recovery, and continuation delivery.

**Proposed behavior**

Creation, bundle creation, and existing decision updates notify the
shared scheduler. A dedicated worker runs at the first saved open
expiry. Resolving an open decision removes its deadline. The combined
tick opts out of TTL selection while its other duties remain intact.

**Reason and benefit**

A future decision no longer requires repeated TTL queries before its
deadline. Terminal history and an empty decision table leave no
recurring TTL work. No queue table is added.

## What Changed

- Add an expiry owner for bounded due selection, next-deadline lookup,
and expiry settlement. Reuse it for expiry-on-use and the existing
target expiration transition.
- Use the central expiry-only creation notification before insert,
including bundles and caller-owned transactions. Update and terminal
transitions continue to notify expiry and continuation.
- Register the enabled-heartbeat TTL worker before decision
housekeeping. Preserve disabled-heartbeat behavior and the public
combined sweep's default batch limit.
- Honor `PAPERCLIP_DECISIONS_SWEEP_BATCH_SIZE` (default 100). Keep a
fair expiry/ID cursor across failures. Stop between candidates on
shutdown and reconstruct saved work after restart.
- Recheck open status and due TTL during settlement. Merge metadata and
persist activity atomically with expiry. Publish activity and deliver
continuation after commit. Handle exact timestamp equality.
- Continue successful overdue batches after one second through the
existing deadline adapter; preserve thirty-second failure retry and all
default callers.
- Add generated migration `0389_brave_hulk.sql`, an idempotent
concurrent partial index on open expiry and ID. Preserve five generated
snapshots.
- Add 20 real-PostgreSQL lifecycle cases and startup ownership
assertions. Document contracts and limitations in
`doc/idle-decision-expiry.md`.

## Verification

Current candidate: `aa03d2c4c25af4e6c2acf49d3160bbaf6ada030b`. This PR
targets master after #17478 merged. Creation now signals only its real
expiry obligation; terminal updates preserve continuation delivery.

- **117 current cases pass** across all 20 expiry, 54
decision/continuation, 42 startup and one scoped-deadline cases. Server
typecheck/build pass. Separate migrated PostgreSQL fixtures exercise
real transaction and notification behavior.
- Single and bundle creation each failed a regression before the
expiry-only signal repair: open rows unnecessarily notified continuation
recovery. Both now arm their saved expiry without a continuation signal.
Cancelling each row afterward still signals and delivers continuation
normally. The distinction is in the existing central notification owner;
there are no per-route flags or counters.
- **Three new real PostgreSQL regressions failed before repair:** a
configured batch of two was ignored, a successful 101-row backlog waited
for the thirty-second error retry, and an activity insert failure left
the row expired without its audit. All three pass. The audit failure
rolls back expiry; retry records one activity, preserves unrelated
metadata and wakes once.
- The shared deadline adapter has optional `continuationMs`. Only this
owner opts into one-second successful overdue continuation. Other
callers keep the previous default, and errors still retry after thirty
seconds. The archive consumer passes with the option absent.
- Migration `0389_brave_hulk.sql` is regenerated from the parent's
metadata. Parent SQL/journal entries are preserved, five generated
snapshots remain, schema drift and migration numbering/safety pass.
Fresh migration apply and replay pass.
- Lifecycle coverage includes first producer activation, future-deadline
quiet, terminal history, exact equality, commit/rollback, restart,
drain/resume, failed expiry and wake delivery, fair paging through
failed rows at PostgreSQL timestamp precision, shutdown and recovery.
- Failure accounting: the first full repair selection passed 113 cases
and failed a startup assertion that observed a pending follow-up
notification before reconciliation. The assertion now waits for the
exact saved future deadline, then still proves no early expiration.
Earlier fault-fixture and timestamp-cursor failures remain recorded in
[the behavior audit](doc/idle-decision-expiry.md). Extra upstream
embedded-only tests attempted on the parent could not initialize
locally; CI must cover that environment.

Commands: server typecheck/build and Vitest for `decision-expiry-work`,
`decisions-service`, `decision-retention-service`,
`server-startup-feedback-export` and `scoped-deadline-work`; DB
migration checks, snapshot drift and actual migration apply/replay. No
full local repository-suite or deployed sleep/cost claim is made. Fresh
current-head CI and Greptile/Ponytail remain required before merge.

## Risks

- Expiry uses `expiresAt <= now`; the old combined loop skipped exact
equality. The deadline boundary is now covered explicitly.
- TTL expiry records the TTL reason. Target-driven expiry preserves its
own reason. When both causes apply, the first terminal transition wins.
- Supported producers share the application DB signal owner. Independent
SQL writers or multiple active processes need a separate notification
contract. New deadline-edit APIs must use the existing helper.
- The index requires top-level concurrent migration execution. It is
registered with the migration owner and replayed in verification.
- Future in-process deadlines are not external Cloud alarms. Existing
sleep guards remain conservative. This PR does not claim host/database
suspension or measured savings.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning and code execution. The exact
serving model identifier and context window were not exposed.

## 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
- [ ] All Paperclip CI gates are green
- [ ] 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-27 18:10:36 -05:00
DottaandPaperclip a63d19816f fix(server): wake decision continuations from committed changes (#17478)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A completed decision can owe a wake to the agent that proposed it.
> - The decision row already stores the pending continuation.
> - The main maintenance tick scans this debt even when no continuation
is ready.
> - Committed changes can activate recovery and leave settled history
quiet.
> - This pull request gives continuation delivery its own worker and
shares foreground delivery ownership.
> - Pending work stays recoverable without an unconditional continuation
scan.

## Linked Issues or Issue Description

Targets master after #17475 merged. This is slice 09 of the recurring
server work migration. Each PR owns one reason for background work. The
remaining inventory is in
`doc/plans/2026-10-24-recurring-server-sleep-stack.md`.

**What existing behavior does this improve?**

Decision continuation delivery and recovery in the server.

**Current behavior**

Foreground paths send a wake and clear a pending flag. The combined
decision sweep scans pending continuations on every maintenance tick. It
can overlap foreground delivery.

**Proposed behavior**

Terminal decision writes notify a dedicated worker through the existing
transaction boundary. Foreground and recovery calls share one in-flight
delivery per decision and DB owner. Recovery re-reads readiness, pages
past failures, and retries only unresolved debt or errors.

**Reason and benefit**

Open decisions, running effects, and delivered history no longer cause
an independent continuation scan. No queue table is added. Decision
expiry, target invalidation, interrupted effects, and retention remain
separate duties.

## What Changed

- Extract continuation readiness, delivery, and paged recovery into the
decision owner. Preserve the wake payload and at-least-once crash
behavior.
- Signal the ten decision update sites before SQL in their existing
transaction scope. Keep effects and wake calls outside those
transactions.
- Explicitly create the continuation obligation on all three
archive-proposal failure settlements, including recovered running rows
without inherited pending metadata.
- Serialize concurrent decision effect execution through final
settlement. Coalesce foreground and worker wakes. Re-read the durable
flag before sending. Fence effect completion to running state so
competing completion cannot restore acknowledged debt.
- Expose an opaque DB work-scope key. Child pools and nested
transactions share that identity; independent roots remain separate.
Existing signal consumers keep their behavior.
- Bound each recovery pass to 100 candidates, retain a fair cursor
across failures, and stop between deliveries on shutdown. Forward the
existing AbortSignal through the shared deadline adapter.
- Register enabled-heartbeat continuation recovery before decision
housekeeping. Remove only its independent scan from the main tick.
Preserve the public combined sweep and disabled-heartbeat behavior.
- Add generated migration `0388_ambitious_micromax.sql`, an idempotent
concurrent partial index for pending continuations, and keep five
generated snapshots.
- Add real producer, transaction, failure, concurrency, paging, quiet,
and shutdown tests. Document ownership and remaining limits in
`doc/idle-decision-continuations.md`.

## Verification

Current candidate: `18f682863cadb91281a86b4da2553a4579ee054a`, normally
integrated with landed archive squash `5785090ff1`. All 31 independent
incoming master files are preserved exactly, including
controller-authority recovery. All 97 decision/startup/deadline cases
plus server types/build pass after the required Runner vendor rebuild.
The preceding full five-suite selection passed 293 cases; the
continuation owner behavior is unchanged by master reconciliation.

- **97 cases pass at this master integration** across decision, startup
and scoped-deadline suites, plus server types/build. The preceding
parent candidate passed the full 293-case
decision/gateway/archive/startup/deadline selection; those results
remain evidence for that revision. All selected cases executed with
separate PostgreSQL fixtures where needed.
- Tenant-driven global scan amplification reproduced six sweeps for six
sequential real cancellations. Existing thirty-second notification
coalescing now permits one recovery sweep per window while retaining
pending work. The first green fixture wrongly expected the intent hold
to clear before coalesced reconciliation; the hold remains and quiet is
checked after settlement. Foreground delivery stays immediate.
- The archive contention test uses a signaled dedicated
single-connection read and two FIFO SQL round trips instead of a fixed
delay. Removing effect coalescing makes it fail with two contender
reads; restoring production code passes. No production test hook was
added.
- **Three new real PostgreSQL regressions failed before repair**:
recovered archive proposals with invalid signatures, mismatched
manifests or archive execution failures and no inherited pending flag.
Each now persists failure, wakes once, acknowledges continuation
delivery and remains quiet on replay. Running-state fencing is
preserved.
- The unmerged continuation index is regenerated as
`0388_ambitious_micromax.sql` after parent migrations 386/387. All
parent journal entries are unchanged, five generated snapshots remain,
numbering/safety and snapshot/schema-drift checks pass. Fresh database
migration apply and replay pass; the concurrent index is valid.
- Existing lifecycle coverage includes foreground/worker coalescing,
child pools, transaction commit/rollback, failed acknowledgement,
restart, drain/resume, shutdown and fair 100-row recovery. Earlier
bounded-pass and concurrent archive-result regressions and their repairs
remain recorded in [the owning behavior
audit](doc/idle-decision-continuations.md).
- Failure accounting: the first combined command used an incorrect
retention fixture variable and skipped 15 cases. The corrected separate
external-PostgreSQL retention run passed all 15. The historical nine
embedded-only work-signal cases were not executed by this selection.
Extra upstream embedded-only suites attempted on the parent could not
initialize locally; they remain CI verification, as recorded in the
parent PR. Earlier reused-fixture, assertion and compile failures remain
in the behavior audit and stack ledger; they are not erased by later
passing checks.

Commands: `pnpm --filter @paperclipai/server typecheck`, `pnpm --filter
@paperclipai/server build`, server Vitest for `decisions-service`,
`decision-retention-service`, `server-startup-feedback-export` and
`scoped-deadline-work`; `pnpm --filter @paperclipai/db
check:migrations`, migration apply/replay, and the generated
snapshot-drift test. Local targeted evidence does not establish a full
local repository run, deployed sleep or fleet savings. Fresh exact-head
CI and both automated reviews remain merge gates.

## Risks

- Notification-driven recovery remains global but is coalesced to thirty
seconds. This bounds request-driven scans; it does not introduce
company-scoped selection. Foreground delivery remains immediate.
- Delivery remains at least once. A crash or acknowledgement failure
after accepting a wake can send it again. Local coalescing is not a
cross-process claim.
- Supported writers share the application DB signal owner. Independent
SQL writers or multiple active server processes need a separate wake
contract. Wake-producing service methods do not gain a new guarantee
when called inside an uncommitted caller transaction.
- The concurrent index needs top-level migration execution; it is
registered with the existing migration owner and can be replayed safely.
- No Cloud sleep blocker or external alarm coverage changes. Worker
quiet does not prove deployed app or database suspension.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning and code execution. The exact
serving model identifier and context window were not exposed.

## 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-27 17:44:51 -05:00