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.