feat(s2s): route conversations to /v1/voice/conversation (extraction parity) - #40
Conversation
…parity) The s2s bridge persisted each voice conversation as a single growing "Voice conversation [...]" blob to POST /api/t/memory_store on every disconnect — bypassing the core memory extraction pipeline and re-writing a cumulative blob per disconnect (weeks of duplicates on the primary). Switch to the core cumulative-turns route added in GENesis-AGI PR-B: sync_conversation filters the bridge's cached context to user/assistant turns and POSTs the cumulative list to POST /v1/voice/conversation. The core route (sync_cumulative) appends only turns beyond the transcript's line count, so replays / double-fires / cumulative re-sends land exactly once and each conversation is mined by the extraction job like every other channel. Session scoping: the bridge context grows monotonically for the whole process lifetime, so a single id would produce one transcript spanning weeks. The session id ROTATES when a persist arrives more than SESSION_REUSE_TIMEOUT_SECONDS after the previous one (the same window the SessionManager uses to decide a conversation ended) and turns before the gap are based out — so transcripts map ~1:1 to human conversations. A defensive guard also rotates if the cumulative list ever shrinks below the session base (never observed; the context is monotonic in prod). 4xx responses are now treated as permanent (saved to the local fallback file immediately, not retried); 5xx / network errors keep the 3-attempt retry. Bearer auth, the log-before-POST durability line, and the fallback-on-failure path are unchanged. No new config — GENESIS_TOKEN already authenticates the same _check_voice_token the edge passes for /v1/voice/tool_call today. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e9ad15461
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| gap = self._session_id is not None and now - self._last_persist_ts > self._session_split_seconds | ||
| if self._session_id is None or gap: | ||
| # New conversation: turns accumulated so far belong to prior epochs, | ||
| # so base them out (0 on the very first persist). | ||
| self._start_session(base=self._last_turn_count if gap else 0) |
There was a problem hiding this comment.
Scope transcript sessions per satellite
When two different client_id/satellite_id values disconnect within SESSION_REUSE_TIMEOUT, this reuses the single _session_id, _last_turn_count, and _session_base from the previous satellite because the state is global to one GenesisToolService. main.py passes each disconnect through the same service with client_id as satellite_id, so the second device can post under the first device's cumulative session; with line-count dedupe on that session, a shorter second transcript may append nothing or get merged into the wrong conversation. Keep the session/base/last-persist state keyed by satellite_id.
Useful? React with 👍 / 👎.
Summary
Switches the s2s bridge from the legacy one-blob
POST /api/t/memory_storelanding to the core cumulative-turns routePOST /v1/voice/conversation(added on the GENesis-AGI side, W0.5), so voice conversations feed the memory extraction pipeline like every other channel instead of landing as an ever-growing raw blob.Background
On every client disconnect the bridge POSTed its full cached context as one
"Voice conversation [...]"blob. The context grows monotonically for the whole process lifetime, and the disconnect can fire 2–3× per teardown, so the core accumulated ~120 duplicated, ever-growing episodic blobs (each embedded in the vector store) that were never mined for facts.Change
GenesisToolService.store_conversation→sync_conversation: filters the cached context to user/assistant turns and POSTs the cumulative list to/v1/voice/conversation. The core route appends only turns beyond the transcript's line count, so replays / double-fires / cumulative re-sends land exactly once.SESSION_REUSE_TIMEOUT_SECONDSafter the previous one (the same windowSessionManageruses to decide a conversation ended), and pre-gap turns are based out — so transcripts map ~1:1 to human conversations. A defensive guard also rotates if the cumulative list ever shrinks below the session base (never observed; the context is monotonic in prod).GENESIS_TOKEN, the same token the bridge already uses for/v1/voice/tool_call), the log-before-POST durability line, and the fallback-on-failure path are unchanged. No new config.Testing
sync_cumulative+run_extraction_cycle— a double-fire lands 0, a gap opens a new transcript, both are mined and watermarked.Deploy ordering
Land + deploy the GENesis-AGI side (the W0.5 route plus its
sync_cumulativeconcurrency fix) on the core first; verify the core serves401(not404/405) onPOST /v1/voice/conversation, then redeploy the bridge and restart the service. Until the bridge redeploys, the core's daily voice-hygiene sweep keeps clearing any interim blobs.🤖 Generated with Claude Code