Skip to content

fix(heartbeat): bound provider-advertised retry horizons (BLO-23438) - #1225

Merged
kkroo merged 1 commit into
masterfrom
codex/supersede-pr-1186
Aug 10, 2026
Merged

fix(heartbeat): bound provider-advertised retry horizons (BLO-23438)#1225
kkroo merged 1 commit into
masterfrom
codex/supersede-pr-1186

Conversation

@kkroo

@kkroo kkroo commented Aug 9, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip runs agents through the heartbeat scheduler, which persists capacity denials as scheduled retries.
  • A provider-advertised reset was previously accepted verbatim, so a stale multi-day value could freeze a task and every coalesced wake behind it.
  • The same untrusted horizon could also re-enter through a finalized run's retryNotBefore floor.
  • This independent successor reapplies the reviewed bounded-cap and additive-jitter implementation from fix(heartbeat): bound provider-advertised retry horizons (BLO-23438) #1186 on current master.
  • The provider_quota family remains exempt because its reset represents a contractual quota boundary rather than a capacity estimate.
  • The result is a bounded re-probe horizon that avoids synchronized retry cohorts without taking fix(heartbeat): close the monitor-lapse recovery gap + manager-chain monitor re-arm (BLO-22860) #1187's watchdog or authorization work.

Linked Issues or Issue Description

Refs BLO-23438 and BLO-18278. This is the independent kkroo-authored successor to app/allyblockcast PR #1186.

Related but intentionally excluded: #1187's watchdog and manager-chain authorization changes, #1142's distinct over-cap parking approach, and #1196's recovery work.

What Changed

  • Add pure capacity retry resolution with a 15-minute ceiling and additive 20% jitter.
  • Clamp the persisted capacity-gate retry and promotion-time re-deferral while preserving the provider's advertised value as metadata.
  • Cap transient retryNotBefore floors at 24 hours and retain the provider_quota exception.
  • Add resolver and persistence regressions for horizon clamping, jitter, and bounded retry scheduling.

Verification

  • pnpm exec vitest run server/src/tests/ccrotate-capacity-retry.test.ts server/src/tests/heartbeat-ccrotate-capacity-retry.test.ts server/src/tests/heartbeat-retry-scheduling.test.ts server/src/tests/heartbeat-rate-limit-retry-schedule.test.ts — 84 passed.
  • pnpm --filter @paperclipai/server typecheck — passed.
  • git diff --check — passed.

Risks

The change intentionally re-probes earlier when an untrusted capacity hint exceeds the ceiling, so an outage can create bounded extra probe attempts instead of a multi-day freeze. Additive jitter never retries before a respected reset, provider_quota remains unbounded, and the patch has no schema or API migration.

Model Used

OpenAI GPT-5 Codex with tool-assisted review-context inspection, source reconstruction, and local test execution.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes / Closes / Refs OR (b) described the issue in-PR following the relevant issue template
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots (not applicable)
  • I have updated relevant documentation to reflect my changes (not applicable; internal behavior is documented in code and tests)
  • I have considered and documented any risks above
  • All Paperclip CI gates are green (pending)
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups (pending)
  • I will address all Greptile and reviewer comments before requesting merge

Clamp untrusted capacity reset hints with additive jitter and bound transient retry floors while preserving provider_quota reset semantics.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@cursor

cursor Bot commented Aug 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@allyblockcast

allyblockcast Bot commented Aug 9, 2026

Copy link
Copy Markdown

🔗 Paperclip issue: BLO-18278
🔗 Paperclip issue: BLO-23438

@kkroo

kkroo commented Aug 9, 2026

Copy link
Copy Markdown
Author

@ally please review exact head 31ebed4 only. This independent kkroo successor to #1186 bounds untrusted provider capacity hints with a 15-minute cap plus additive jitter, clamps transient retry floors at 24 hours, preserves provider_quota, and deliberately excludes #1187 watchdog or manager-chain authorization changes.

@kkroo
kkroo requested a review from allyblockcast August 9, 2026 14:32

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 31ebed4

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The capacity-specific resolver centralizes ceiling, fallback, jitter, and provenance behavior, with deterministic coverage for boundary conditions.
  • The persistence-path test verifies that the clamped retry floor, original advertised horizon, and operator-facing clamp metadata all reach the scheduled run.
  • The second clamp closes the finalized-run retry-floor path, preventing it from reintroducing the same multi-day park.

Recommended Action

  1. Merge when the remaining required checks pass.

@allyblockcast allyblockcast left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 31ebed4

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The capacity-specific resolver centralizes ceiling, fallback, jitter, and provenance behavior, with deterministic coverage for boundary conditions.
  • The persistence-path test verifies that the clamped retry floor, original advertised horizon, and operator-facing clamp metadata all reach the scheduled run.
  • The second clamp closes the finalized-run retry-floor path, preventing it from reintroducing the same multi-day park.

Recommended Action

  1. Merge when the remaining required checks pass.

@kkroo
kkroo merged commit c00a1b3 into master Aug 10, 2026
16 of 18 checks passed
kkroo pushed a commit that referenced this pull request Aug 11, 2026
…ents observable (BLO-24011)

A `ccrotate_capacity` park is re-decided every time the run comes due and the
pool is still exhausted, but the promotion-time re-defer updated only
`scheduledRetryAttempt`/`scheduledRetryAt`. Every descriptive field stayed
behind from the *first* denial, so the incident row read
`penstockRetryAfterSeconds: 3834` and `retryNotBefore: 08:00Z` beside a
`scheduledRetryAt` four days out — two decisions wearing one row, two orders of
magnitude apart, on attempt 1, with nothing explaining why.

The horizon clamp itself landed in #1225. This closes the remaining gap:

- Route both writers through `applyCcrotateCapacityDecision` so a re-defer
  clears the previous decision wholesale before writing the current one, and
  the two writers cannot drift.
- Add `GET /api/companies/:companyId/parked-agents` + the
  `paperclipListParkedAgents` MCP tool, answering "which agents cannot run, and
  until when?" without invoking a heartbeat on each one to find out. Reuses the
  same per-agent read filter as the agents list, so an agent caller sees exactly
  what it is already allowed to see.
- `overdueMs` separates "parked a long time" from "due but the sweep is not
  draining it" — different failures that looked identical before.

Regression replays the incident end to end: initial denial advertising 3834s,
promotion-time denial advertising ~4.6 days. Asserts attempt 1 never parks past
24h, that the row's retry floor and scheduled instant agree, and that the
superseded 3834s figure is gone rather than sitting beside a park it no longer
explains. Verified as a negative control: reverting the resultJson write fails
the test.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants