voice context reset + boot-time sub-agent discovery#7
Open
zdql wants to merge 1 commit into
Open
Conversation
Two related additions, sharing the control socket and progress store already plumbed by #4. == voice context reset == Long realtime sessions accumulate enough conversation history that the voice model starts drifting and (occasionally) the server rejects further turns. Three new ways to wipe the conversation back to a safe baseline without disturbing audio or the websocket itself: - voice tool `reset_voice_context(reason?)` — the realtime model can self-reset when it senses overload or the user asks for a fresh start. Acked with a function_call_output so the model keeps speaking naturally afterward. - control socket `Request::Reset { reason }` exposed as `gamechat reset [--reason TEXT]` from a second terminal. - auto-reset once tracked items exceed `settings.auto_reset_after_items` (clamped to [20, 2000]; omit or set to 0 to disable). Disabled by default. Mechanics (see `src/voice_loop/reset.rs`): 1. `response.cancel` if a response is in flight, so the server stops emitting audio for a turn whose item id we're about to delete. 2. `conversation.item.delete` for every id we've observed via `conversation.item.created` (tracked in `ConversationItemTracker`, capped at 4000 entries with FIFO eviction). 3. Re-send the original `session.update` to re-baseline instructions, voice, tools, and turn detection. The local playback buffer and mic input are deliberately left alone, so audio already handed to cpal finishes playing while the next turn comes back clean. Any queued `response.create` events that pre-dated the reset are dropped — the conversation they would have continued no longer exists. == boot-time sub-agent discovery == When a new `gamechat --realtime` starts, it now scans `runtime_dir()` for sockets owned by other live gamechats and asks each peer for its active slug list (with a 750ms per-peer timeout). Discovered slugs are stamped into the local `ProgressStore` under `peer_<pid>_<slug>` with a `discovered:<pid>` provider tag so they are visible in `gamechat inspect` but unambiguously not locally owned. Standalone CLI: `gamechat discover` walks the runtime dir and prints every peer's slugs in one table — useful when triaging "who's running the background work I see in claude?" across multiple terminals. Discovery is enabled by default; flip `settings.discover_existing_subagents: false` to opt out. == other == - `BASE_INSTRUCTIONS` updated to tell the voice model when to call `reset_voice_context` and not to announce it. - duplicate `spawn_server` call in `run_realtime_voice` removed (was binding the control socket twice, second bind always failed). - 30 new tests covering the reset event sequence, item tracker bounding/dedup, auto-reset threshold clamping, discovery seeding, peer-query timeout, the new `reset_voice_context` tool definition, and the control-socket Reset handler (both happy path and voice-loop-channel-closed). Total: 71 tests, all passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zdql
commented
May 29, 2026
| return Err(format!( | ||
| "reset takes no positional arguments, got: {}. Use --reason <text> if you want to record one.", | ||
| positional.join(" ") | ||
| )); |
Owner
Author
There was a problem hiding this comment.
Doesn't make any sense, gamechat can't call itself?
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.
Summary
Two related additions, sharing the control socket and progress store already plumbed by #4.
1. Voice context reset
Long realtime sessions accumulate enough conversation history that the voice model starts drifting and (occasionally) the server rejects further turns. Three new ways to wipe the conversation back to a safe baseline without disturbing audio or the websocket itself:
reset_voice_context(reason?)— the realtime model can self-reset when it senses overload or the user asks for a fresh start. Acked viafunction_call_outputso the model keeps speaking naturally afterward.Request::Resetexposed asgamechat reset [--reason TEXT]from a second terminal.settings.auto_reset_after_items(clamped to[20, 2000]; omit or set to 0 to disable; disabled by default).Mechanics (
src/voice_loop/reset.rs):response.cancelif a response is in flight, so the server stops emitting audio for a turn whose item id we're about to delete.conversation.item.deletefor every id observed viaconversation.item.created(tracked inConversationItemTracker, FIFO-capped at 4000 entries).session.updateto re-baseline instructions, voice, tools, and turn detection.The local playback buffer and mic input are deliberately left alone, so audio already handed to cpal finishes playing while the next turn comes back clean. Any queued
response.createevents that pre-dated the reset are dropped — the conversation they would have continued no longer exists.2. Boot-time sub-agent discovery
When a new
gamechat --realtimestarts, it now scansruntime_dir()for sockets owned by other live gamechats and asks each peer for its active slug list (750ms per-peer timeout). Discovered slugs are stamped into the localProgressStoreunderpeer_<pid>_<slug>with adiscovered:<pid>provider tag so they are visible ingamechat inspectbut unambiguously not locally owned.Standalone CLI:
gamechat discoverwalks the runtime dir and prints every peer's slugs in one table — useful when triaging "who's running the background work I see in claude?" across multiple terminals.Enabled by default; flip
settings.discover_existing_subagents: falseto opt out.Drive-by
BASE_INSTRUCTIONSnow tells the voice model when to callreset_voice_contextand not to announce it.spawn_servercall inrun_realtime_voice(the second bind was always failing on EADDRINUSE).Risks
conversation.item.deleterace. The server may have already produced audio for items we're deleting. The local playback buffer is intentionally untouched, so the cancelled turn's audio finishes; the next turn starts from a clean conversation. If OpenAI later changesconversation.item.deletesemantics for items currently being voiced, behaviour could regress —reset_trigger_string_repr_is_stableand the e2e reset event order test will surface that on the next API smoke.*.sockunder$XDG_RUNTIME_DIR/gamechat-$USER/can answer theListprobe. Same trust boundary as the existinginspect/tailclients — single-user runtime dir.seed_discovered_subagentsusesBox::leakto satisfyregister_job's&'static strrequirement. Bounded by the number of distinct peer pids ever seen during the binary's lifetime — at most a handful.Usage
Optional
~/.config/gamechat/settings.json:{ \"auto_reset_after_items\": 400, \"discover_existing_subagents\": true }The voice model can also self-reset: it will call
reset_voice_context(withreason=\"context_overload\"orreason=\"user_requested\") per the updatedBASE_INSTRUCTIONS.Test plan
gamechat --realtime, talk for a while, rungamechat resetfrom another terminal, verify voice keeps speaking but next turn doesn't reference earlier contextgamechat --realtimeprocesses; rungamechat discoverand verify both peer's slugs appearauto_reset_after_items: 20and confirm the auto-reset log line fires once the threshold is crossed🤖 Generated with Claude Code