Files
PaperClipAI/tests/speko-voice-proof/README.md
T
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

150 lines
8.4 KiB
Markdown

# 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
```sh
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:
```sh
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:
```sh
node summarize-run.mjs /absolute/private/run/report.json reports/new-run.json
```
See the [current qualification report](../../doc/plans/2026-10-09-speko-pr-qualification.md)
and [E2E runbook](../../doc/plans/2026-09-11-speko-voice-e2e-runbook.md) for inspectable
results and remaining acceptance gaps. Historical private call reports are not
included in this checkout.