Skip to content

v0.95.0

Choose a tag to compare

@sboily sboily released this 05 Oct 12:25
· 1657 commits to main since this release

What's Changed

  • docs(changelog): promote 0.95.0
  • docs(changelog): consolidate [Unreleased] into what changes against 0.94.0
  • refactor(realtime): per review, the reconnect step drops its redundant guard and names who takes it, ElevenLabs holds its reject-then-abandon pair once, the reconnect tests renegotiate the session, and the CHANGELOG says which calls are abandoned (RMK-508)
  • test(conference): a hang-up call ending its session, detaching its room or unplugging the provider, on the realtime channel and the conference, a sibling call interrupted; CHANGELOG (RMK-515)
  • fix(conference): a tool call whose own handler detaches its room or unplugs the realtime provider runs on and reports its own outcome, as on the realtime channel: one rule in the executor, interrupt_for_ending, spares the current task on both hosts (RMK-515)
  • test(realtime): a second connect under a live session id, on the five providers with tools, through each one's own wire; CHANGELOG (RMK-508)
  • fix(realtime): Gemini reads its book before it requires a connection: a result for a session it no longer holds is dropped with the log every provider writes, instead of raising (RMK-508)
  • fix(realtime): every provider's connect() abandons the calls an earlier connection under the same session id issued, through one step of the base: Gemini, GPT-Live and ElevenLabs had none, OpenAI Realtime and Deepgram each a hand-written guard (RMK-508)
  • fix(realtime): an agent reasoning backend's close closes the agent it owns, releasing its provider and cutting its turns, as the channel releases its own (RMK-511)
  • fix(tools): per review, a channel's close cuts every call its turn runs: an external handler's pending decision, a round whose calls had not started, the other calls of a hang-up call's round; a close from the turn's own hook does not wait for it, and a second close does not wait again (RMK-511)
  • test(tools): a close while a tool runs, on the text door in both modes, the realtime door, a delegated turn, and a handler closing its own channel; CHANGELOG (RMK-511)
  • fix(tools): AIChannel.close() ends its running turns first: the calls their rounds execute are cancelled and reported cancelled, no further round is asked, and the close waits for them, at most 5 s, as a realtime channel's close does (RMK-511)
  • refactor(tools): a text turn knows when its channel closes: the loop context holds the round's call tasks, a closing flag and an ended event, and a call cut by the close ends cancelled, reported once (RMK-511)
  • fix(realtime): an audio-video channel's auto-greeting is the model's own assistant turn again: send_greeting dispatches on the realtime channel, not on the session start's channel type, which RMK-501 made REALTIME_AUDIO_VIDEO (RMK-501)
  • fix(tools): per review, a handler override that raises at the call itself is caught by the guard too, and a handler's report counts as made only where the observers hear it (RMK-507)
  • fix(acp): a call whose end the external handler raised before reporting is reported by the channel for every outcome, and one it raised after reporting is not reported twice (RMK-507)
  • refactor(tools): one guard for a handler's report: handler_reported watches whether the report reached ON_TOOL_CALL, marked by the kit's report callback, and the external door relies on it alone (RMK-507)
  • fix(streaming): per review, every start of a round gets its end even when none rode it, the segment writer knows a start row by its marker rather than its id, and the round's ids are freed where the round begins (RMK-506)
  • fix(tools): per review, an external handler that raises before reporting a call leaves its report to the channel, on the external door and on the cut path, so the call is reported once and never zero times (RMK-506)
  • fix(streaming): a turn cut while two calls under one id are open closes each of them with its own outcome: each call's end rides its start marker as soon as it ended, and the response reader holds every open start as a call of its own (RMK-506)
  • fix(tools): on the external handler's door, two calls under one id in a round are two calls: a pending one is refused without asking the handler, one the provider ran is reported as its own; a handler that raises reporting no longer fails the turn (RMK-506)
  • fix(tools): each call of a text turn is held as a call of its own, its id freed at the end of its round: a call under an id an earlier round used is reported, and reported cancelled when the turn cuts it (RMK-506)
  • fix(realtime): per review, a call named twice in one cancellation is abandoned once instead of raising; the book is checked first on every provider, a call from a connection that is gone is dropped, and a second connect under a live session abandons the old connection's calls (RMK-502)
  • fix(realtime): one book of open tool calls on RealtimeVoiceProvider, which Gemini, OpenAI Realtime, xAI, GPT-Live, Deepgram and ElevenLabs answer through: a result for an id the provider abandoned or never issued is dropped with a log on every one of them (RMK-502)
  • fix(tools): per review, a call run past an ACP refusal is reported by the channel when the handler's report raises; the backend's loop sets its model's id itself; an override without a keyword is warned about once; the external door's handler dispatch is one step (RMK-498)
  • fix(streaming): per review, a turn cancelled from outside while a tool runs records cancelled on its kept text too, and a cancelled read keeps the turns of the streams read before it; the reasoning backend's failure and a cancelled read's record are named steps (RMK-497)
  • fix(tools): an external handler override written before detail or refused_but_ran is never handed them, so no report is lost to a TypeError: the refusal is reported without its detail, and a call the agent ran past its refusal is reported by the channel with its marker (RMK-498)
  • fix(acp): a call RoomKit refused that the agent ran anyway and closed completed is reported served, marked refused_but_ran on its report, its END row and its tool_call event (RMK-498)
  • fix(ai): two calls under one id in a text round are two calls: the second is refused as a realtime session refuses it and reported as its own, the first keeps its report and its END row its own arguments, which ride each call's start marker (RMK-498)
  • fix(realtime): a reasoning backend's call is reported under the id its model gave it on every path: through the gate, refused by it, refused by the backend's loop, as one its provider served; the id rides model_call_id(), the reporters keep their signatures (RMK-498)
  • fix(streaming): a call the turn cuts while it runs is closed with the arguments it ran with, as its report is: they ride its start marker (ran_with) (RMK-498)
  • fix(tools): a refusal that came from a failure carries what failed to the observers on the external doors too: ToolDecision.detail, filled by the policy handler, reaches on_tool_refused(detail=) on the AI and ACP doors (RMK-498)
  • fix(delegation): a delegated turn whose stream a shared transport rendered is logged once, by its task, as one on the trace path; a room turn streamed to a live target keeps its line (RMK-497)
  • fix(realtime): a reasoning backend's turn that fails on an error fires ON_ERROR once, category reasoning, beside its spoken fallback; a turn its cap cut fires none (RMK-497)
  • fix(delegation): kit.delegate() refuses to start on a closing kit, by the check a strategy's background run asks, now in core (RMK-497)
  • fix(delegation): a delegated reply that did not respond and carries an error fails the task with that error on the trace path, as on the room path (RMK-497)
  • fix(regenerate): the error a regeneration reports is chosen as an inbound turn's, the buffered failure first, through the cascade (RMK-497)
  • fix(delegation): a delegated turn whose stream fails on the trace path fires ON_ERROR by the room reader's hook, in the turn's scope (depth, correlation, thread) (RMK-497)
  • fix(streaming): a turn cancelled from outside records cancelled on its row and under its caller's turns, as one its reader stopped does (RMK-497)
  • refactor(tools): per review, one refusal of a door's host tools (refuse_host_tools), warn_unoffered takes the offered tools in one call per door, a tool under an orchestration name is logged as such (RMK-499)
  • fix(realtime): per review, two pipeline agents may give one tool name their own definitions: the agent-tool entry carries every agent's, so the agent a handoff made active declares its own and its handler serves it (RMK-499)
  • fix(providers): per review, a turn that states reasoning off sends the least effort the model takes as its catalogue declares it (none from GPT-5.1, minimal on GPT-5 and Muse, low on o3, o4-mini and GPT OSS), where GPT-5 and o3 answered 400 to none; OpenRouter and Azure let the server decide vision; one stream end and one first-token clock (RMK-500)
  • fix(realtime): an audio-video channel's session start, tool hooks and provider errors name its own channel type, not REALTIME_VOICE (RMK-501)
  • fix(delivery): per review, a conference's realtime model is waited on, announced and pinned as a realtime voice channel's: RealtimeModelHost carries wait_idle, the injection fires ON_REALTIME_TEXT_INJECTED through one helper, a pinned delivery stays an injection when the model is unplugged, and an agent attached as a transport is never the room's transport (RMK-501)
  • fix(providers): a turn that switches reasoning off reaches OpenAI's reasoning models and Meta; behind a base_url the server decides vision; time to first token, a null message, a function-less call entry and the answering model read alike through generate() and the stream (RMK-500)
  • fix(human-input): one rule for the person's tools on every door: never hidden by Tool Search, a definition given twice or under a name the channel serves refused, and the two wiring warnings said by the shared component (RMK-499)
  • fix(realtime): a tool a session is given under a name orchestration serves is not declared unless it is one of that entry's own declarations, and a call to the name is checked against its server's schema, as on a text turn (RMK-499)
  • feat(rooms): attach_channel takes the channel's own category when none is given, so an agent attached without one takes part as an intelligence channel (RMK-501)
  • fix(delivery): a hand-back reaches the model of every channel that hosts one, one predicate read by the three switches, a conference injecting into its room session and an audio-video channel as a realtime voice one; deliver() to an intelligence channel whose transport is itself one is refused instead of recursing (RMK-501)
  • fix(providers): per review, Mistral decides its own endpoint by the shared helper too, which reads a scheme's default port as no port; the OpenAI vision provider takes a cut think block out of its description; Ollama keeps a lost call name empty; PolarGrid's response reading is its own step (RMK-484)
  • fix(anthropic): a system message in the history goes as a user turn, as on Gemini, instead of a message role the Messages API does not take (RMK-484)
  • fix(providers): a reasoning block with no text goes back as nothing in an answer without calls on an inline-reasoning wire, as in a round with calls, instead of an empty think block (RMK-484)
  • fix(providers): a streamed call's arguments sent as an object read as the text they spell, ToolCallSlots.fold taking the object itself, on OpenAI's wire and PolarGrid as on Mistral (RMK-484)
  • fix(providers): one reader for a chat completion's calls on OpenAI's wire and PolarGrid, so a call whose name was lost reaches the loop nameless as on the stream, and a response with no choice reports its usage (RMK-484)
  • fix(orchestration): per review, a skill registry even empty is refused on a realtime pipeline's agent as on a reasoning backend's, one rule without a mode flag, since a skill added later opened its gated tools ungated; each refusal says what serves the cause instead; the entry-barrier test waits for its one result (RMK-482)
  • fix(ai): a provider that holds no tool unseen receives each tool result as its text, declared_for dropping the references only a deferring provider reads, so Anthropic behind a gateway gets the text as OpenAI does (RMK-484)
  • fix(providers): a block the output cap cut is reasoning through generate() as on the stream, extract_think_tags reading a whole response with the stream's parser, on OpenAI's wire and PolarGrid (RMK-484)
  • fix(human-input): per review, a channel given no human-input handler holds an empty component, so the 23 guards go; the realtime close settles what the ended sessions left in one step; a provider that calls no tool is warned about human-input tools too; tests for a host-declared name, a detach, an ended session, the policy and a reconfigure (RMK-481)
  • fix(providers): a vendor's official URL written out as base_url is its own endpoint, decided by one helper on OpenAI text, Anthropic, OpenAI Realtime, GPT-Live and DeepSeek, which read it as a proxy and dropped the vendor's rules (RMK-484)
  • fix(realtime): a reasoning backend's agent is refused an empty skill registry again, which its own loop would serve once filled outside the voice channel's gate; a pipeline agent's, whose loop never runs in a session, is still installed (RMK-482)
  • test(realtime): the entry-refusal barrier test waits for the served call's result as well as its report before closing, which a loaded run reached first (RMK-482)
  • fix(orchestration): a realtime pipeline refuses at its install an agent carrying what a realtime session never serves for it (skills, a human-input handler, planning, a sandbox, an external tool handler), each cause named, by the rule a reasoning backend's agent is refused by (RMK-482)
  • fix(tools): the arguments BEFORE_TOOL_USE leaves are validated again by one helper on every gate, and arguments a hook broke in place read as rewritten on the AI channel too (RMK-482)
  • fix(orchestration): per review, how a supervisor's task-formulation pass ended reaches its caller under turns only, never copied flat beside them, and its record stays the field's own type (RMK-479)
  • fix(tasks): per review, a delegated turn whose responder raised or returned its error fires ON_ERROR once on either path, by the reporter a room turn uses, as does one that answered then failed with a transport shared (RMK-479)
  • fix(streaming): per review, a turn its transport stopped while the model was still thinking ends cancelled, and so does an ACP agent's that finished its prompt meanwhile, whose task handed on the partial text as an answer (RMK-479)
  • docs(changelog): human_input_handler= on realtime voice channels and conferences, the AI channel serving its human-input tools itself; example realtime_human_input (RMK-481)
  • feat(realtime): a HumanInputToolHandler given as a realtime channel's tool_handler stays a plain handler, and a warning points to human_input_handler= (RMK-481)
  • feat(conference): ConferenceRealtimeConfig takes human_input_handler=, its tools declared and served before tool_handler under the handler's own timeout, each request announced through ON_USER_INPUT_REQUIRED and settled by a detach, the unplug or the close (RMK-481)
  • feat(realtime): RealtimeVoiceChannel takes human_input_handler=, whose tools every session declares and every door serves before the host's handler, under the handler's own timeout, each request announced through ON_USER_INPUT_REQUIRED and settled at close (RMK-481)
  • fix(ai): the tools that ask a person are served by the channel through one per-channel component, before the host's handler, so replacing tool_handler no longer drops them; the request names the door's channel type (RMK-481)
  • docs(changelog): an activate_skill with no hint to reveal is refused (RMK-480)
  • fix(skills): per review, activate_skill on a name that is no skill is refused when its answer carries no hint to reveal, on the AI channel and a realtime session; with a hint it stays served (RMK-480)
  • fix(acp): per review, an approved call the agent never announced nor closed before its turn ended is closed and reported once, cancelled, as every call RoomKit decided (RMK-480)
  • refactor(realtime): per review, a backend's calls reported outside the gate are built by one helper, the ReasoningRequest docstring follows its field order, and the unreachable report's docstring and changelog say the framework event is then the call's one report (RMK-480)
  • fix(acp): per review, a permission decision is its own step (decide, record, answer), a raising handler's call reads as the AI door words it, and an approval ACP cannot apply is refused with the channel's reason; the turn's external tools have their factory (RMK-480)
  • fix(skills): per review, run_skill_script on a skill the registry does not offer is refused on every door, as read_skill_reference is (RMK-480)
  • fix(realtime): per review, a backend's provider-served call claims its one report before report_call relays it and the relay runs to its end, so a delegation ending meanwhile no longer reports the served call a second time, failed (RMK-480)
  • fix(ai): a response-schema turn cut short fails truncated inside the tool loop, so a reasoning backend's turn fails its span as a room turn does (RMK-479)
  • fix(streaming): a response its transport stopped reading once it began records its end cancelled, so a delegated AIChannel worker's task fails as an ACP worker's does and a room turn's caller reads it under turns (RMK-479)
  • test(orchestration): per review, close() ends a supervised background run between the supervisor's framing and its review, as it ends the other doors' runs (RMK-478)
  • fix(orchestration): per review, a per-worker background delegation is a task of the kit's task runner again (task_id answered, task_runner.cancel and a custom TaskRunner reach it), followed by the background run that bounds it, frees the worker and alone hands its outcome back (RMK-478)
  • fix(orchestration): per review, a supervisor without a model (configuration only, as a voice supervisor often is) lets its sequential team run unsupervised in the background, where every frame and review it was asked for failed and the chain never reached its second worker (RMK-478)
  • test(tasks): the child trace tests' mock kit has no ON_ERROR context, which a failed delegated turn now asks for (RMK-479)
  • fix(tasks): a delegated turn that failed on the trace path fires ON_ERROR once in its child room, as the room path's reader fires it (RMK-479)
  • fix(core): regenerate_response records its buffered replies through the inbound path's reader, so a reply whose end rides its last message is under turns and a turns key a hook wrote never reaches the caller (RMK-479)
  • fix(orchestration): a supervisor's task-formulation pass carries its turn's record to its caller on every branch, so an error, a cancellation or an empty completion is read under turns as on a room turn (RMK-479)
  • refactor(orchestration): per review, Supervisor.install registers its agents through one helper, which keeps install under its size signal (RMK-478)
  • docs(orchestration): per review, the background run's docstring names run_worker as the poster of worker entries and what on_done reports, the status bus lists the review and worker actions, and the per-room install test asserts its one delegation again (RMK-478)
  • fix(orchestration): per review, a Loop review whose task failed posts its terminal entry failed, where it read info like a request for a revision (RMK-478)
  • fix(orchestration): per review, a worker delegation is bounded through answer_within, so a TimeoutError the delegation raised itself fails it as raised instead of reading as the bound expiring (RMK-478)
  • fix(tasks): per review, an inline delegation whose worker's turn ended before its caller was cancelled ends completed with its output, then the cancellation goes on; the isolated turn and its outcome are their own helpers (RMK-478)
  • test(orchestration): the supervisor tests' mock kits say they are open, which a background run now checks before it starts (RMK-478)
  • fix(orchestration): per review, a background run asked for once close() began does not start, so none outlives the kit (RMK-478)
  • fix(orchestration): per review, a supervised background run whose chain stopped on a step the supervisor left unvalidated is told the work could not be completed and posts failed, as the supervisor reads it within the turn (RMK-478)
  • fix(skills): read_skill_reference on a skill the registry does not offer is refused on the AI channel and a realtime session, the model reading the same error; activate_skill's hint answer stays served (RMK-480)
  • fix(tools): a call's report carries the arguments it ran with, or that the gate had when it stopped it, on the AI channel as on the realtime doors: a refused call and a call its turn cut no longer report the model's own (RMK-480)
  • fix(acp): RoomKit's decision on a call's permission stands however the agent ends the call: a refusal is reported refused with the handler's reason and a raising handler failed, when the agent leaves the call open or never announced it (RMK-480)
  • fix(tools): a provider-served call's result is bounded as every outcome the model reads, for the next round and its TOOL_CALL_END row, its observers still hearing it whole (RMK-480)
  • feat(realtime): ReasoningRequest.report_call reports a call the backend's own provider served outside the gate to the channel's ON_TOOL_CALL hooks, served or failed, once; AgentReasoningBackend relays its agent's provider-side calls through it, which were reported nowhere (RMK-480)
  • fix(hooks): the tool_call framework event of a call ON_TOOL_CALL withheld (a SYNC BLOCK, a fail-closed hook that raised or whose context would not build) carries is_error on every door, as its observers and the model read it (RMK-480)
  • refactor(examples): extract the realtime voice step of tool_call_context's main into _alice_by_voice, so main ends shorter than it began (RMK-476)
  • refactor(tools): tidy tool_turn_context per review: a call record naming another room than the turn's is refused like a room and a room_id that disagree, the module docstring says what a realtime call reads, the example's test step is its own coroutine, and the five tests that set the turn or call contextvars by hand go through tool_turn_context (RMK-476)
  • fix(realtime): per review, a call an ending interrupts is reported on every ending: a failed start reports the calls issued while it was pending in its shielded rollback, on either branch and once cancelled (the gate kept dropping them), the conference reports a call its unplug left unserved and its settled reports survive a cancelled waiter, and the channel's close lets abandonment reports finish before its sweep (RMK-477)
  • feat(tools): tool_turn_context(...) runs a block as a tool call of the turn its arguments describe (room or room id, actor, toolset, chain depth, call record) and restores the previous context on the way out, so a test calling a handler directly no longer sets the private contextvars; the realtime serving_tool_call and the tests' tool_call_in install through the same installer (RMK-476)
  • test(orchestration): the background run's own tests hand it the supervisor and run a parallel team, a sequential one being supervised in the background now (RMK-478)
  • fix(orchestration): a supervisor's sequential team in the background is supervised as in its turn, and every background run takes the install's task_timeout and max_revisions (it ran an unsupervised chain at 120 s); a voice auto_delegate install registers its supervisor for that flow (RMK-478)
  • fix(orchestration): a supervisor's per-worker background delegation is the strategies' background run, bounded by task_timeout, freed before its outcome is handed back, its terminal entry failed when no one heard it, ended by close(); a background pipeline whose workers all failed posts failed and tells the supervisor so (RMK-478)
  • fix(orchestration): kit.close() ends the strategies' background runs (supervisor delegate_workers with async_delivery, Loop delegate_loop): the kit holds each run, close() cancels it before the delegated tasks, and a cancelled run frees its room and posts its terminal entry failed with nothing handed back; they outlived the kit, their workers still generating (RMK-478)
  • fix(orchestration): a strategy's worker delegation is one sequence (run_worker) on the supervisor's sequential, parallel, supervised and waiting per-worker doors and the Loop's producer and reviewers: bounded by task_timeout (delegate_to_ ignored it), and its terminal status entry posted however it ends, a cancellation included (it left the worker pending) (RMK-478)
  • fix(tasks): an inline delegation runs its worker's turn in a task of its own, so a worker cut at its bound under a supervisor's call (the supervised sequential delegate_workers) no longer leaves its tool-loop context in the call's task, which reported the call twice on ON_TOOL_CALL, with its result then cancelled (RMK-478)
  • fix(realtime): a call an ending interrupts is reported once, cancelled, on the conference as on the channel and when a start fails: one serve_unbooked helper for a call no result can name, the conference's refusals and abandonment reports tracked beside its teardown instead of cut by a detach, and the calls queued behind a start that fails handed to the executor on the ended session (RMK-477)
  • fix(conference): per review, a conference mint reads the room with the caller's organization before anything is tracked or admitted, through the scope check the realtime session start now shares from channels.base, so no credential admits to another organization's room; the scope tests cover the inbound session and auto-connect doors and read the channel's own books (RMK-475)
  • fix(tools): per review, every gate judges an MCP alias through two shared readings, call_admitted for the policy and call_gated for skill gating: PolicyExternalToolHandler approved an external agent's mcp____ call its policy refuses, and the text channel's execution guard and its closed-gate wording read the called name only (RMK-483)
  • fix(security): join() and a realtime channel's start_session() read the room scoped to the caller's organization before any session exists, so another organization's room is not found and no session joins it or declares a track to its recordings (RMK-475)
  • fix(tools): a call under an MCP alias is judged by the tool policy and skill gating under both names, so a tool the policy denies or a skill gates never runs as mcp____ on a realtime session or a conference that declares no catalogue; the MCP handler reads the alias through the same rule (RMK-483)
  • test(streaming): a failed streamed turn's tests read ON_ERROR and AFTER_TTS as soon as process_inbound returns, which awaits the async hooks, instead of after a sleep (RMK-467)
  • fix(streaming): the stream handed to the streaming channel is closed on every exit, a failed response whose channel stopped reading at its row, a channel that failed mid-stream and a cancelled turn included, instead of staying suspended until garbage collection (RMK-467)
  • fix(tools): per verification, a human-input request whose waiting call is cut is withdrawn, so no late answer is taken for a call that is gone (RMK-465)
  • fix(realtime): per verification, an activation for a name that is no skill hints none of the tools the channel serves itself, as on the text path (RMK-461)
  • fix(tasks): per verification, delegate_task's cache answers a repeat only once its task completed, a failed or cancelled task running again (RMK-462)
  • fix(elevenlabs): per verification, a call's parameters are read with the shared realtime rule, readable_arguments (RMK-455)
  • fix(realtime): per verification, a call recovered from speech is booked on arrival as a provider's call is, and a conference reports a call that arrives while it unplugs, so an ending reports every call once, cancelled (RMK-460)
  • fix(gemini): per verification, a reconnect reconfigure() asks for frees the calls the old socket issued before the new socket's handshake, as the receive loop's does (RMK-460)
  • fix(tools): per verification, a tool a served recovery revealed survives a find_tools of the same round, whichever call settles first (RMK-461)
  • fix(recording): per review, a gone room's recordings are reached by the organization they were started under only: the recorder manager keeps it while the room records, and list, feed and stop refuse another organization on a gone room as on a live one; the guard is named for what it checks (RMK-469)
  • docs(streaming): per review, the streamed responses' docstring says what a batch broadcast would do, not what it used to (RMK-467)
  • fix(streaming): per review, a voice speaks all a failed response produced, its last partial sentence included, then sends its transcript and fires AFTER_TTS; the framework closes the failed response's open calls outside the read a channel may cancel; the response's failure stays the turn's when the channel raises on top of it; and a streamed failure's log line carries its traceback again (RMK-467)
  • refactor(voice): extract the response text the sessions read and the sentence chain built on it from VoiceChannel.deliver_stream (RMK-467)
  • fix(conference): per review, a conference call no tool handler serves reads as unserved, which an ON_TOOL_CALL hook may still serve, as on every channel, where it was refused (RMK-465)
  • fix(voice): per security review, the frame-rate limiter counts every session again, bound or not, as it guards the pipeline, and forgets the windows that expired once a second, which changes no decision and leaves no entry for a session that stopped sending (RMK-466)
  • fix(tools): per review, a human-input request the handler gave up on (closed, released) takes the generic failure path whether it happened before the call or while it waits, only a human's or a hook's rejection being a refusal; HumanInputRejectedError is a RoomKitError too, and the change is marked breaking with its consequences (RMK-465)
  • fix(recording): listing a room's recordings and declaring a track to them read the room scoped to its organization, through the read a stop makes, so another organization's room is not found and nothing of its recordings is listed or fed; both verbs are async (RMK-469)
  • fix(orchestration): per review, a strategy's background run goes through one sequence (run, release the room, hand back, post the terminal entry) for the supervisor's workers and an async Loop: its outcome is told in the session whose call started it, one run per room whichever voice channel calls, and the terminal entry is failed when the outcome reached no one or a Loop's producer stopped it; a background delegation is told in the session that delegated, and a task that did not complete is handed back whatever text it left (RMK-462)
  • docs(changelog): per review, what the ending window records, the AudioPipeline contract after on_session_ended, the VoiceChannel doors, the energy barge-in count and the realtime segment spans (RMK-466)
  • test(voice): per review, the offload tests build their voice and realtime channels through shared builders instead of four copies of the setup (RMK-466)
  • fix(realtime): per review, a RealtimeVoiceChannel session's speech segment spans hang under its session span, which the pipeline never had: both channels hand it over through _pipeline_session_active, after the activation, and the teardown comment says the marks come before this teardown's own awaits (RMK-466)
  • fix(pipeline): per review, the AEC reference process_outbound feeds as audio is generated skips an ended stream, as the playback reference does, instead of rebuilding the canceller's native state for every leftover frame (RMK-466)
  • fix(voice): per review, the VoiceChannel doors outside the pipeline rebuild nothing for a session unbound since: the three audio level hooks share one gate that reads the binding before the throttle writes, the frame-rate limiter counts bound sessions only, an out-of-band DTMF notes its digit only for a bound session, and unbind forgets the energy barge-in count too (RMK-466)
  • refactor(pipeline): per review, the ended-stream marks live in EndedStreams, which owns their bound and a lock, so two sessions ending at once on different threads cannot fail the eviction and skip a release (RMK-466)
  • fix(elevenlabs): per review, a call tool_timeout_s abandons frees its id before the channel hears of the abandonment, as every provider frees it, so a call the agent issues under the id while a cancellation callback suspends is a new call (RMK-460)
  • fix(tools): per review, a recovered tool joins the reveal window only once the tool answered its call, its eligibility read without revealing: a refused recovery no longer takes out a find_tools reveal or a served recovery of the same round, and a sibling that answers while it waits references nothing of it (RMK-461)
  • fix(realtime): per review, a call the provider abandons frees its id whether or not its observers already heard it, so nothing is sent for a call reported but not yet delivered and a call issued under its id is answered: ToolCallBook.release frees the id and RealtimeToolCall.interruptible says whether a handler is left to cancel, on the channel and the conference (RMK-460)
  • fix(tools): a reasoning backend's loop reads a call the voice gate refused as refused and any other error as failed (ToolCallResult.refused, filled by the channel from the call's outcome), and the human-input tool reads only a rejection as a refusal (HumanInputRejectedError): a timeout is a failure and any other error the generic failure, its message withheld from the model (RMK-465)
  • fix(streaming): a response that fails mid-stream is not handed again to the channel that streamed it: its text row reaches that channel inside the stream, as a completed response's does, then the failure; a channel that swallows the failure no longer makes the turn a cancelled success, and the default deliver_stream delivers what it buffered (RMK-467)
  • refactor(streaming): extract the hand-off to the streaming channel from _handle_streaming_response (RMK-467)
  • fix(regenerate): every intelligence channel whose failure the broadcast reports fires ON_ERROR, as on the inbound path, through the same helpers; the first stays the caller's error (RMK-402)
  • refactor(voice): the ended-stream mark is explained where it is kept, so the pipeline's init grows by its one field and bind_session ends no longer than it began (RMK-466)
  • docs(changelog): the speech state an unbind forgets and the segment spans' parent (RMK-466)
  • fix(voice): bind_session hands the pipeline its VOICE_SESSION span after activating the session, whose cleanup of a previous session's state dropped it, so speech segment spans hang under their voice session again (RMK-466)
  • fix(voice): unbind_session forgets the speech state a session unbound mid-utterance kept, its onset, suppression and queued segments, which only its SPEECH_END would have cleared (RMK-466)
  • fix(voice): audio work of a session whose end has begun reaches nothing and rebuilds nothing: the pipeline remembers the streams it ended, drops their frames and the callbacks still due for them, releases what a frame in flight across the release rebuilt, and a realtime session marks its end as it turns ENDED, before the teardown's awaits (RMK-466)
  • refactor(pipeline): extract the release of what a stream's frames built up from on_session_ended and _cleanup_session_state (RMK-466)
  • fix(orchestration): a voice Loop hands its outcome back to the voice channel that started it through hand_back, success and failure alike, as the supervisor's background door does: a loop that raised tells the model the work could not be completed instead of staying silent, a success arrives as a bounded, fenced instruction instead of the user's words, the room is released first and one terminal status entry follows the hand-back (RMK-462)
  • feat(tools): export TOOL_SEARCH_INFRA_TOOL_NAMES, TOOL_FIND_TOOLS and TOOL_LIST_TOOLS from roomkit and roomkit.channels, so a host that treats Tool Search's discovery tools apart reads their names instead of spelling them (RMK-468)
  • fix(tools): a hidden tool the model calls by its exact name stays revealed only once the tool answered the call (served, failed, withheld by an ON_TOOL_CALL hook), as the tool memory keeps any tool used; a call BEFORE_TOOL_USE, the policy or its handler refused before it ran reveals nothing, for the turn's next rounds or later turns (RMK-461)
  • fix(realtime): a call the provider abandons frees its id at once on the channel and the conference, as a delivered call does: a call the vendor issues under the id while the interrupted handler cleans up is a new call, answered, nothing is sent for the abandoned one even when its handler answers anyway, and a call its own handler's reconnect orphaned frees its id the same way (RMK-460)
  • feat(providers): export realtime_call_arguments and CutArguments from roomkit.providers.ai beside readable_arguments, so a realtime provider written outside RoomKit whose wire tells a cut follows the cut rule of RFC §6.4 through a supported import (RMK-455)
  • fix(tools): per review, a failure in a handler's own words audits failed as the failure envelope does, a host reporter written to the earlier report_refusal signature still hears refusals and cuts, the realtime door logs refusals and failures, and tests pin the stored outcome, the relayed refusal and the claim when the context fails (RMK-459)
  • fix(realtime): per review, tests pin that a message item done is no call (OpenAI Realtime, xAI, GPT-Live), the mock reads a cut through the shared rule, the cut-off hint fits an interruption too, and on_tool_call says how a provider reads a cut (RMK-455)
  • fix(realtime): per review, a delegation reads its catalogue inside its bound so a read that fails is answered by the spoken fallback, takes its transcript and first in the order it was announced, and a backend's call to a gated tool reads the refusal worded for a model that cannot activate a skill, at the gate as in unavailable (RMK-458)
  • fix(tools): the line between a refused and a failed call reads the same everywhere: ToolFailedError carries a handler's failure in its own words on the text and realtime doors, an MCP isError result is a failure (it was a refusal), a reasoning backend's relay keeps the outcome and detail its loop gave a call, and a call whose observers' context fails still emits its tool_call framework event (RMK-459)
  • fix(realtime): a realtime call its response cut runs only when its argument text reads: OpenAI Realtime and xAI hand a call on once its item is done (its status says whether the output cap cut it), GPT-Live reads that status through the same rule, and a cut call reaches the channel marked cut, refused before the gate as cut off (RMK-455)
  • fix(orchestration): per review, a supervisor's dispatch answering a background outcome starts a new run on success too (the cached dispatched answer is dropped when every run ends), a closing framework hands no background result back (one check, in hand_back), a run posts one terminal status entry after its hand-back, and the failure is the framework's words, not fenced as a worker's output (RMK-451)
  • fix(voice): per second review, a session's failed synthesis is reported once its playback is released and in the room it joined when the channel never bound it, a session failing before the AI did is still reported, AFTER_TTS waits for a served session, and PlaybackErrors never turns a cancellation into the stream's failure (RMK-448)
  • refactor(voice): per review, extract the playing of one session's speech from _send_tts, run AFTER_TTS under the session span from one helper, and keep the chunk wrapper private to PlaybackErrors (RMK-448)
  • fix(realtime): a reasoning backend is offered the tools the participant's current role admits (its catalogue and refusals were read with the role the session last read, while the gate re-reads it at each call), and skill gating reads the same per-door exemption as the policy, which exempts nothing where the channel serves none of its tools (RMK-458)
  • fix(tools): per review, only the schema list_tools(name=...) serves goes out uncut (a hook's replacement, a BLOCK or a gate refusal in its place is bounded), a realtime reveal is dropped when a handoff changed the session's catalogue while the call was judged (an activation hint's too), and tests pin that a served search reveals whatever a hook replaced, that the hooks judge the whole result and that an orphaned call reveals nothing (RMK-447)
  • fix(voice): per review, every session whose synthesis fails is reported once as tts_error with its session_id and fires no AFTER_TTS, a TTS without streaming included; on a streamed response that session stops early as a barge-in does, so the response is stored cancelled, without ON_ERROR or a replay of what was heard (RMK-448)
  • refactor(voice): extract the closing of a streamed response on the sessions that heard it from deliver_stream (RMK-448)
  • refactor(voice): per review, one PlaybackErrors context manager carries the rule the four backends copied, handing back a stream failure even one the backend caught, FastRTC sends per transport, and send_audio documents the contract (RMK-448)
  • fix(orchestration): a supervisor's background workers whose pipeline fails hand the failure back to the supervisor, which had told the user results would follow and never heard of it, without the error's message; the room is released before the outcome is handed back, so a re-dispatch runs rather than reading the stale dispatched answer (RMK-451)
  • fix(realtime): per review, a call no result can name serves no call's context and is reported cancelled when the channel closes before its outcome, its refusal names the tool call_tool carries, a call on an ended session leaves no barrier lock, OpenAI Realtime and xAI hand on an explicit null id as an id-less call, and ElevenLabs parameters that are no object reach the channel as text instead of ending the conversation (RMK-442)
  • fix(realtime): per review, a reconnect a call's own handler caused spares only that call (an id issued again names another call, which it abandons), OpenAI Realtime, xAI and GPT-Live take the id off the response's books before the send yields, a Gemini injection made during the send queues behind the held ones, and tests pin that a call counts delivered before its send and that a cancellation names the latest call under its id (RMK-441)
  • fix(tools): a find_tools reveal counts once its call is served: a text turn revealed its matches from the handler before ON_TOOL_CALL judged the call, and a realtime session swapped its window and reconfigured whatever a hook returned; ON_TOOL_CALL now judges a realtime Tool Search call before the model reads it, as activate_skill, and a search that finds nothing reveals nothing (RMK-447)
  • fix(realtime): every call reaches the channel and every entry refusal takes the path of any call: a call that named no tool is refused before the gate and answered (Deepgram and GPT-Live dropped it), a call without an id or under an id in flight is read against the session's end, unwrapped and held behind the transcription barrier, and ElevenLabs keeps the service's call id whatever parameter the model wrote (RMK-442)
  • fix(voice): tts_error goes through one emission that names the session when one session's synthesis failed; say() reports a TTS failing on any backend and fires no AFTER_TTS (RMK-448)
  • fix(voice): the local, RTP, FastRTC and Buzz backends hand back an exception raised by the audio stream they play, through one ChunkSource wrapper, and absorb only their own transport errors, so a TTS failing mid-sentence reaches the VoiceChannel (RMK-448)
  • refactor(local): extract the queuing of a realtime response's bytes in the speaker buffer from LocalAudioBackend.send_audio (RMK-448)
  • fix(realtime): an id names its call until the call's result goes out, so a call issued under it after that is a new call the channel and the provider both answer (it was refused by the one and booked by the other, never answered); Gemini Live and Deepgram free the id before the send yields and an ElevenLabs handler's cleanup takes only its own future (RMK-441)
  • fix(tools): per review, a call the channel decided that never ran (a handler that raised, a refusal ACP imposes on an approval it cannot apply) reaches ON_TOOL_CALL's observers only on the external and ACP doors, refused sits last on ToolCallEvent, and the change is marked breaking with every case on_tool_result no longer hears (RMK-432)
  • fix(skills): per review, a host says how its requires names are served (SkillRegistry(requires_match=...), exact names by default, so a hub-style requires is read as the host reads it), an activated skill opens a gate an unavailable one shares, a required tool only a closed gate holds is missing and a call to it says so, the channel's own tools count on both doors, and no hook sees a denied schema (RMK-429)
  • fix(tools): per review, a name no tool carries reads the same under Tool Search on a realtime session as on a text turn (its call_tool and list_tools included) with the hint only where find_tools is callable, a reasoning backend is not told to activate a skill, a tool both denied and gated reads the policy, and the gate's docstrings state its order (RMK-428)
  • fix(tools): a refused call reaches ON_TOOL_CALL's observers only on every door, marked refused beside cancelled; an external handler hears its own refusal through on_tool_refused, a handler that raises is a failure the channel reports with what failed, and an ACP call reports one body with or without a handler (RMK-432)
  • fix(skills): a skill's requires is checked by every activation with one rule, against the tools the conversation declares once its tool policy is applied (a text turn checked none, a realtime session counted a denied tool and handed over its schema), and a skill marked unavailable keeps what it gates closed (RMK-429)
  • fix(providers): per review, a call to a tool the request did not enable is told so rather than that it could not be parsed, Mistral's error and the last-call rule on an unreadable call and on PolarGrid's function-less entries are tested, and the docs name the context window, a refusal and the last-call rule (RMK-438)
  • docs(providers): per review, transport= keeps redirects and the per-request timeout but the transport owns its pool and environment proxies are not read; the webhook rename is marked breaking with its migration; acp_event_text cites RFC §6.4; the OTel close flushes inline when no thread can start (RMK-408)
  • fix(mcp): per review, the catalogue is built before the connection is kept, so a listing it cannot read closes what was opened (RFC §21.2); connected says whether the connection is live, and tool_meta hands out deep copies (RMK-408)
  • fix(conference): per review, ensure_bot on a channel with nothing to consume or say raises ConferenceCapabilityError instead of joining a silent observer (RMK-408)
  • fix(webrtc): per review, a refused websocket client is closed, not only forgotten by the stream (where it stopped counting against concurrency_limit): reject_connection closes its socket, the voice backend's auth refusal closes it too, both transports await their pending closings on close, and the auth test fails if the refusal runs inside the start-up it cancels (RMK-408)
  • fix(tools): a refusal reads the same text for the same cause on every door (an AI channel's turn, a reasoning backend's, a realtime session, a conference), and every gate checks the tool policy and skill gating before the arguments, so a denied tool never names its schema (RMK-428)
  • fix(inbound): per review, an ACP turn never prompted names no end (its record says when the prompt returned), turns is RoomKit's key so a host's value there is neither carried nor merged, a buffered reply to the caller (a Supervisor's cut pass) names its end too, an ACP stop reason outranks interrupted again, and an answer to an answer is tested off the caller's turns (RMK-437)
  • fix(providers): every text provider reads a response's end with one rule: the context window, a filter and a refusal cut a call as the output cap does (Anthropic ran an open tool_use block with {}), the context window ends a turn truncated without a retry and a schema answer truncated everywhere, Gemini's UNEXPECTED_TOOL_CALL is told and retried as a malformed call, and only a response's last call can be cut (RMK-438)
  • docs(c7): regenerate llms-full for the host extension points (RMK-408)
  • fix(webrtc): a peer FastRTCRealtimeTransport's auth refuses is closed through reject_connection, in a task of its own, instead of staying connected with its audio ignored (RMK-408)
  • feat(webrtc): FastRTCRealtimeTransport.reject_connection refuses a peer the host will not serve: told why, closed, cleaned from the stream and unregistered, each step even when an earlier one fails (RMK-408)
  • fix(telemetry): the OpenTelemetry provider never exports on the event loop: flush hands the export to a thread and returns at once, and close waits for it at most shutdown_flush_timeout seconds (RMK-408)
  • feat(mcp): a host reads what an MCP App needs from the same connection: each tool's _meta from the listing made at connection, a resource, and a tool's raw CallToolResult, which like call_tool is not bound by tool_filter (RMK-408)
  • feat(providers): an OpenAI-family provider takes transport=, which every request of the SDK goes through inside its own default client: OpenAIAIProvider and every provider built on it, Azure and OpenRouter that build their own client, and create_vllm_provider (RMK-408)
  • feat(http): a webhook provider's body and headers are built by public build_payload and build_headers, the extension points send() calls, and its config is a property (RMK-408)
  • feat(conference): ConferenceChannel.ensure_bot awaits the bot's join for a host that must know it is in: the live session returned, one join for concurrent calls and the lazy triggers, a lost session joined again, an unattached room refused without a record (RMK-408)
  • feat(acp): acp_event_text is public: the text an ACPChannel gives its agent for an event, a rich one read as its plain text, for a host building an ACP prompt of its own (RMK-408)
  • fix(orchestration): per review, a supervisor's pass hands no consumed stream on (no empty message to a streaming transport), its failure is logged at its own level, its fallback carries the turn's record, and a pass stopped on purpose says nothing (RMK-436)
  • fix(inbound): the caller reads how each agent's turn ended under response_metadata turns[channel_id], even a turn that wrote no message, and an ACP turn's ON_AI_RESPONSE carries its stop reason; one reading of a turn's end serves both (RMK-437)
  • fix(memory): per review, the turn's footprint counts the channel's own notes (plan, tools-used digest, speaker attribution) and keeps the reply budget apart as a TurnFootprint; BudgetAwareMemory's reserve floors the measured input and the reply is reserved once, the larger of the margin and the budget; the identity joins the channel's features, and a store's skills log at debug (RMK-406)
  • fix(orchestration): a Supervisor whose task-formulation pass was cut short answers the user with the fallback a reasoning backend speaks, its loop_end_reason on the message; a pass that failed hands its error to the caller, logged once (RMK-436)
  • fix(delegation): per review, a Loop tells a producer's failed turn from a cut and reports the provider's error; a result submitted before a failure still counts; one agent's failure never carries another's end (RMK-433)
  • fix(voice): per review, the playback measure and the turn recorder count every chunk as the 16-bit PCM the guard lets through, VoiceBackend.send_audio documents decoded PCM, LiveKit's voice track is tested refusing an encoded chunk, the CHANGELOG says which backends hand the refusal back, and the LLM docs configure ElevenLabs for PCM (RMK-415)
  • test(tools): per review, an external handler decides a call whatever keys the model wrote, and the handler reads a model's _result key whole; the CHANGELOG says what the old keys did and what a provider still sending them gets (RMK-439)
  • fix(tools): per review, an activation's hint reveals its tools once the call is served, as find_tools reveals (the window swapped, what is declared anyway left out), points to call_tool on a fixed provider and never names the channel's own tools; a native tool is kept by skill gating, not counted toward Tool Search and no longer breaks an activation (RMK-430)
  • feat(memory): an AI channel measures the turn's footprint before reading its memory, which a budget-aware memory reserves at least; run_skill_script is a public Tool; a skill registry adds a skill built in memory and copies a subset with its paths and marks (RMK-406)
  • fix(voice): a VoiceChannel refuses a TTS chunk that is not 16-bit PCM before a byte reaches the pipeline or the transport, on say, deliver and deliver_stream, so an MP3, Opus or G.711 TTS no longer plays as noise (RMK-415)
  • fix(delegation): a delegated turn that failed after it began keeps its end on the task: the error stays the provider's, and the task carries loop_end_reason (error, an ACP agent's interrupted) and the worker's narration, through a TaskTurnFailedError logged as its cause (RMK-433)
  • refactor(voice): the 16-bit PCM guard of a conference backend moves beside AudioChunk as require_pcm16, shared by LiveKit and the mock conference backend (RMK-415)
  • fix(tools): a call the provider already ran says so on the call, never in its arguments: AIToolCall.served carries its result, and a _result key the model writes is an argument like any other, judged by the policy and the gate (RMK-439)
  • fix(tools): a turn's toolset reads alike at its edges on every door: a native tool without a name stays declared under Tool Search and any policy, call_tool is exempt like the other search tools, what a round declares is in the allowed names and list_tools, AFTER_TOOL_ROUND sees what BEFORE_AI_GENERATION sees, and an activate_skill named after tools hints and reveals them alike (RMK-430)
  • fix(recording): per review, a record naming another room is refused, a room closed by its timer stops its recordings, a recording started on a live room joins its media only once announced with the room's tracks declared first, a gone room's running recordings are stopped, and the feed lives in a public module (RMK-405)
  • fix(delegation): per review, a task ends exactly once: whoever takes its end runs it, so a cancel landing while a finished task ends leaves it as it stands; a cut cancel still ends its task; the inline end and a delegation cut before it ran end too; a closing framework hands nothing back; the span covers the whole delegation (RMK-434)
  • fix(delegation): a task cancelled from outside ends as every task does, inline or in the background: cancelled, ON_TASK_COMPLETED and on_complete run to their end, the notified agent told, a supervisor's worker freed, and its span ends with the task's status (RMK-434)
  • feat(recording): a host commits a record outside the pipeline with its index counted delivered, maps a Postgres events row as the store does, and starts, feeds, lists and stops a room's recordings, each start and end announced (RMK-405)
  • fix(recording): closing or archiving a room stops its recordings once the scoped read has found the room, so a call refused for another organization stops nothing (RMK-405)
  • build(deps): cap elevenlabs below 2.70 and install it with the providers extra, so the SDK patch's canaries run in CI (RMK-440)
  • fix(orchestration): per review, a realtime pipeline's gate judges each call by the agent the room talks to, the one that serves it; a handoff applies the next agent's policy to every session before any is reconfigured, read for the participant's role; the skills refusal comes before anything is installed and spares an empty registry (RMK-427)
  • test(orchestration): read the Loop's typed outcome in the worker cut-short test (RMK-435)
  • fix(orchestration): per review, an async Loop names its producer's failed task without the task's error; the caller reads the failure beside an earlier draft too; the result is typed, its iteration the last completed, the stop recorded on the room's state; no producer is blamed for a loop that ran no iteration (RMK-435)
  • fix(realtime): per review, no provider books a call the channel refuses with nothing sent, so OpenAI Realtime's continuation no longer waits on one; ElevenLabs sends nothing for a call without an id; a duplicate keeps the first call's tool name and delegation; GPT-Live reads a cut call's whole arguments (RMK-440)
  • fix(orchestration): a Loop whose producer's task failed gives no empty answer, keeps an earlier output not approved and says why it stopped, sync and async; the voice delegate_workers waits like its twins, and a cut stream is no supervisor answer (RMK-435)
  • fix(orchestration): a realtime pipeline's active agent answers to its own tool policy beside the channel's, on the declaration, the gate, Tool Search and the allowed names, a handoff applying the next agent's, and an agent carrying skills is refused at the install (RMK-427)
  • fix(realtime): every provider hands every tool call to the channel, an undeclared ElevenLabs tool, a duplicate id, an id-less call and a GPT-Live call the output cap cut included, and the channel refuses an id-less call as a duplicate, reported once with nothing sent (RMK-440)
  • test(ai): per review, the exception type is pinned on the streamed and regenerated ON_ERROR too, the realtime pipeline keeps an opted-out agent's prompt, and the shared-agent example splits its setup from its report (RMK-407)
  • fix(tools): per review, a report is claimed once its observers' context is built on every runner, a realtime result that went out owes its outcome, an ACP call's end reaches its report when cut, a call on an ended session is reported once, and a cut call's framework event keeps its markers (RMK-431)
  • fix(tools): a call cut while its own report ran is reported once, with the outcome the model read, every channel claiming its report after ON_TOOL_CALL's SYNC chain, and a backend call issued once its session ended is reported once, cancelled (RMK-431)
  • feat(ai): a turn's context has public extension points: steer() addresses a room and says how many loops it reached, describe_empty_event says what an event with no content reads as, and an agent's identity block can be left to the host (RMK-407)
  • fix(framework): a turn that fails before its stream exists reaches ON_ERROR named by its exception's type, as a streamed turn's failure is (RMK-407)
  • refactor(framework): the intelligence ON_ERROR report after a delivery set is a step of its own (RMK-407)
  • fix(tools): per review, the turn's deadline is resolved once for the rule the loop enforces and the limit its end marker states, the loop's round logs count tool rounds, the limits are said to ride the marker only, and every exit, error included, is pinned against its ON_AI_RESPONSE (RMK-411)
  • fix(tools): per review, a turn's toolset and what its policy admits are stamped by one helper on both text doors, the reasoning backend's included, resolved for the turn's participant, and a session with no catalogue names no list (RMK-420)
  • fix(tools): per review, a call the provider ran keeps its outcome, a cut call reaches the observers alone, one cut while reported or before its handler is asked is reported once, and a handler that raises while deciding fails the call (RMK-419)
  • feat(tools): a tool loop's end marker states the limits the turn ran under, its round cap, deadline and per-turn budget, and counts the tool rounds that ran as ON_AI_RESPONSE does (RMK-411)
  • fix(tools): current_tool_allowed_names() leaves out what the turn's policy denies its actor, on a text turn and a realtime session as on a conference (RMK-420)
  • fix(tools): per review, a round the loop tries again ends its segment on a new SegmentBreakMarker, so the room keeps a continued answer as its own message and a reasoning backend says it as progress; the retry rule returns its verdict, one helper stores the loop's rules, and unfinished joins the cut-short reasons and every list of loop ends (RMK-410)
  • fix(tasks): per review, a delegated answer and how its turn ended are read off the same agent, the turn's end rides its stream rather than the caller's merged record, and an ACP worker whose prompt never returned is cut (RMK-418)
  • fix(tools): a call the turn cut while an external handler decided it is reported once, cancelled, through the handler's new on_tool_cancelled, on an AI channel and an ACP agent alike (RMK-419)
  • fix(tasks): a delegated turn's end is read from its record by one reader, so an ACP worker that stopped short and a worker cut before any text with a shared transport fail their task naming the end; an AI turn's end joins its response-metadata record (RMK-418)
  • fix(realtime): per review, a reasoning backend's docs name both of the agent's bounds it drops, and its bound test pins the timeout the observers read (RMK-417)
  • feat(tools): an AI channel's continuation policy goes on an answer that did not act, and a turn whose bound runs out ends unfinished (RMK-410)
  • fix(realtime): a reasoning backend's tool call is bounded by the voice channel's gate only, so a tool that waits by design or one the channel bounds above 30 s is not cut by the backend agent's own bound (RMK-417)
  • fix(tools): per review, AFTER_TOOL_ROUND's text matches its pipeline, and the event names only the resolved toolset (RMK-409)
  • refactor(framework): per review, extract an AI channel's hook wiring from register_channel (RMK-409)
  • fix(tasks): per review, Loop, Supervisor and the notified agent read a failed task's work as none, both task builders share one outcome, ON_TASK_COMPLETED carries the turn's end, and a cut turn is logged once as a warning (RMK-414)
  • fix(tasks): a delegated worker whose turn its round cap, deadline or budget cut short fails its task, its narration kept as the output and loop_end_reason in the metadata, on every reader of the turn, inline or in the background (RMK-414)
  • fix(providers): per review, a call the response cut before argument text that reads arrived is partial on every provider, and Gemini replays as text only the current turn's foreign rounds, their images kept, on any model that may sign its calls (RMK-398)
  • feat(tools): an AFTER_TOOL_ROUND event names the turn's toolset, so a hook names what it withdraws (RMK-409)
  • feat(tools): AFTER_TOOL_ROUND acts between two rounds of an AI channel's tool loop (RMK-409)
  • refactor(tools): withdrawing a tool for the rest of the turn is _ToolLoopContext.withdraw (RMK-409)
  • fix(providers): an Anthropic cut call whose streamed arguments read runs as on every provider, a fallback receives the primary's rounds in a form its vendor takes, and an untyped tool schema root is declared an object (RMK-398)
  • fix(tools): per review, a provider's native tool no longer fails every realtime call, a conference with no catalogue has no allowed names, a realtime inventory lists the skill tools and call_tool reaches what it lists, and a pipeline declares only the agent tools it serves (RMK-397)
  • fix(tts): per review, Grok and Gradium ask a stream for pcm through one streamed_format rule, Gradium types alaw as audio/alaw and its Ogg opus as audio/ogg and declares the format its stream asked for, Grok encodes its WebSocket query, and the declared formats are tested on every path (RMK-413)
  • fix(tools): one tool list and one set of rules on a text turn and a realtime session: unavailable skills, requires, a pipeline's live channel tools, find_tools' related names, the always origin, the list_tools inventory, an undeclared infrastructure tool, and current_tool_allowed_names on a realtime door (RMK-397)
  • fix(elevenlabs): synthesize() reads the SDK's convert() stream instead of awaiting it, so it no longer raises TypeError on every call (RMK-413)
  • fix(elevenlabs): TTS chunks and data URLs declare the sample rate and codec output_format names, so pcm_8000, pcm_32000, pcm_48000 and ulaw_8000 are no longer declared 44.1 kHz, nor alaw and opus mp3 (RMK-413)
  • fix(tts): a Grok or Gradium stream configured for WAV asks for raw PCM, so no RIFF header is played as audio at the start of every sentence; synthesize() keeps its WAV (RMK-413)
  • refactor(grok): extract the WebSocket URL of a streamed turn from synthesize_stream_input (RMK-413)
  • fix(voice): per review, a channel's close() stops taking frames and lets the DSP pool finish the ones it holds before its teardown, so a frame in flight opens no STT stream after it and reaches no provider after the session ended (RMK-392)
  • fix(realtime): per review, a reasoning agent keeps its budget, repairs and serialises its session's conversation, is refused once registered, and is traced under the session; a cut backend call is reported; a supervisor's first pass stores its calls through the room's gate and hands on no task when cut; a worker's result is read from its served stored call only (RMK-396)
  • fix(voice): with inbound_dsp_threads, the callbacks a frame fires run on the pipeline's event loop, so a streaming STT opens its stream and a realtime provider hears the caller (RMK-392)
  • fix(realtime): a reasoning backend's provider error is logged once by the delegation, at its level, without a traceback (RMK-403)
  • refactor(framework): the cascade carries whether its caller logs its stream failures, so no function grows to thread it (RMK-403)
  • fix(realtime): a reasoning backend is an agent on the AI channel's tool loop, so a delegation ends as every AI turn does; a supervisor's first pass hands on its final answer and stores its calls; a worker's result is its own served call (RMK-396)
  • fix(framework): per review, every failed turn, delivery or delegated task is logged once by what catches it, at its level, DEBUG only for a caller that receives it (RMK-403)
  • refactor(framework): extract the ON_ERROR of a failed response stream from _handle_streaming_response (RMK-403)
  • fix(vui): a reply written on several lines reaches Vui as running sentences, so no syllables are invented at line breaks, and max_secs goes to 60 s with a warning when a reply reaches it (RMK-400)
  • fix(ai): a provider error that fails a turn before any round is logged once, by the stream's consumer (RMK-403)
  • fix(tools): per review, an AI turn reports once, cancelled, every call it announced and nothing else reported, through one claim per call, whatever cut it (a stop, a barge-in, a cancellation in the gate, the handler or the judging); an ACP refusal follows the last decision, and the tool_call framework event carries its markers on every path (RMK-395)
  • fix(tools): every outcome of a tool call reaches ON_TOOL_CALL as the model read it: a text call a stop or a cancellation cut, an ACP call with or without an external handler and its outcome, a report's chain, a realtime skill activation and a pipeline agent's declined call (RMK-395)
  • fix(tools): per review, only a skill's served instructions are kept whole, a realtime or conference refusal's observers receive the raw message, and the external handler's example states the new order (RMK-394)
  • fix(tools): the same gate and serving on every door: a hidden sandbox or human-input tool is recovered and validated, a refusal is bounded, a realtime skill tool runs in the tool call context, the external handler applies its policy first, and a realtime or conference handler's structured copy reaches ON_TOOL_CALL (RMK-394)
  • fix(ai): per review, a rebuilt tool call pairs with its start by the turn's correlation id, so two turns running at once that reuse a call id keep their own arguments (RMK-393)
  • fix(ai): an AI channel's tool memory and skill activations are rebuilt from its own tool rows only, each call's end paired with its own start (RMK-393)
  • fix(realtime): per review, GPT-Live checks names in the provider on its own endpoint before anything is sent, Deepgram checks the think block in force, and tools given with a session or a reconfigure are refused under a name no vendor accepts (RMK-375)
  • fix(gemini): Gemini Live flags a failed call's result under the function response's error key and a served one under result, as Gemini text does, whatever the result's text (RMK-375)
  • fix(realtime): a call whose arguments do not read reaches on_tool_call as the model's text and the channel and a conference refuse it, and each realtime provider checks its endpoint's tool-name rule when it declares a session's tools (RMK-375)
  • fix(voice): a resumed sentence is answered once: an incomplete turn's wait holds until the transcript of speech that has ended joins the turn, at most 10 s more (RMK-391)
  • feat(examples): the local assistants wait 2 s for the first words before judging an interruption on duration, INTERRUPTION_WAIT_MS to change it (RMK-390)
  • fix(voice): a SEMANTIC segment that ends before its first word is judged on its final transcript, so a short "stop" cuts in and an "okay" does not (RMK-390)
  • feat(examples): the local Vui and French assistants keep talking through an "okay" (SEMANTIC with PhraseBackchannelDetector), INTERRUPTION picks the strategy through a shared build_interruption (RMK-390)
  • feat(voice): PhraseBackchannelDetector lets the voice talk through an acknowledgement and stops for anything else, in English and French (RMK-390)
  • feat(examples): the local Vui assistant's LLM is told it remembers the conversation, VOICE_DEBUG shows the messages it receives, and emphasis marks no longer reach Vui (RMK-390)
  • feat(examples): the local Vui assistant tells its LLM Vui's tags and filters the rest, waits for Smart Turn, names its STT, and can log and record a session (RMK-390)
  • feat(tts): StripBrackets keeps a TTS's own tags, TTSFilterChain runs several filters as one, and VUI_TAGS lists the tags Vui renders (RMK-390)
  • fix(tts): Fluxions lists the account's cloned voices, reads a list without short ids, and warns about an unknown voice at warmup, per review (RMK-373)
  • refactor(tts): one collect_wav_content turns a PCM stream into a WAV data URL for Vui, Pocket and Fluxions, per review (RMK-373)
  • feat(tts): FluxionsTTSProvider speaks with Vui hosted by fluxions.ai, beside the local VuiTTSProvider (RMK-373)
  • fix(vui): close() unloads and joins the Vui thread even when cancelled or failing, and the provider never falls back to the shared executor, per review (RMK-371)
  • fix(vui): every vui-tts call runs on a thread of the provider's own, so Vui's codec leaves no shared thread in inference mode (RMK-371)
  • docs(ai): ModelInfo.capabilities names PolarGrid's catalog tags and what the three shared tags share (RMK-389)
  • fix(polargrid): a zero temperature or top_p reaches the server on both paths, a default cap fits a small model window, a max_tokens below 1 is refused at config, and the generate tests answer through the SDK's own request path (RMK-389)
  • docs(ai): ModelInfo.capabilities says what it holds: a listing's reported and speech tags, a catalog's provider-specific flags, and the three tags shared across providers (RMK-389)
  • fix(polargrid): an answer is no longer cut at the SDK's 150-token default: a request always carries a cap, 4096 when none is set, and a larger one is sent as 4096 instead of failing the turn (RMK-389)
  • refactor(ai): tidy per review: every list_models ends through one AIProvider._listing, LiteLLM tags a speech model by its cost-map mode, and the tags are exported and documented (RMK-389)
  • fix(ai): a catalog's routing flags stay out of list_models, and the speech models OpenAI, Gemini, Mistral and PolarGrid list carry transcription or speech, read off their ids or PolarGrid's catalog (RMK-389)
  • refactor(polargrid): tidy per review: a request the SDK refused itself has no HTTP status, the dead pg_model_type read goes, the curated index is a constant, and error statuses are tested on generate, stream and list_models (RMK-389)
  • fix(polargrid): an edge's speech models carry transcription or speech, an SDK error keeps its HTTP status, a billing error is not retried, and the tests answer in the SDK's own shapes (RMK-389)
  • fix(ai): list_models backfills the capability tags the curated catalog knows for a model its listing leaves untagged, what the endpoint reports still winning (RMK-389)
  • fix(polargrid): the SDK patch refuses a bad message as the SDK does, drops a usage line it cannot read instead of failing the turn, closes its stream, and its canary runs the SDK's own stream; ollama and polargrid-sdk capped below their next minor (RMK-384, RMK-383)
  • fix(ollama): the SDK patch builds a tool request's messages with the SDK's own types, so an image given by its path is read, always says whether it streams, and its canary watches only what the server reads (RMK-383)
  • fix(polargrid): a streamed turn reports its usage, through a patch of polargrid-sdk that asks for it and reads the server's last line, kept apart with a test that fails the day the SDK carries it (RMK-384)
  • fix(ollama): a tool's parameter schema reaches the model whole, through a patch of the ollama SDK kept apart until a release stops dropping nested properties, and a test that fails the day it does (RMK-383)
  • fix(ai): Anthropic reports thinking tokens, an OpenAI overflow is read off the body the SDK hands over, DeepSeek checks its tool names and replays an answer's reasoning in its field, and the conformance drivers speak through the real SDKs, each vendor's usage shape, and catch every reviewed mutant (RMK-378, RMK-309)
  • fix(deepseek): a tool round's earlier reasoning goes back in reasoning_content, empty when the round did not reason, which DeepSeek requires in thinking mode (RMK-309)
  • test(ai): one tool-calling conformance suite runs on every text provider, a driver per wire answering with the vendor SDK's own objects, and fails on a provider with no driver (RMK-378)
  • fix(ai): Gemini receives a failed tool result under its error key, and Mistral reads cached prompt tokens from the dict its SDK hands over (RMK-378)
  • fix(ai): one round assembler for the loop and the reasoning backend, each stretch of text kept in place, and a reasoning block cut before its signature not replayed (RMK-377)
  • fix(anthropic): each reasoning block goes back as it came, with its own signature and in its place, a redacted block as its opaque data (RMK-377)
  • refactor(ai): AIChannel's mixins share one contract the type checker reads after them, so ty checks every implementation against it, and a test checks each member exists as declared (RMK-315)
  • refactor(ai): each AIChannel mixin declares what it needs from the others under TYPE_CHECKING, so ty checks every contract against its implementation (RMK-315)
  • fix(ai): an Anthropic block the response ended before closing keeps its announced id and streamed arguments, a server behind a custom URL decides its tool names, a second call to the same tool stays apart, and the Chat Completions builder renders exactly as the copies did (RMK-309)
  • feat(ai): OpenAI refuses before the request a model Chat Completions refuses, and Anthropic flags a tool result whose call did not succeed (RMK-309)
  • fix(ai): a tool call whose arguments do not read never runs on any provider, the model reading whether it was cut or written unreadable, and streamed calls stay apart under the id they end with (RMK-309, RMK-301)
  • refactor(ai): OpenAI, its derivatives, Mistral and PolarGrid render a conversation through one Chat Completions builder, each stating what it renders differently as a ChatDialect (RMK-309)
  • feat(ai): one declaration per tool format, a tool without parameters declared as an empty object, a name no vendor accepts refused when the tool is defined, and a name its vendor refuses refused by the provider before the request (RMK-309)
  • fix(acp): cancel reconstruction locally during shutdown
  • fix(acp): honor shutdown before a replacement prompt
  • fix(acp): keep session reconstruction cancellable
  • feat(acp): rebuild an invalidated session once under the room turn lock
  • refactor(ai): drop the dispatcher chain the one tool loop no longer reads, and bring the changelog, docs and test names in line with it (RMK-308)
  • fix(ai): the skill activations rebuilt from the stored rows read each call's outcome, and a MockAIProvider that does not stream is read through the default adapter (RMK-308)
  • fix(ai): the realtime reasoning backend replays a call with its metadata, and ACP's ON_AI_RESPONSE carries the turn's thinking (RMK-308)
  • fix(ai): a round that mixes the provider's calls with the channel's replays them all with their results, a tool a hook withdrew stays the channel's to refuse, and a call the external handler denies or the response cut is stored refused (RMK-308)
  • docs(ai): the CHANGELOG and the AI channel reference read one tool loop, the stored tool row's outcome and the per-call routing (RMK-308)
  • feat(ai): a stored tool row states how its call ended, and the tool memory rebuilt from the rows keeps the calls the live one keeps (B18, RMK-308)
  • fix(ai): who serves a tool call is decided call by call: the provider's own calls are reported, a provider-side tool is its handler's, any other is the channel's, gate first (C8, C14, VC6, RMK-308)
  • refactor(ai): one tool loop for every turn: a provider that does not stream is read through its generate(), and a turn without tools is one round (RMK-308)
  • fix(ai): a provider that does not stream hands the tool loop its thinking signature and each call's metadata (C10, RMK-308)
  • refactor(ai): the tool loop's context and its contextvar live in roomkit.tools.context, beside the accessors that read them (RMK-142, RMK-308)
  • fix(vui): a cut reply keeps no word of the text chunk after the last frame heard, and the changelog states where the decoder grid does not hold, per review (RMK-197)
  • fix(vui): presets carry their baked bias and speaker token through vui-tts 1.2's public API, which replaces every private access (RMK-197)
  • fix(realtime): every provider reports the calls a disconnect, a handoff or a lost connection drops, the ElevenLabs SDK task starts in a context of its own, and a toolless model is offered no skill or Tool Search (RMK-299)
  • fix(realtime): a provider reports every call it abandons, returns a failed call as an error where its protocol can, declares no tool to a model that calls none, and runs its session tasks in a context of their own (RMK-299)
  • fix(gemini): a Live turn that ends on a function call the server could not parse tells the model the call did not run, once until the user speaks again (RMK-299)
  • fix(ai): the turn notes' header opens them only as the channel places it, and a part appended after the notes opens no second header (RMK-368)
  • feat(ai): add_turn_note and split_turn_notes, so a generation hook adds to the turn's notes and a reader splits them off (RMK-368)
  • refactor(realtime): the gate of a realtime session's tool calls moves to its own mixin (RMK-306)
  • fix(realtime): a call is reported once when a cancellation, the session's end or its own hang-up lands while it is judged, and a backend's call reaches the host's tools only (RMK-306)
  • fix(realtime): the participant's role a tool policy reads before each call is read under the framework's lease (RMK-306)
  • refactor(conference): a conference's tool calls go through the realtime tool executor, on the same book of calls in flight (RMK-306)
  • refactor(realtime): one executor serves a call on every door of a speech-to-speech channel, the provider's, speech recovery's and a reasoning backend's (RMK-306)
  • feat(tools): one BEFORE_TOOL_USE decision, one ON_TOOL_CALL judgement and one failure report in the kit, and a BLOCK's reason reaches the model on every channel (RMK-306)
  • fix(realtime): one book of the calls in flight, so each call is delivered once and reported once on every door (RMK-306)
  • fix(tools): a blocked call keeps its reason in memory, a call whose hooks cannot run is reported on every channel, and only the host's answer declines a call (RMK-305)
  • fix(tools): the audit wrapper hands the channel the answer itself and records a cancelled or declined call as such, and a live TOOL_CALL_END carries each call's status (RMK-305)
  • feat(tools): a handler declines a call with UnservedToolCallError, read the same on every path, and every result part is built from a typed outcome (RMK-305)
  • refactor(realtime): extract the handler's answer to a call, as text, from _serve_gated_tool_call (RMK-305)
  • fix(tools): one reader applies an ON_TOOL_CALL verdict and one runner runs the chain, on every channel, and an emptied result stays served (RMK-305)
  • refactor(ai): extract a tool call's gates, its serving and its judging from _run_one (RMK-305)
  • fix(tools): data that names a part type stays data, a tool's tags survive extraction, and a missing stored result is a refusal (RMK-305)
  • fix(elevenlabs): tool_timeout_s defaults to None, so the channel's call bound is the only one (RMK-366)
  • refactor(tools): tidy the call bound per review (RMK-366)
  • fix(tools): the call bound reaches a realtime session's skill scripts and a pipeline agent's own tools, and only tools with a bound of their own escape it (RMK-366)
  • fix(ai): the streaming loop logs the provider error that ends a turn after a round, as the buffered loop does (RMK-366)
  • feat(tools): every wait on a tool handler is bounded per call, the handler cancelled at expiry, on the text loops, a realtime session and a conference alike (RMK-366)
  • refactor(recorder): tidy room recorder start per review (RMK-365)
  • fix(recorder): a room created again under its id keeps its recordings, and a recorder that fails to stop no longer holds the others (RMK-365)
  • fix(recorder): a room recorder that refuses to start fails create_room before the room is written, all or nothing (RMK-365)
  • feat(recorder): PyAVMediaRecorder refuses to store a recording unencrypted, and encrypts it with the RecordingEncryption the voice recorder takes (RMK-69)
  • fix(orchestration): a transport shared into a delegated room receives the agent's answers through the room's gates, never the task nor a result tool's re-prompt (RMK-360)
  • fix(voice): a streamed response runs BEFORE_TTS on each sentence before the TTS reads it, so a redaction hook is no longer bypassed on a streaming TTS (RMK-268)
  • fix(voice): RealtimeVoiceChannel announces its pipeline's recordings, through one implementation shared with VoiceChannel (RMK-355)
  • fix(sandbox): the sandbox preamble no longer tells the model its commands run in an isolated container (RMK-357)
  • fix(video): ScreenInputTools leaves pyautogui's failsafe on, and a tripped failsafe answers the model (RMK-356)
  • Begin 0.95.0.dev0 development

Full Changelog: v0.94.0...v0.95.0