* feat: agent-voice reference skillpack (Mars + Venus) + copy-into-host-repo install paradigm Ships a new skillpack paradigm: gbrain holds the REFERENCE content; `gbrain integrations install agent-voice --target <repo>` COPIES it into the operator's host agent repo where it becomes user-owned and mutable. Future refresh is diff-and-propose against per-file SHA-256 hashes from .gbrain-source.json, not blind overwrite. What ships: - recipes/agent-voice.md entrypoint + recipes/agent-voice/ bundle - Two voice personas (Mars dual-mode SOLO/DEMO, Venus executive assistant) with PII / private-agent-name / hardcoded-path scrubbed out - WebRTC-first browser client (call.html) with ?test=1 gated instrumentation and Web Audio API tee -> MediaRecorder capture for E2E roundtrip testing - Read-only tool router (D14-A allow-list: search, query, get_page, list_pages, find_experts, get_recent_salience, get_recent_transcripts, read_article). Write ops permanently denylisted; opt-in via local override - Persona-aware prompt builder with identity-first composition + Unicode sanitization for OpenAI Realtime API safety - Upstream-error classifier (HTTP 429/500/503 -> soft-fail, plumbing -> hard) - Three SKILL.md skills (voice-persona-mars, voice-persona-venus, voice-post-call) with routing-eval.jsonl fixtures - 99 host-side tests (vitest-compatible, runs in bun) covering registry, prompt-shape privacy guards, tool allow-list, upstream classifier - install/manifest.json + refresh-algorithm.md + post-install-hint.md Privacy infrastructure: - scripts/check-no-pii-in-agent-voice.sh wired into bun run verify Shape regex (phone/email/SSN/JWT/bearer/credit-card) + path patterns + $AGENT_VOICE_PII_BLOCKLIST env-driven name blocklist - scripts/import-from-upstream.sh + scripts/upstream-scrub-table.txt Deterministic refresh from upstream voice-agent source. Placeholder- driven (envsubst-expanded at run time) so no private names land in checked-in files - recipes/agent-voice/code/lib/personas/private-name-blocklist.json Single source of truth for the regex contract (shape categories + path patterns + env-var contract for operator-specific names) src/ surface: - src/commands/integrations.ts gains `install <recipe-id>` subcommand with install_kind: 'local-managed' | 'copy-into-host-repo' discriminator. Path-traversal hardening (rejects '..', absolute paths, symlink escapes). Refuses target == gbrain itself, missing .git, existing files (without --overwrite). Writes .gbrain-source.json with per-file SHA-256. Appends resolver rows to host repo's RESOLVER.md or AGENTS.md. - test/integrations-install.test.ts: 11 cases (happy path, manifest shape, no upstream_repo field per D11-A, resolver appending, file modes, refusal cases, dry-run) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.36.0.0) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(privacy): scrub literal private agent names from prompt-shape tests + guard script The prompt-shape tests carried regex patterns naming the literal banned terms (Garry/Steph/Garrison/Solomon/Herbert/Wintermute) inline. CLAUDE.md's "never use Wintermute in any public artifact" applies to test source files too. Master's check-privacy.sh correctly caught this. Replaced with env-driven check that reads AGENT_VOICE_PII_BLOCKLIST (the single source of truth from private-name-blocklist.json). Same enforcement guarantee via the env var, zero literal names in shipped source. Also scrubbed the literal /data/.openclaw/ from the guard script's comment and the literal 'tell_wintermute' from the venus write-tools test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: ship all v0.36.0.0 deferred items in this PR (E2E + evals + pipeline + refresh + multilingual + twilio deprecation) Closes the "deferred to follow-up" section of the v0.36.0.0 CHANGELOG. E2E tests + harness (env-gated): - tests/e2e/voice-roundtrip.test.mjs — spawns server, drives puppeteer + fake-audio, three-tier assertions (CONNECTION hard, NON-SILENT hard, SEMANTIC soft via Whisper + LLM judge). Upstream errors (429/500/503, WS 1011/1013) soft-fail via lib/upstream-classifier.mjs. - tests/e2e/voice-full-flow.test.mjs — wraps openclaw doing the install, then runs the roundtrip. Friction-discovery flavor, NOT a ship gate. - tests/e2e/lib/browser-audio.mjs — puppeteer + fake-audio harness; reads window._gbrainTest namespace; PCM RMS-variance helper. - tests/e2e/lib/whisper-judge.mjs — Whisper transcription + LLM-judge for SEMANTIC tier. - tests/e2e/audio-fixtures/utterance-{add,joke,brain-query}.wav — 16kHz mono WAV via `say` + ffmpeg, committed for reproducibility. - test/fixtures/claw-test-scenarios/voice-agent-install/{BRIEF.md, scenario.json, expected.json} — labeled BENCHMARK_FRICTION, blocks_ship=false. LLM-judge persona evals + synthetic canonical baselines: - tests/evals/judge.mjs — gateway-routed 3-model (Claude + GPT + Gemini) harness with 4-strategy JSON repair + 2/3-quorum aggregation (per the v0.27.x cross-modal pattern). Pass criterion: every axis mean ≥7 AND no model <5. - tests/evals/fixtures/{mars-solo,mars-demo,venus,persona-routing,mars-multilingual}.jsonl — 5 fixture sets covering all axes. - tests/evals/{mars-eval,venus-eval,mars-multilingual-eval,persona-routing-eval}.mjs — per-axis drivers. - tests/evals/baseline-runs/canonical/*.json — agent-authored synthetic exemplars (PII-impossible by construction; demonstrate expected pass shape; never overwrite with live model output). - tests/evals/baseline-runs/.gitignore — live receipts excluded. DIY pipeline (Option B): - code/pipeline.mjs — streaming STT (Deepgram nova-2) + LLM (Claude Sonnet 4.6 streaming SSE with sentence-boundary TTS dispatch) + TTS (Cartesia primary, OpenAI TTS fallback). 20-turn history cap, exponential-backoff reconnects, 25s keepalives, VAD presets (quiet/normal/noisy/very_noisy), barge-in via STT speechStart → LLM interrupt. Modular adapters for swapping providers. --refresh mode (D3-A diff-and-propose): - src/commands/integrations.ts: refreshRecipeIntoHostRepo() + classifyForRefresh() implementing the five states from refresh-algorithm.md (unchanged-identical, unchanged-stale, locally-modified, source-deleted, host-deleted, new-in-manifest). Transaction journal at .gbrain-source.refresh.log. Default policy: preserve operator's local edits (keep-mine); --auto take-theirs to overwrite; --dry-run for preview. - test/integrations-install.test.ts: 7 new test cases pinning each classification state + default-preserve behavior + take-theirs overwrite + transaction journal + refusal on uninstalled target. Mars multilingual restore: - code/lib/personas/mars.mjs: explicit cross-lingual rule (Mandarin, Spanish, French, Japanese, Korean default to English but follow the speaker). Voice (Orus) supports the languages natively. - tests/unit/mars-prompt-shape.test.mjs: assertion flipped from "MUST NOT claim multilingual" to "declares cross-lingual capability with English bias." - tests/evals/fixtures/mars-multilingual.jsonl: 5 fixtures across Mandarin/Spanish/Japanese/French + explicit switch-back, pinned by mars-multilingual-eval.mjs. Twilio recipe deprecation: - recipes/twilio-voice-brain.md: deprecation banner pointing at agent-voice.md. Frontmatter version bumped to 0.8.2. Will be removed in v0.37. Verify: bun run verify clean, 6736+ unit tests pass, 18/18 install+refresh tests pass, 96/98 host-side persona/tool/classifier tests pass (2 skipped env-gated). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: update CHANGELOG — all v0.36.0.0 deferred items now shipped in this PR Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: rebump version v0.36.0.0 → v0.37.0.0 Captures the wave-1 + wave-2 scope at the v0.37 slot. The bump reflects the size of what this PR ships: copy-into-host-repo install paradigm (new install_kind discriminator + new install/refresh subcommand) + Mars/Venus voice agent reference + 5,500+ LOC of vendored scrubbed code + 4 LLM-judge eval suites + 2 env-gated E2E test suites + DIY Option B pipeline + 18-case install subcommand test coverage. A minor bump felt too small. Side fix: privacy guard caught two stale literal "wintermute" and "/data/.openclaw/" references in wave-2 files (voice-full-flow.test.mjs comment, expected.json blocklist payload). Both replaced with env-driven references to $AGENT_VOICE_PII_BLOCKLIST matching the D15-A pattern from the original review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: rebump v0.37.0.0 → v0.40.0.0 Jumps past the v0.37/v0.38/v0.39 slots master might claim in subsequent PRs. The wave's scope (copy-into-host-repo skillpack paradigm + agent-voice + install/refresh + LLM-judge evals + DIY pipeline + Mars multilingual) justifies a larger version arithmetic step. Files bumped: - VERSION 0.37.0.0 → 0.40.0.0 - package.json 0.37.0.0 → 0.40.0.0 - CHANGELOG.md header + "To take advantage of v0.40.0.0" block - recipes/twilio-voice-brain.md deprecation banner (now "removed in v0.41") - recipes/agent-voice/tests/evals/mars-multilingual-eval.mjs comment Left alone: master's pre-existing "v0.37+" roadmap labels in src/core/calibration/*, src/core/cycle/*, DESIGN.md, CLAUDE.md, etc. Those are master's author-intent references to "the next planned release" relative to master's frame at the time — rewriting them just to keep numbering consistent would overreach. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.9 KiB
id, name, version, description, category, install_kind, requires, secrets, health_checks, setup_time, cost_estimate
| id | name | version | description | category | install_kind | requires | secrets | health_checks | setup_time | cost_estimate | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| agent-voice | Voice Personas (Mars + Venus) | 0.1.0 | WebRTC-first voice agent reference (Mars + Venus personas, optional Twilio adapter). Skillpack-as-reference paradigm — the install-time agent COPIES code into your host agent repo where it becomes user-owned and mutable, NOT a runtime gbrain dependency. | voice | copy-into-host-repo |
|
|
10 min | $0.06-0.24/min OpenAI Realtime, optional $1-2/mo Twilio number |
Voice Personas: Mars + Venus
A reference voice agent (WebRTC-first; OpenAI Realtime) shipped as copy-into-your-repo content rather than runtime gbrain skills. The install-time agent reads this recipe, copies the bundle into your host agent repo (e.g. ~/git/your-agent-repo/), wires the resolver, and starts the voice server. From there, the code lives in YOUR repo, on YOUR cadence, with YOUR edits.
What ships in the bundle
- Two personas — Mars (introspective thought partner; voice
Orus) and Venus (sharp executive assistant; voiceAoede). - WebRTC browser client at
/call?test=1for the production-grade voice loop. Production load installs zero test instrumentation;?test=1enables Web Audio API tee → MediaRecorder capture for the E2E. - Tool router with a read-only allow-list by default (search, query, get_page, list_pages, find_experts, get_recent_salience, get_recent_transcripts, read_article). Write ops are denylisted; operators opt in to a bounded set via local override.
- Persona-aware prompt builder with identity-first composition + Unicode sanitization for Realtime API safety.
- Optional Twilio adapter (
/voiceTwiML, WSS bridge) for phone inbound. Skip if you only want browser voice. - Three skills for resolver routing:
voice-persona-mars,voice-persona-venus,voice-post-call. - Unit + E2E tests that ride with the copy. PII-shape regex guards every prompt, classifier triages upstream vs plumbing failures.
The skillpack-as-reference paradigm
Earlier gbrain skillpacks installed to ~/.gbrain/skills/<name>/ as managed-block-canonical first-class skills. The user's local edits drifted from the canonical and updates were either "overwrite local" or "skip update" — neither is what an operator wants on code they've extended.
This recipe ships a different shape: gbrain holds the up-to-date REFERENCE, and gbrain integrations install agent-voice --target <host-repo> COPIES it into the operator's repo. The code now lives in the host repo, on the operator's release cadence, with the operator's edits. Subsequent --refresh invocations diff host-side files against gbrain's reference and propose changes; the operator picks per-file (keep mine / take theirs / merge).
The shipped reference does NOT contain personal names, hardcoded private paths, or upstream-agent codenames. A CI guard (scripts/check-no-pii-in-agent-voice.sh) blocks any drift back; a deterministic import script (scripts/import-from-upstream.sh) refreshes the gbrain reference from an upstream voice-agent source.
Install
# 1. Detect target repo
export TARGET_REPO=$OPENCLAW_WORKSPACE # or your agent repo path
# 2. Install
gbrain integrations install agent-voice --target $TARGET_REPO
# 3. Set env vars in $TARGET_REPO/.env (NOT in gbrain)
echo "OPENAI_API_KEY=sk-..." >> $TARGET_REPO/.env
echo "DEFAULT_PERSONA=venus" >> $TARGET_REPO/.env
# 4. Implement context builder (optional but recommended)
# Replace $TARGET_REPO/services/voice-agent/code/lib/context-builder.example.mjs
# with your operator-specific implementation. See the contract at:
# $TARGET_REPO/services/voice-agent/code/lib/personas/context-builder.contract.md
# 5. Run host-side tests
cd $TARGET_REPO/services/voice-agent && bun install && bun run test
# OR if your repo uses npm: npm install && npm test
# 6. Start the voice server
cd $TARGET_REPO/services/voice-agent && bun run start
# Voice agent listens on http://localhost:8765
Open http://localhost:8765/call and click Connect. The browser asks for mic permission; once granted, it does an SDP exchange via POST /session, the OpenAI Realtime API returns the SDP answer, and audio flows bidirectionally over WebRTC.
For test-mode roundtrip checks, append ?test=1 to the URL — that enables the window._gbrainTest instrumentation namespace + MediaRecorder capture of the response audio.
Update (refresh from gbrain)
# Pull latest gbrain → re-run the install with --refresh
git -C $(which gbrain | xargs -I{} dirname {})/.. pull # or your gbrain update path
gbrain integrations install agent-voice --target $TARGET_REPO --refresh
--refresh reads the .gbrain-source.json manifest written by the original install, re-computes per-file SHA-256 against gbrain's current reference, and classifies each file:
- unchanged-identical — host file matches gbrain reference; skip.
- unchanged-stale — host file matches the recorded SHA but reference moved; offer to update.
- locally-modified — host file diverges from the recorded SHA; show diff, offer three options (keep mine / take theirs / merge).
- source-deleted — gbrain reference removed a file; offer cleanup.
- source-renamed — detected via path-mapping; offer to follow.
A transaction journal at <target>/services/voice-agent/.gbrain-source.refresh.log allows partial-apply recovery if the refresh is interrupted.
Architecture
Browser (call.html)
│
│ WebRTC (mic + remote audio + data channel)
▼
┌─────────────────────┐
│ server.mjs (8765) │
│ ───────────── │
┌──────────┤ GET /call │ POST /session
│ static │ GET /health ├──────────────────▶ api.openai.com/v1/realtime/calls
│ files │ POST /session │ (SDP exchange via FormData)
└──────────┤ POST /tool │
│ POST /voice (Twi.) │
│ WSS /ws (Twi.) │
└──────────┬───────────┘
│ /tool dispatches through tools.mjs allow-list
▼
┌─────────────────────┐
│ tools.mjs router │
│ ───────────── │ denylist: put_page, submit_job, file_upload, ...
│ READ_ONLY_OPS only │ allow-list: 8 read ops; operator extends optional ops via override
└──────────┬───────────┘
│
▼ stdio JSON-RPC
┌─────────────────────┐
│ gbrain serve (MCP) │
└─────────────────────┘
Production checklist
Reference code ships intentionally minimal. Before public deployment:
- Twilio signature validation on
/voice— currently absent; addX-Twilio-Signatureheader validation. - Rate limiting on
/sessionand/tool— currently absent. - CORS allowlist — currently
*; restrict to your deployed origins. - Auth on /tool — voice-side tool calls currently trust the in-process connection; if you expose
/toolpublicly, gate it behind a session token. - HTTPS — required for browser mic access in production. Use ngrok / Caddy / Cloudflare Tunnel.
- Twilio fallback URL —
/fallbackis a TwiML stub; wire to your operator's cell for crash recovery. - PII scrub at context-builder — the shipped
context-builder.example.mjsincludes phone/email regex scrubs, but operators should extend per their brain's PII pattern set.
Tests
cd $TARGET_REPO/services/voice-agent
bun run test # host-side unit tests (5 suites, ~100 cases)
AGENT_VOICE_E2E=1 bun run test:e2e # WebRTC roundtrip (~$0.10/run)
AGENT_VOICE_FULL_E2E=1 bun run test:full-flow # openclaw-driven install + roundtrip (~$1-2/run)
The full-flow E2E is friction-discovery, not a ship-gate. Pre-ship gates on host-side unit tests and the PII guard; flakes in the live OpenAI Realtime path soft-fail with STATUS: skipped_upstream_degraded and log to the friction channel.
What's deferred
- DIY STT+LLM+TTS pipeline (
pipeline.mjs,pipeline-v3.mjsfor Gemini Live) — recipe Option A (WebRTC direct to OpenAI Realtime) ships now; Option B (Deepgram + Claude + Cartesia) is a follow-up wave. - Multilingual Mars — the persona drops the multilingual claim until a multilingual eval lands; restoring it is gated on the eval.
- Live cross-call memory between sessions — the persona is session-scoped today.
- Pre-computed engagement-bid system (the "Bid System" pattern from production deployments) — would belong in
prompt.mjs. - Smart VAD presets (quiet/normal/noisy/very_noisy) — uses Realtime API's default VAD today.
- WebRTC
/sessiondoes not yet ship MediaRecorder fallback for environments where the WebAudio-tee fails.
Each of the deferred items is filed as a TODO in the gbrain repo's TODOS.md.