Third PR: depends on #15728 and #15727. The user has reviewed and authorized merging this experimental feature stack. ## Thinking Path > - Paperclip manages agent work through tasks and governed connections. > - Chat connections already bind external conversations to those tasks. > - Speko handles voice and telephony, while the selected Paperclip agent does the work. > - A call needs durable session authority, ordered follow-ups, and safe result delivery. > - This PR adds repeatable qualification for the Speko voice task integration. > - This PR depends on #15728, which depends on #15727. Speko stays experimental. ## Linked Issues or Issue Description **Problem or motivation** Phone and browser voice conversations cannot use the chat connection task lifecycle. A separate voice runtime is needed to preserve task authority, retries, and reply history. **Proposed solution** Add company-scoped voice sessions, signed callbacks, durable replies, inbound intake, and an agent-facing outbound-call tool. Reuse existing tasks, queues, secrets, and publications. **Roadmap alignment** This extends the experimental chat connector entry points under Agent Chat and Connected Apps. The project owner requested this feature. Refs #11418 for the original provider catalog proposal. This implementation uses the newer chat connection infrastructure rather than an MCP catalog-only integration. Refs #15721 for remote OpenCode startup. ## What Changed - Add a real Paperclip server/database E2E fixture that stubs only external Speko transport and the execution provider. - Add authenticated multi-user and cross-company qualification journeys. - Add Storybook interaction, accessibility, responsive, and visual-suite integration. - Add an E01–E16 runbook, component-to-story inventory, and current redacted qualification report. - Keep historical private call traces out of the PR. Provider proof tools are opt-in. ## Verification - All 21 standalone proof tests and browser client/server syntax checks pass. They cover signatures, redaction, cleanup retry, final audio saves, bounded report transport and late server write ordering. No live calls were placed. - All 27 focused UI/controller regressions, UI typecheck, token gates, and 28 map/catalog structural tests pass. The shared connection cache test covers pause/resume invalidation during the 30-second fresh period. - Storybook builds 157 Speko production-component stories. The complete prior 156-story set passed all 624 checks on UI source249cbc616a. After the final cache repair, all 152 affected page/launcher checks passed across both themes, 390/1200 widths and reduced motion. Real microphone access and external provider requests are blocked. - Hosted Storybook origin isolation and representative narrow-screen stories pass. Provider requests remain blocked. - Source annotations follow the current generated-catalog contract. No generated catalog or lockfile is included. Three inherited GitHub-heading tests passed after master reconciliation. - Commit905bc5da5dhas 55 passing checks, including required verify/E2E gates, and a fresh Greptile 5/5 review with zero unresolved findings. Local fixture success does not establish provider or spoken-playback acceptance. - The separate integrated browser run passed two journeys and failed browser answer retrieval. Real browser/telephone playback and timing acceptance remain outstanding. See the dated qualification report and E01–E16 runbook. Speko stays experimental. ## Risks - Voice sessions add migrations and provider-dependent lifecycle behavior. Migrations preserve existing chat and AI transports. - Callback origins must be reachable HTTPS. Provider keys and signing secrets remain vaulted. - Unverified callers receive task-scoped low-trust authority. Caller ID does not grant private task access. - Outbound telephony incurs provider charges. Provider acceptance is not proof that the caller heard an answer. - Keep the feature behind the existing experimental chat setting until the real browser, inbound, outbound, and delayed-result acceptance matrix passes. ## Model Used OpenAI Codex, GPT-6, with tool use and code execution. The exact serving model ID and context window are not exposed in this session. ## 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 - [ ] 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>
Speko delayed voice feasibility probe
This isolated harness implements the first gate in the approved Speko connection plan. It does not implement a Paperclip connection, touch Paperclip tasks, or qualify browser/phone acceptance on its own. A hosted Speko agent must keep retrieving a result for at least 60 seconds, accept an interruption and follow-up, then speak the result without another request from the caller.
The harness deliberately has no UI, microphone capture, database, company credentials, private task data, or recording download. Production integration is gated on real-provider evidence; its tests must not be reused as claims about Paperclip permissions, persistence, or actual spoken playback.
Local protocol checks
cd tests/speko-voice-proof
npm install --ignore-scripts --no-package-lock
npm test
The delay test uses an injected clock, so it checks the 60-second withholding contract without a minute-long unit test. It cannot prove that Speko continues polling, that its media runtime supports interruptions, or that audio plays.
Live setup
- Set
SPEKO_API_KEYorSPEKO_MCP_API_KEYin the shell environment from a private secret store. Never paste it into a command argument, fixture, trace, screenshot, or issue. The API key stays in the provisioning process only. - Run
npm run init. State defaults to~/.paperclip/speko-proof/state.json(mode 0600, parent directory 0700).SPEKO_PROOF_STATEcan select a separate private path. Initialization refuses to overwrite an existing run. The signing secret is generated locally. - Run
npm run serve; it listens only on127.0.0.1:3198(override withSPEKO_PROOF_PORT). It stops after 15 minutes. Only/healthand the signedPOST /speko-proof/toolexist. Do not expose a Paperclip development server. - Configure an explicitly scoped HTTPS reverse proxy to this receiver. Verify
POST /speko-proof/toolwith an unsigned JSON body returns 401. SetSPEKO_PROOF_PUBLIC_ORIGINto that HTTPS origin, with no path or credentials. - Run
npm run provision. It creates a dedicated synthetic test persona and two tools. It does not provision phone numbers or place calls. Partial creation is recorded. If an API result is uncertain, it refuses blind retries; reconcile the dedicated agent/tools in Speko before clearing the pending marker. Use a fresh state/agent when changing the callback origin or secret. - Open the named Paperclip delayed voice proof agent in Speko. Its Browser test runs the hosted deployed agent. Use only synthetic test speech.
State contains secrets. Do not copy it into reports. The final evidence.json
contains event kinds, timings, counts, cursors, and the generated synthetic
verification phrase (never caller text). The probe
pins its first valid signed provider session and rejects other sessions; restart
the receiver between conversations. This first-delivery binding is acceptable
only for an isolated synthetic probe, not the production authority model.
Conversation and evidence
- Start the call. Note browser/device, provider session ID, UTC time, agent/model configuration, and whether the provider UI says it retains the call.
- Say “Start the test.” Measure speech-end to acknowledgment.
- At roughly 15 seconds, interrupt with “Add a follow-up.” Confirm it is accepted into the existing synthetic job. Do not repeatedly ask for the answer.
- Stay on at least 75 seconds. Expect an automatically spoken, unpredictable four-digit verification number and follow-up count 1 after the 60-second delay. A fabricated number before 60 seconds is a failure.
- Compare heard output to tool retrieval evidence and any provider transcript.
A
result_returned_to_toolevent is not a spoken-playback acknowledgment. Record what was actually heard and how timings were measured. - End the call and stop the receiver with SIGINT. Preserve sanitized evidence. Remove only the reverse-proxy route added for this probe; never reset an existing proxy configuration. Do not download provider recordings by default.
Repeat for phone using a designated test number and configured Speko number. An untested transport remains blocked. If autonomous polling stops or the worker cannot accept follow-ups during polling, record a failed feasibility gate and prepare the questions in the qualification report before expanding the feature.
The full implementation and acceptance matrix is in
doc/plans/2026-09-11-speko-voice-e2e-runbook.md.
Automated hosted-worker reproduction
npm run live starts a paid real Speko cascade session, with a Node LiveKit
media client and this signed receiver. It does not run in the normal test suite,
request a microphone, dial a telephone, create Paperclip work, or implement a
replacement voice worker. Do not run serve at the same time: live owns the
receiver port. Configure the reachable HTTPS callback and dedicated test agent
first, using the same private state as provision.
With a Speko credential already in the environment:
NODE_ENV=production SPEKO_PROOF_LLM_PROVIDER=openai:gpt-4.1 npm run live
The default input is native lk.chat text, which isolates hosted tool execution
from speech recognition. It still receives real generated speech. Set
SPEKO_PROOF_INPUT=audio, SPEKO_PROOF_START_AUDIO, and
SPEKO_PROOF_FOLLOWUP_AUDIO to use synthetic speech instead. Both audio files
must be raw signed little-endian 16-bit PCM, mono, 48 kHz, saying “Start the test”
and “Add a follow-up.” This input also exercises hosted STT. It is not browser
SDK, microphone, Safari, phone, or audible interruption qualification.
Optional settings:
SPEKO_PROOF_OUTPUT: a new private directory; reuse is rejected before session creation. Defaults to a timestamped directory beside private state.SPEKO_PROOF_PROMPT_FILE: a per-session prompt override. The stricter tested prompt iscontinuous-polling.txt; it has not passed the delayed-result gate.SPEKO_PROOF_STATE/SPEKO_PROOF_PORT: same meaning as the CLI.SPEKO_PROOF_NOTIFY_READY=1withSPEKO_PROOF_PROMPT_FILE=application-notification.txt: send one explicitly labeled application notification on nativelk.chatafter the synthetic 60-second deadline. It carries no answer or authority; the agent still fetches the result through the signed tool. This is a typed user-role transport, not Speko's unsupported contextual-update channel. Product UI must preserve application provenance rather than attribute it to something the caller said.SPEKO_PROOF_NOTIFY_WEB_JOIN=1: experimental separate, data-only participant obtained through SDK 0.5.3'scalls.webJoin. Requires notifications enabled. No audio is published because web-join audio invokes agent takeover. A joined transport does not establish that the worker will consume its text.SPEKO_PROOF_INTERRUPT_ACK=1: inject the follow-up while acknowledgment audio is arriving, rather than after a fixed 15 seconds. Private audio-burst timings permit inspection of when playback stopped; this does not establish physical-device playback latency.
Each run journals creation intent and the resulting session ID without media credentials. It sends two instructions and detects excess submissions, waits for the delayed result, then asks Speko to end the session. A 180-second provider cap bounds abandoned runs; creation is never automatically retried. SIGINT/SIGTERM request cleanup. Verify provider termination if a process is forcibly killed.
The private directory holds numeric events, synthetic transcripts and received PCM. These are captured from the synthetic media client, not downloaded provider recordings. Do not use human or private input with this client. Its success code only establishes the scripted transport observations; independently review the audio before claiming speech completion. Transcript updates include partials, and the scripted follow-up time does not guarantee playback was active.
Publish a restricted report without transcripts, audio, keys or caller data:
node summarize-run.mjs /absolute/private/run/report.json reports/new-run.json
See the current qualification report and E2E runbook for inspectable results and remaining acceptance gaps. Historical private call reports are not included in this checkout.