Files
PaperClipAI/doc/idle-grok-dispatch.md
T
DottaandPaperclip 2adcde162e fix(server): wake Grok dispatch from committed reservations (#17490)
## Thinking Path

> - Paperclip manages agent work and should become idle when no work is
owed.
> - Grok routine dispatch already persists each outbound intent before
delivery.
> - Its dispatcher starts a one-second timer whenever public MCP is
configured.
> - That timer queries settings even when Grok is disabled, and scans
dispatches when enabled but idle.
> - The reservation transaction and feature-setting transaction already
define when work can become available.
> - This PR connects those writes to the shared coordinator and retains
the Grok service's claim and delivery policy.
> - The worker becomes quiet without adding a queue table or replaying
uncertain sends.

## Linked Issues or Issue Description

Stacked on #17486. This PR owns only Grok routine dispatch. It does not
include decision retention or other remaining maintenance owners.
Related open Grok PR #17278 concerns Runner credential-lease fixtures,
not this dispatcher. The duplicate search found no overlapping
dispatcher migration.

**What existing behavior does this improve?**
Grok routine dispatch recovery in the server.

**Current behavior**
With public MCP configured, the dispatcher reads instance settings every
second, including when Grok is disabled. With Grok enabled, it scans for
prepared dispatches every second. The baseline regression observed three
queries in 3.2 seconds with Grok disabled.

**Proposed behavior**
Reconcile once at startup. Wake from committed dispatch reservations and
experimental-setting changes. Schedule another pass only while prepared
dispatches remain or reconciliation failed. Disabled and settled workers
perform no recurring SQL or provider calls.

**Reason and benefit**
Remove unnecessary tenant database activity while preserving dispatch
recovery, current authorization, provider outcome semantics, and
shutdown ownership. This follows the existing sleep-aware maintenance
direction; ROADMAP review found no competing dispatcher redesign.

**Breaking changes**
No public API or provider payload change. The internal dispatcher start
method receives the shared coordinator; that coordinator now owns
shutdown. Sending, accepted, unknown, and terminal dispatches remain
non-replayable. This does not extend Cloud wake coverage.

## What Changed

- Register the Grok worker with the existing coordinator; remove its
independent timer.
- Signal reservation and experimental-setting writes before commit.
Nested writes follow their outer transaction.
- Preserve ten-row batches, durable send claims, current authority
checks, and existing cancellation/uncertainty policy.
- Join admitted work during shutdown and leave later rows for restart.
- Add a concurrent, replay-safe partial index for prepared dispatch
lookup. Keep only the five newest generated snapshots.
- Reuse the existing real OAuth/agent/routine fixture for lifecycle
tests and add a validated external PostgreSQL test option.
- Document producers, recovery, quiet conditions, and remaining
host-sleep limitations.

## Verification

Test revision: `6d81746fe260ee879442e7e0c879cfe88c250c8e`, followed by
docs-only head `679f523d78`, which received both 5/5 reviews and passing
CI. This candidate normally integrates landed target `8dfed893d8`. Its
entire runtime and acceptance-test sources match that reviewed/tested
tree; only four independently merged Runner E2E accounting files and the
ledger differ, preserved exactly. Migration 0391 and the parent journal
prefix are unchanged. Local checks were not repeated on unchanged
runtime; fresh final-head CI and reviews are required.

- **273 cases pass** in nine complete suites: real-PostgreSQL dispatch
lifecycle, Grok qualification, server startup, scoped deadlines and all
five instance-settings suites. Server typecheck and production build
pass with the updated Runner vendor.
- The baseline disabled-feature regression failed with three SQL reads.
Existing lifecycle cases cover first reservation, restart,
non-replayable history, re-enable, outer commit/rollback, drain,
failure/retry, concurrent enqueue, paging, foreground send race, expiry,
settings commit/rollback, and shutdown.
- Generated migration `0391_third_roulette.sql` adds only the
concurrent/idempotent prepared-dispatch index. All 389 parent journal
entries are unchanged; five generated snapshots remain (0387–0391).
Migration safety/numbering, schema drift and actual fresh apply/replay
on two isolated PostgreSQL databases pass.
- The earlier 259-case selection and 40,000-row generic-plan probe
remain historical evidence: both unchanged queries used the partial
index, six shared buffers including the routine join, 0.032ms / 0.025ms
local execution. No new deployed suspension or cost claim is made.
- Earlier browser CI failed during agent-chat fixture restoration (lock
timeout/HTTP 500), followed by a test seeing the experiment left
enabled. Those failures remain recorded in the owning audit; they were
not dispatcher assertions. Later pre-reconciliation `a74305e2b4` checks
were terminal and green. Fresh CI and both 5/5 bot reviews are required
for this head.

Commands: server Vitest for `grok-dispatch-work`, `grok-bot-routine`,
`server-startup-feedback-export`, `scoped-deadline-work` and the five
`instance-settings-*` suites; server typecheck/build; DB migration
safety/numbering, snapshot drift and fresh migration apply/replay.

## Risks

- Signals are local to the shared database owner and its child pools.
Independent SQL writers and other replicas need explicit wake
integration. Startup recovers committed prepared rows.
- Disabled Grok retains prepared rows and resumes after a committed
settings change. This does not relax Cloud's binding/assignment/hold
guards.
- Existing send-error cancellation policy is preserved. Sending or
unknown outcomes are not reset or automatically replayed; this is not an
exactly-once delivery change.
- Shutdown awaits the current send within existing transport timeouts
and stops before the next row.
- The index adds write/storage cost but bounds queries independently of
completed history. Existing migration execution runs concurrent index
creation outside a transaction.

## 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 19:07:53 -05:00

7.1 KiB
Raw Blame History

Idle Grok routine dispatch

The Grok dispatcher previously started a one-second timer whenever public MCP was configured. Each tick read experimental settings, even when Grok was otherwise disabled. Enabled ticks scanned prepared dispatches regardless of whether any work existed. The disabled baseline regression observed three settings queries in 3.2 seconds.

The dispatcher now registers with the existing delivery coordinator and shared scheduler. It performs one startup reconciliation. The worker becomes quiet when Grok is disabled or when no prepared dispatch remains. Quiet means no recurring worker SQL or provider calls, including with retained terminal history. This does not establish whole-instance or database suspension.

Ownership and producers

grokRoutineService.reserve is the only supported creator of prepared dispatches. It signals intent before its insert in the existing transaction. Route verification, manual retry of a confirmed rejection, and native-broker assignment admission all use this service. The native broker passes its transaction, so the wake follows the outer commit. Savepoint release is not publication; rollback does not send.

instanceSettingsService.updateExperimental signals the same queue before its existing transactional update. This covers enablePublicMcp, enableNativeRunner, and enableGrokBot changes, including caller-owned outer transactions. It permits prepared work retained while disabled to resume when enabled. Managed settings and environment overlays are process configuration and are reconstructed on restart. Other settings writers update general/default-environment fields, not these flags.

All other dispatch writers move rows away from prepared: send claims, delivery settlement, acknowledgement, explicit recovery, assignment completion, and revocation. None reopens a row as prepared. They cannot introduce hidden new work. A previously scheduled backlog pass may observe cancellation and disarm.

Signals cover the current process's shared database owner and child pools. Independent SQL writers or another process need explicit ingress integration; this change does not claim cross-process notifications. Startup reconstructs committed prepared rows from the existing dispatch table. No queue table is added.

Delivery and recovery

The Grok service continues to own current authority checks, binding locks, prepared-to-sending compare-and-set, secret access, provider delivery, and outcome settlement. The worker does not interpret provider payloads or relocate policy into app wiring. A foreground send and background recovery share the durable claim; only the winner contacts the provider.

Each pass processes at most ten prepared rows. The next lookup uses the same predicate and schedules the existing one-second cadence only while prepared work remains, or after a reconciliation failure. New commits wake the shared worker; its deadline adapter preserves requested generations through concurrent changes and drain. The partial created_at/id index keeps both selection and pending-work lookup independent of retained completed history. The migration builds it concurrently, is replay-safe, and uses the normal snapshot-retention workflow.

The existing send-error policy is preserved: if sending throws and the row is still prepared, the dispatcher cancels it. A row already claimed as sending is never reset or replayed. Accepted, sending, unknown, acknowledged, rejected, and cancelled rows do not become automatic retry work. Explicit operator retry still creates a new intent only for confirmed rejection. This migration does not claim exactly-once delivery or resolve uncertain provider outcomes automatically.

Lifecycle and sleep boundary

The app registers only when public MCP is configured, after the shared coordinator exists. Registration does not delay readiness for a provider send. The coordinator tracks startup work and joins admitted sends on shutdown. Abort prevents the next row from starting; existing HTTP timeouts bound a current transport operation. Later prepared rows remain durable for restart.

Drain and warm standby retain pending requests without passive worker queries. Resume uses the same coordinator retry path; a first write or failed sweep cannot be forgotten merely because dispatch was paused. A disabled feature may retain prepared rows without scheduling a scan; a committed settings change reactivates it. Disabled feature state is not permission to discard durable work or relax Cloud sleep guards. Existing binding/assignment/hold blockers remain unchanged.

Verification

  • The baseline failed the disabled-feature quiet assertion with three SQL reads.
  • 21 real-PostgreSQL lifecycle cases cover disabled/configured idle, first real reservation, prepared startup recovery, all six non-prepared states, re-enable, outer commit/rollback, drain/startup, failed scan through drain, concurrent enqueue, two-page draining, foreground claim overlap, expired unsent work, settings transaction rollback/commit, and shutdown joining a send.
  • The existing 45 Grok qualification cases and 39 startup cases pass together with the new suite: 105 cases total.
  • The four affected settings suites pass 154 cases (259 selected cases total).
  • Workspace typecheck, production build, migration checks and replay pass. A generic-plan probe with 40,000 completed dispatches and one prepared row uses the partial index for both queries (six shared buffers including the routine join; 0.032ms batch and 0.025ms next-work execution in this local probe).
  • Remote PR review and CI must pass before the slice is ready.

The tests use the existing real OAuth/agent/routine fixture and a controlled provider response. A validated disposable external PostgreSQL option permits local execution where embedded PostgreSQL is unavailable; CI retains embedded PostgreSQL by default. Provider-host suspension is outside this local proof.

Current parent reconciliation

The normal merge of target parent 85ce6545bf preserves its complete tree outside this duty's explicit files. Grok dispatch, reservation, settings and app wiring are unchanged from the preceding candidate. The index is regenerated as 0391_third_roulette.sql, with concurrent/idempotent execution. All 389 parent journal entries are unchanged; five generated snapshots remain (0387–0391). Schema drift, migration safety/numbering and fresh apply/replay on two isolated PostgreSQL databases pass.

All 273 cases in nine complete suites pass at 6d81746fe2: dispatch lifecycle, Grok qualification, server startup, scoped deadlines and all five instance-settings suites. Server typecheck and production build pass with the current Runner vendor. The earlier 259-case and populated-plan measurements above remain historical evidence; fresh exact-head reviews and CI are required.

An earlier browser CI run failed during agent-chat fixture restoration with a lock timeout/HTTP 500; the subsequent case observed the experiment left enabled. Those failures were not dispatcher assertions and are retained in the evidence. The later pre-reconciliation a74305e2b4 checks were terminal and green. Neither that older result nor local acceptance substitutes for this candidate's CI.