feat(voice): add call-flow attribution to call records - #41
Merged
Conversation
A call answered by a flow ("receptionist", wavekat-voice docs/48) is
indistinguishable from one the user answered once it crosses the wire:
disposition is `answered` and end_reason is `hangup_local`, because the
flow's own goodbye sends the BYE. Consumers rendering that literally
show "Answered … You hung up" for a call nobody touched.
Adds three optional fields to `VoiceCallRecord`:
- `flow_id` / `flow_name` — which flow took it. The name is the one the
daemon held at answer time and is shipped verbatim, not resolved from
the flow on read, so a later rename or delete can't rewrite history.
- `flow_outcome` — the run's own terminal, which consumers prefer over
`end_reason` when present. `None` for runs with no terminal step
(caller hung up mid-flow), where `end_reason` is already honest.
`VoiceCallFlowOutcome` is declared here rather than re-exported from
`wavekat-flow` — keeping this crate's dep set small is worth the small
duplication; the two lists are pinned together by a test.
The platform side (wavekat-platform#176) already accepts these. This
release unblocks the daemon mapping in wavekat-voice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K9XwWFb2NB6pHaWzXBj3iH
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.
A call answered by a flow ("receptionist", wavekat-voice docs/48) is indistinguishable from one the user answered once it crosses the wire:
disposition: answered,end_reason: hangup_local— because the flow's own goodbye is what sends the BYE. A consumer rendering that literally shows "Answered … You hung up" for a call nobody touched.Step 2 of a three-repo train, and the one that has to be released, not just merged — the consumer can't use the fields from git.
wavekat-voice— maps the fields, bumps its dep to the version this PR releases.What's added
Three optional fields on
VoiceCallRecord, allskip_serializing_if = "Option::is_none"per the additive-only policy:flow_id/flow_name— which flow answered. The name is the one the daemon held at answer time, shipped verbatim rather than resolved from the flow on read, so a later rename or delete can't rewrite what history says happened.flow_outcome— the run's own terminal, which consumers prefer overend_reasonwhen present.Nonefor runs with no terminal step (the caller hung up mid-flow), whereend_reasonis already the honest story.VoiceCallFlowOutcomeis declared here rather than re-exported fromwavekat-flow. Keeping this crate's dep set small is worth the small duplication — the crate deliberately has no flow-engine dependency — and a test pins the wire strings so the two lists can't drift.Compatibility
Additive and non-breaking. Old consumers deserializing a platform response that now includes these keys are unaffected — there's no
deny_unknown_fieldsanywhere in the crate, so serde ignores them. A record built without the fields serializes exactly as before; a test asserts they stay off the wire entirely rather than appearing as nulls.Tests
79 pass. Three new: the six wire strings are pinned (the daemon's
flow_outcome_to_str,wavekat_flow::trace::FlowOutcome's serde, and the platform's Zod enum all have to agree — a rename 400s every flow-answered batch), a full round-trip, and the human-answered omission case.cargo fmt+clippyclean.Docs
CLAUDE.mdgains the release-train ordering next to the existing additive-only section — specifically that landing a field here without cutting a release leaves consumers unable to build, and that they must not paper over it with a[patch.crates-io]in a commit.🤖 Generated with Claude Code
https://claude.ai/code/session_01K9XwWFb2NB6pHaWzXBj3iH