## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Voice calls retain durable session state and provider identities. > - The chat maintenance timer scans live voice sessions even when no call exists. > - Session writes, saved expiry and active-call inspection deadlines identify real work. > - This pull request moves live-session recovery to the shared coordinator. > - Idle session recovery becomes quiet without losing exact-call cleanup or current authorization checks. ## Linked Issues or Issue Description Refs #17572. This PR is stacked on receiver discovery. It owns only live voice session recovery. Reply delivery, inbound admission cleanup, inbound history retention and call reports remain separate migration slices. **What happened?** The one-second chat maintenance lane reads voice settings and scans live sessions even when an instance has no calls or only terminal history. **What did you expect?** Reconstruct live calls on startup. Wake on committed session or configuration changes. Inspect active calls and retry cleanup at actual deadlines. Stop querying when no live obligation remains. **Steps to reproduce** Start an instance with no live voice sessions. Let startup settle. The old maintenance method continues to read settings and session inventory every chat maintenance pass. ## What Changed - Add a voice-owned bounded deadline reader and scoped worker on the existing coordinator. Retain concurrent writes across pages and reset partial scans on restart. No tables or migrations are added. - Separate generic connection health observations from configuration changes. Existing receiver and command consumers retain their notifications. Voice eligibility and provider operations remain in the voice owner. - Signal existing session transactions and configuration/feature changes. Keep provider calls outside database transactions. Standalone session writes use one small transaction helper. - Preserve saved expiry, the two-minute unknown-creation grace, active-call authorization and missed-hangup inspection, and exact-provider cleanup. Recovery never creates a provider call. - Retry transient authority lookup failures instead of treating them as confirmed revocation. - Fence expiration against a provider identity bound after discovery. A late known call remains eligible for cleanup. - Preserve work through rollback, drain, restart, concurrent refresh, backoff and shutdown. Page inventory and rotate bounded batches across companies. - Leave the four other voice maintenance duties in the periodic lane. - Document producer coverage and the supported writer contract in `doc/idle-voice-session-recovery.md` and the dated audit. ## Verification Published head: `f0b2e3cc22a18e7d08b10d4fc02aae4a19a52f30`. Receiver #17572 has landed in master at `dce004259394486f6361d0c0dd5a33a5272acc0a`. This normal merge reconciles squash ancestry; the complete 22-file voice recovery patch is byte-for-byte identical to the reviewed and tested `ad79c7e805` patch, while all unrelated master changes are preserved. The PR now targets master. Fresh exact-head CI and Greptile are running; interrupted Ponytail turns are not completed verdicts under the maintainer’s clarified merge policy, and an actual failing verdict still blocks. - **210 distinct targeted cases pass on the current repair**: 19 complete recovery lifecycle cases and 191 complete voice-store, coordinator, batching, startup and hostname cases. Production server typecheck and build pass. Later edits record evidence only. - CI `40633667141` on prior head `1b6323c164` failed the feature-change assertion with `34050 < 25000`; its verification aggregate also failed. The test now disables automatically advancing fake time, drives 20 controlled scheduler turns and requires all 205 calls to close before an unrelated inspection deadline can mask a lost wake. Removing generation replay fails with missing closures. The repair does not benchmark database/host speed or raise the elapsed-time bound. - A real committed exact-call regression reproduces wildcard-backoff starvation. The worker keeps the broad retry but processes independent exact hints and cached due calls. Each check reloads current state and authority. Failed exact scopes remain fenced. Removing wildcard isolation fails the intended assertion; a successful broad retry then leaves the worker SQL-quiet. Full-row and lean queries now share the state/scope/cursor predicate. - Earlier acceptance on `712f3862b5` passed 725 distinct targeted cases. Its measurements remain useful history: five real metadata writes and one health check over 205 future calls selected 1,230 rows before the inventory repair and zero afterward, without changing the saved deadline. Those older test counts are not added to the current 210-case count. - Both external voice fixtures self-migrate; the current full voice-store run uses a separate initially empty database. Earlier fixture/method/observer failures, source mutations and partial edit attempts remain in the [current audit](https://github.com/paperclipai/paperclip/blob/ad79c7e805367fc7e2df3ea53c942ca6c7d27365/doc/plans/2026-10-25-voice-maintenance-sleep-audit.md#wildcard-failure-and-deterministic-generation-acceptance-october-28). Source is restored after guarded mutations. - Older CI `40632057899` failed because its trusted policy script was absent from the checkout, while build separately received a runner shutdown signal. E2E shards succeeded but their aggregate failed on policy. The current checkout includes the landed policy tooling and its checker/tests pass locally. The shutdown cause is unproven; neither old failed run is counted as passing. Earlier full-local attempts stopped on embedded PostgreSQL setup failures and did not pass. - Fresh exact-head Greptile, security and all CI must pass before merge. A completed failing Ponytail verdict blocks; an interrupted turn without a verdict does not. No full current local repository matrix, live provider acceptance, multi-writer support, Cloud wake, host suspension or savings is claimed. ## Risks - Process-local hints cover the existing active writer and shared clients. Independent SQL writers or another replica need explicit wake integration. - Membership, task binding, phone preferences and other authority inputs remain checked on the existing 30-second active-call inspection deadline and on use. This is not an instant cleanup guarantee for every authority mutation. - Unknown creation retains its existing maximum-duration grace. Cleanup failures retain retries. A provider request is never replayed merely because its creation response is unknown. - Genuine configuration changes retain bounded reconstruction. Metadata writes can probe parent eligibility, while health-only observations leave voice recovery quiet. Paging state is process-local and reconstructs from durable rows after restart. - Active calls, the other voice maintenance owners and connector transports remain real obligations. This does not expand Cloud alarm coverage, relax sleep eligibility or prove deployed suspension. - The receiver and Discord parents have landed. Current-master compatibility and exact-head CI and Greptile remain merge gates. ## Model Used OpenAI GPT-6 via Codex, with repository inspection, reasoning, code execution and test tools. The runtime does not expose a more specific deployment identifier or context-window size. ## 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 linked existing issues or described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal ticket identifier - [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>
12 KiB
Voice session recovery without idle discovery
The live-session worker replaces the session-discovery portion of the recurring voice maintenance call. It uses the existing delivery coordinator, transaction signals and shared scheduler. It adds no tables or migrations. Reply delivery, inbound admission cleanup, inbound history retention and call reports are separate obligations; their existing maintenance remains until their own migrations land.
Work and deadlines
Existing chat_voice_sessions rows are authoritative. Startup reconstructs live
sessions in UUID pages of 100, joined to their same-company endpoint and connection.
Each background refresh reads at most one inventory page. An unfinished pass has
a one-second continuation; it does not create a permanent empty scan. Only timing
and eligibility fields are selected for discovery. The explicit foreground
reconciliation method keeps its complete inventory behavior.
Terminal history does not create a live-session recovery obligation. With no live
sessions, the worker also avoids reading experimental settings after reconstruction.
| State | Next obligation |
|---|---|
| No provider identity yet | Existing expiresAt plus two minutes of teardown grace; no provider creation replay |
| Known provider, ending or invalidated configuration | Immediate cleanup; a recorded cleanup failure waits 30 seconds |
| Connecting or active call | Earlier of session expiry and updatedAt + 30 seconds for current authorization and missed-hangup inspection |
| Other live state with provider identity | Saved session expiry |
| Ended, failed or expired | No live-session work; reports have a separate owner |
Provider maximum duration remains the final bound for uncertain creation. A late provider response can bind identity while expiry is being considered. The expiration write requires that provider identity is still absent, so a newly known call retains exact-call cleanup rather than being marked terminal prematurely.
Each due call is reloaded before execution. The voice store checks current company, endpoint, credentials, caller, task, guest sandbox and binding authority. A 403/404 means authority no longer permits the call. A transient lookup error retains a retry; it does not authorize ending an otherwise valid call. The existing voice service owns provider inspection and termination, including single-flight termination and ambiguous-response confirmation. Recovery never creates a call.
Producers and transaction ownership
| Owner | Changes that wake live-session recovery |
|---|---|
voice-session-store.ts |
Reservation, guest promotion, connecting-to-active transition and reply cursor writes that change the inspection timestamp |
voice-session-service.ts |
Creation claim, binding, known/unknown creation failure, user end, cleanup outcome and foreground inspection |
voice-inbound-service.ts |
Guest session creation, guest denial and signed terminal lifecycle callbacks |
voice-reply-push.ts |
Confirmed remote end and unknown delivery outcome that changes session state/timestamp |
| Chat configuration helper | Endpoint configuration, pause, reconnect, generation/credential changes, connection hints and company removal |
| Generic tool/access services | Voice connection API mutation, removal and credential revocation during access removal |
| Instance settings | Explicit chat-connector feature enable/disable |
Existing transactions register the awaited signal before the session/configuration
write. Standalone session updates use the small withVoiceSessionChange helper.
Provider requests remain outside database transactions. Nested writes retain outer
commit ownership, rollback does not dispatch work, and uncertain commit settlement
stays with the shared database protocol.
Membership changes, task reassignment, phone preferences and other authorization
inputs that do not mutate the session or connection remain checked on the existing
30-second active-call inspection deadline, as well as on use. This slice does not
promise instant cleanup for every authority mutation or add cross-cutting voice
hooks throughout those owners. Session activity that changes updatedAt retains
the existing inspection-time semantics. The session expiry is always an upper
bound for authorized active work.
The generic chat connection hint does not always carry a transport. It can cause a bounded empty voice lookup after a chat configuration write. It does not create idle polling or a recurring deadline. The shared helper contains only routing; voice eligibility, authority and provider policy stay with the voice owner.
Bounded configuration refreshes
The voice owner remembers the endpoint/connection fields that affect its existing deadline policy: endpoint status, runtime generation, connection enabled state, connection identity, and the chat feature flag. A broad configuration hint first reads a bounded page of these parent records. If those facts are unchanged, the cached call deadlines stay in place and no call inventory is read. Metadata-only writes do not move business deadlines. An unknown or changed parent requires bounded reconstruction; a real disablement still settles every affected call.
Only the voice owner interprets these facts. The generic health producer now sends a health-observation notification through the existing chat routing owner. It preserves receiver and command signals without generating a voice-configuration wake. An observation changes neither voice eligibility nor the saved call deadline. Supported session producers continue to signal their exact session ID.
The worker processes at most 25 dirty scopes and 25 provider checks per pass. Scope pagination continues without restarting on every newer hint. Failures retain their backoff and block stale entries in the failed scope. Successful pages can make progress while the remaining inventory is discovered. The existing coordinator can use the actual error deadline, including an already-due healthy backlog, rather than postponing it to an unrelated fallback retry.
Failure and lifecycle behavior
Refreshes coalesce by scope. Paged reconstruction keeps narrower producer hints, including a newly committed session behind the UUID cursor. A scope generation retains configuration changes that arrive between pages. Seen IDs protect concurrent refresh results; absent cached entries are removed only when the scope's inventory is complete. A new worker resets partial inventory state before reconstructing its saved obligations. Changes arriving during refresh remain dirty. Failed refreshes block use of stale cached entries in that scope. Per-scope and per-call failure backoff starts at 30 seconds and caps at five minutes. New hints do not erase failure backoff. Batches rotate across companies, process at most 25 calls, and continue an already due backlog after one second. A failed provider does not stall independent calls.
Drain and standby retain dirty work without passive SQL. Resume processes that work, and restart reconstructs deadlines from the rows. Shutdown joins admitted provider work and removes the coordinator's subscriptions and timers. Retained terminal history becomes quiet after any already committed hints settle.
Process-local signals support the existing single active database writer and its shared clients. Independent SQL writers or another replica need an explicit wake contract. This change neither expands Cloud alarm coverage nor relaxes sleep eligibility. Active calls and their real deadlines remain obligations; local worker quiet is not proof of host suspension or fleet savings.
Verification
The voice integration suite exercises actual service producers, guest intake, signed callbacks, current authority, unknown creation and adjacent reply/report behavior. The recovery-owner suite uses real PostgreSQL and controlled provider operations for outer commit/rollback, drain, restart, scope failure, mutation during refresh, paging, company fairness, shutdown and the late-provider-binding race. The latter race failed before the write-time guard and passes afterward.
The dated audit records exact commands, results, initial fixture failures and remaining acceptance work: voice migration audit.
Landed-foundation acceptance, October 27
The voice repair now integrates receiver parent 6e88ca0201, including Discord
547fdf2139 and master f8563d2507. Exact-patch comparison preserves the repaired
22-file scope and master's shared task-drain and native-checkpoint queue names.
No database file is part of this slice.
All 725 distinct targeted cases pass again: 112 complete voice/shared/startup cases, 97 complete voice-store cases, 469 complete tool-access cases, and 47 complete health/receiver cases. Server production typecheck and build pass. Voice worker/store and health/receiver fixtures use separate initially empty self-migrating databases. Tool access uses a separate migrated clone. One initial tool-access invocation supplied the generic fixture variable instead of the suite-specific variable: 23 cases passed and 446 skipped. It was not a complete suite result or an assertion failure. The corrected invocation executes and passes all 469. Tests are not counted twice. Expected controlled failures and listener warnings remain in logs. Only documentation changes follow these checks. Fresh current-head reviews, CI and parent landing remain required. No full local matrix, provider acceptance, external wake or deployed sleep result is claimed.
Wildcard failure and deterministic generation acceptance, October 28
Current-head CI 40633667141 fails server shard 1 at the feature-change test's
34,050 ms result against its 25,000 ms bound; verification fails as an aggregate.
This is an actual required assertion failure, distinct from the older interrupted
build and missing policy script. The old test mixed automatically advancing fake
time with real database latency. It now disables automatic clock advancement,
drives 20 explicit scheduler turns, waits for each pass without advancing the
clock, and requires every invalidated call to close before the unrelated periodic
authority deadline. Removing generation replay still fails with missing closures.
The test no longer benchmarks host/database speed or raises its business bound.
A real committed-session regression also reproduces wildcard starvation: while one broad reconstruction backs off, a newly ending exact call is not closed. The worker now retains the broad retry but considers ready exact hints and cached due calls independently. Each due call still reloads state and authorization in its owning check before effects. Failed exact scopes retain their own fence; the existing stale-cache failure regression still passes. Removing wildcard isolation fails the new exact-call assertion. Broad reconstruction becomes quiet after its successful retry, with no continuing SQL afterward.
The full-row execution lookup and lean inventory lookup now share the live-state, scope and cursor predicate. Their projections remain intentionally different. An initial edit script rejected the header cursor name before applying the timing and predicate changes; the intermediate 19-case result covers only the earlier worker repair. It is not the final repair evidence. The corrected source passes all 19 lifecycle cases and 191 complete voice-store/shared/startup cases: 210 distinct targeted cases. Production server typecheck and build pass. Both guarded regressions fail at their intended effect assertion and restore source afterward. The fresh voice-store database self-migrates. Existing controlled failure logs and signal-listener warnings remain recorded. The older 725-case acceptance belongs to its stated revision and is not added to this count. Fresh current-head reviews and all CI remain required before merging after receiver discovery.