feat: add selectable phone call voice stacks - #83
Merged
Conversation
dimavrem22
marked this pull request as draft
July 29, 2026 23:45
dimavrem22
force-pushed
the
plan/phone-call-voice-stack
branch
from
July 30, 2026 09:37
03d29d1 to
6358226
Compare
dimavrem22
force-pushed
the
plan/phone-call-voice-stack
branch
from
July 31, 2026 19:22
90fd2c8 to
153ae6a
Compare
dimavrem22
marked this pull request as ready for review
July 31, 2026 21:30
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Executive Summary
Adds explicit phone-call voice-stack selection and reliable Inkbox Voice AI post-call follow-up to the Hermes Inkbox plugin.
Description
The setup wizard persists one canonical voice stack, validates Realtime credentials before saving them, supports contact-scoped and YOLO Voice AI authority, and keeps administrative credentials transient. Outbound tools use hosted calls for Inkbox Voice AI and the client media bridge for Realtime or TTS/STT. Voice AI calls omit a per-call authority override so the API applies the saved identity default. Existing installations without the new setting retain legacy Realtime auto-detection.
The gateway registers call lifecycle, iMessage, and agent-to-agent events in separate identity-owned subscription rows at the same canonical receiver URL. Hosted completion events fetch the authoritative transcript, include open actions and the current remote phone number, enqueue a suppressed-text Hermes reconciliation turn, and persist bounded receipts for deduplication and crash recovery.
For a required post-call SMS, the plugin binds trusted call context to the real Hermes session.
pre_tool_calldurably records a sanitized pending attempt beforeinkbox_send_smscan perform an external write;post_tool_callreplaces that exact attempt by hashedtool_call_idwith its final sanitized result. Settlement requires exactly one successful tool call to the authoritative remote number. A missing initial call or deterministic pre-send argument/content error gets exactly one mandatory correction turn. Wrong recipients, duplicate calls, timeouts, rate limits, 5xx responses, carrier/consent failures, unknown results, pending crash-window writes, and any unsuccessful correction are terminal and never automatically replayed.Open post-call action items remain authoritative. Transcript fallback uses clause-aware matching and requires explicit post-call timing plus an explicit text/SMS action. It rejects generic live-call requests, noun/past references, email sends, and negated instructions. This avoids forcing a duplicate or forbidden SMS when Voice AI may already have handled the request during the call.
If trusted session lookup or context persistence fails, the call/attempt is terminal and
pre_tool_callblocks the affected target before an external write. Failure markers contain only target digests and do not block unrelated recipients. Durable context files are mode 0600 inside a mode 0700 directory, and temporary JSON files are created mode 0600 before content is written. Settlement state excludes SMS bodies, raw tool results, transcripts, contact memories, and credentials. Terminal cleanup removes per-call observations and queued/active session context.Reason
Inferring voice behavior from credential presence allowed setup, inbound routing, outbound tools, and post-call work to disagree. Separately, a successful model turn could return
[SILENT]without executing a promised post-call SMS, while blind restart replay could duplicate an external write after a crash. The plugin needs one explicit runtime choice and host-native side-effect settlement so promised work is either proven once or fails closed.Decisions
INKBOX_VOICE_STACKselectsinkbox_voice_ai,openai_realtime, orinkbox_tts_sttfor all call paths.hosted_agentremains the API transport value.pre_tool_callandpost_tool_callhooks, keyed to the realsession_idand hashedtool_call_id, determine SMS settlement.[SILENT]is forbidden on that mandatory correction; an unsuccessful correction is terminal and may settle silently without a third attempt.Testing
Local verification
ruff check .,python -m compileall,uv lock --check, andgit diff --check: passed.Live-test inventory
tests/live/test_voice.py::test_outbound_call_inkbox_voice_ai_and_completion: proves a current hosted call useshosted_agent, exactly matches the saved authority, persists a reason, disables voicemail detection, records both caller intent and an open SMS action before test-owned hangup, receivescall.ended, settles against the authoritative caller, creates exactly one fresh sender-side SMS row, and remains duplicate-free through the full grace period. It excludes baseline IDs, requires server creation watermarks, enforces driver/AUT timestamp skew, and observes the exact pair for a duplicate grace before accepting it. The exact five-word everyday-word marker survives TTS/PSTN/STT while avoiding conventional phonetic-alphabet substitutions and a known six-word action-summary truncation; partial markers still fail.tests/live/test_voice.py::test_inbound_call_inkbox_tts_stt: refreshes the exact placed call through the API and verifies persistedvoicemail_detection=disabled. This closes the gap where the request argument was asserted without proving what the server stored.tests/live/test_voice.py::test_outbound_call_realtime: correlates the fresh AUT-owned outbound call and verifies persisted disabled voicemail detection. This closes false matches against older or opposite-side call records.tests/live/test_voice.py::test_outbound_call_realtime_direct_contact_lookup: applies the same exact-call persisted voicemail proof to Hermes' direct-contact Realtime flow. This preserves the host-specific tool path while enforcing the fleet call policy.tests/live/test_cross_channel.py::test_email_request_gets_call: snapshots both identities, requires the fresh driver inbound and AUT outbound legs, and verifies voicemail detection on the authoritative AUT leg. This closes the false positive where a driver-side call alone could pass even if the plugin placed no compliant outbound call.tests/live/test_cross_channel.py::test_sms_request_gets_call: adds the same paired-leg and persisted voicemail verification for SMS-triggered calls. This closes stale-call and wrong-owner matches.tests/live/test_sms.py::test_sms_retry_after_carrier_delivery_failure: increases bounded conversation-history inspection so the test can still locate its current correlated record on busy shared live identities. This closes a history-window false negative without relaxing recipient, correlation, retry, or delivery assertions.tests/live/test_email_intelligence.py::test_reports_sender_details: requires the requesting agent to report the sender's complete email address and phone number, preserving the existing API-side assertions while removing ambiguity in the natural-language task.curlattempts, and prints cancellation diagnostics when a matrix child prevents the aggregate gate from settling.Exact-head CI
30700714915: passed on91c20335386e86e1c665a76cc1b5c05803e493b2(Python 3.11, Python 3.12, and current-host contract).30700714990: passed on the same exact head, including mock and real channels, all four A2A variants, inbound TTS/STT, hosted Voice AI, outbound Realtime, Hermes' direct-contact Realtime path, external events, and the aggregate gate.Related PRs