Skip to content

feat(voice): add call-flow attribution to call records - #41

Merged
wavekat-eason merged 1 commit into
mainfrom
feat/voice-call-flow-attribution
Jul 26, 2026
Merged

feat(voice): add call-flow attribution to call records#41
wavekat-eason merged 1 commit into
mainfrom
feat/voice-call-flow-attribution

Conversation

@wavekat-eason

Copy link
Copy Markdown
Contributor

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.

  • 1. wavekat-platform#176 — migration + routes accept these fields. Already open, safe to deploy alone.
  • 3. wavekat-voice — maps the fields, bumps its dep to the version this PR releases.

What's added

Three optional fields on VoiceCallRecord, all skip_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 over end_reason when present. None for runs with no terminal step (the caller hung up mid-flow), where end_reason is already the honest story.

VoiceCallFlowOutcome is declared here rather than re-exported from wavekat-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_fields anywhere 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 + clippy clean.

Docs

CLAUDE.md gains 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

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
@wavekat-eason
wavekat-eason merged commit c55d6cb into main Jul 26, 2026
1 check passed
@wavekat-eason
wavekat-eason deleted the feat/voice-call-flow-attribution branch July 26, 2026 00:24
@github-actions github-actions Bot mentioned this pull request Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant