Files
PaperClipAI/tests/speko-voice-proof
DottaandPaperclip 2238831ecd test(voice): qualify Speko task conversations and Storybook surfaces (#15729)
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 source 249cbc616a. 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.
- Commit 905bc5da5d has 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>
2026-10-13 16:54:41 -05:00
..

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

  1. Set SPEKO_API_KEY or SPEKO_MCP_API_KEY in 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.
  2. Run npm run init. State defaults to ~/.paperclip/speko-proof/state.json (mode 0600, parent directory 0700). SPEKO_PROOF_STATE can select a separate private path. Initialization refuses to overwrite an existing run. The signing secret is generated locally.
  3. Run npm run serve; it listens only on 127.0.0.1:3198 (override with SPEKO_PROOF_PORT). It stops after 15 minutes. Only /health and the signed POST /speko-proof/tool exist. Do not expose a Paperclip development server.
  4. Configure an explicitly scoped HTTPS reverse proxy to this receiver. Verify POST /speko-proof/tool with an unsigned JSON body returns 401. Set SPEKO_PROOF_PUBLIC_ORIGIN to that HTTPS origin, with no path or credentials.
  5. 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.
  6. 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

  1. Start the call. Note browser/device, provider session ID, UTC time, agent/model configuration, and whether the provider UI says it retains the call.
  2. Say “Start the test.” Measure speech-end to acknowledgment.
  3. 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.
  4. 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.
  5. Compare heard output to tool retrieval evidence and any provider transcript. A result_returned_to_tool event is not a spoken-playback acknowledgment. Record what was actually heard and how timings were measured.
  6. 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 is continuous-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=1 with SPEKO_PROOF_PROMPT_FILE=application-notification.txt: send one explicitly labeled application notification on native lk.chat after 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's calls.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.