Skip to content

v0.96.0

Choose a tag to compare

@sboily sboily released this 09 Oct 15:36
· 1432 commits to main since this release

What's Changed

  • docs(changelog): 0.96.0
  • fix(models): AIGenerationEvent keeps its released positional order, trigger and purpose after timestamp (RMK-545, RMK-562)
  • fix(delivery): an HTTPChannel delivers to its webhook without a recipient, as it did before the refusal (RMK-646)
  • fix(gemini): per review, every size and configured type the model lacks is refused by its own name, the geometry in its own module (RMK-655)
  • fix(meta): per review, a model the catalog does not list refuses every image option instead of dropping them (RMK-654)
  • fix(gemini): a size the image model cannot produce is refused by its own name, with what the model offers (RMK-655)
  • refactor(gemini): resolve a call's image options in one function and write them into the request in another (RMK-655)
  • fix(tests): the Meta image test prompts in English (RMK-654)
  • feat(image): thinking_level takes medium, which Nano Banana 2.1 accepts and Meta refuses before the call (RMK-654)
  • fix(gemini): per review, an undeclared image type is read off its bytes, the default model's refusals are tested on every entrance, and the example speaks English (RMK-656)
  • refactor(image): extract payload_mime_type from the xAI, OpenRouter and Meta providers (RMK-656)
  • feat(gemini): Nano Banana 2.1 is the default image model, as Google recommends for new projects (RMK-656)
  • fix(models): Nano Banana 2.1 returns JPEG only, so a PNG request is refused before the call (RMK-652)
  • fix(check-models): drop the claude-haiku-5-5 exception now that the mirror lists it (RMK-652)
  • refactor(models): per review, Nano Banana 2.1's 4K is 3780 tokens as Google prices it, 2.5 Flash Image shuts down 2027-03-15, and Pocket TTS has no english_24l (RMK-652)
  • refactor(mistral): per review, one builder for the Mistral SDK fixtures, close() tested on every text provider, and the extra's 3.x admission recorded (RMK-651)
  • fix(models): Sonnet 5.5 cache reads at $0.10, Sonnet 4.5 deprecated, Mistral Large 4 and Nano Banana 2.1 catalogued (RMK-652)
  • fix(mistral): close() closes the SDK's HTTP client, and the tests drive mistralai on httpx2 as production does (RMK-651)
  • chore(deps): bump the python-deps group across 1 directory with 19 updates (#48)
  • fix(routing): per security review, a room step 1 finds through an identity is the sender's only when the identity joined it through the same channel (RMK-579)
  • fix(routing): per review, email addresses are compared lower-case and without a display name, so one mailbox is one room (RMK-646)
  • fix(routing): step 1 finds a member the host added under their identity by the number linked to it, among several rooms (RMK-579)
  • feat(providers): per review, Messenger, Sinch, VoiceMeUp and Telnyx take an API host too, and SendGrid and ElasticEmail check theirs under the same rule (RMK-648)
  • fix(routing): per security review, the chat a binding delivers to names its conversation, so a room prepared for a chat takes no one else (RMK-646)
  • fix(routing): per review, on a chat channel a message's conversation is the chat it was posted in, so a private chat and a group are two rooms and a group's members share one (RMK-646)
  • docs(delivery): deliver() brings content into a room for its agents and never texts the correspondent, whose message goes out with send_event (RMK-647)
  • feat(providers): WhatsApp through Twilio's Messages API, from the SMS config, with whatsapp: addresses (RMK-649)
  • feat(providers): a Twilio or Telegram provider's API host is configurable, HTTPS or this machine only (RMK-648)
  • fix(routing): per security review, a room prepared for one correspondent takes no one else, a room opened for a message replies where it came from, and phone numbers are compared in E.164 (RMK-646)
  • fix(context): per security review, a message an AFTER_TOOL_ROUND hook adds is cleaned as one steering injects, and the long-run scan test leaves a slower runner room (RMK-639)
  • fix(context): per review, a hook's notes block, a reasoning backend's answer and an image's prompt are cleaned, a copy split over two notes is cut, and a realtime host compiles the patterns when attached (RMK-639)
  • fix(delivery): a room RoomKit opens for an inbound sender replies to them, and a transport with no recipient refuses before any send (RMK-646)
  • fix(speaking): per review, a channel has its policy forget a room it joins or leaves, and a listening room answers no held-back question and stays silent on a turn it cannot judge (RMK-641)
  • feat(speaking): the agent's thought is about what it hears, names the people, and never turns to the agent itself (RMK-642)
  • feat(speaking): a request to only listen is a state of the room the classifier reads, and a final silence waits for no thought (RMK-641)
  • fix(context): a copy of a runtime mark is replaced in every block of the turn's notes that quotes others and in every text injected into a realtime session, and Gemini Live's marks join the marks (RMK-639)
  • fix(context): per review, no double quote mark reads as a labelled line's end, a split copy is cut only over unlabelled messages, and a compaction reads a line back before naming and cutting (RMK-635)
  • fix(voice): per review, every reasoning backend receives the transcript with a copy of a runtime mark replaced, the host's own included (RMK-637)
  • fix(context): a copy of a runtime mark is replaced in a memory's retrieved notes, a realtime broadcast and a reasoning backend's transcript, and a prior summary is told by its provenance (RMK-637)
  • fix(context): each labelled line carries what was typed on it as a JSON string, so a Name: in the middle of a line reads as part of it (RMK-635)
  • fix(context): per security review, the handoff's bracketed opening counts without its colon, and only runtime_record says the runtime wrote a record (RMK-603)
  • fix(context): per review, a runtime record's model-written text is cleaned, a split copy is cut over every run of user messages, and the handoff's mark needs its colon (RMK-603)
  • fix(context): which text is the runtime's is a provenance, runtime_record, so a copied handoff relay or a host memory's copied mark is replaced while the runtime's own are kept (RMK-603)
  • fix(context): per review, every line break is made a line feed before the line labels, a blank first part keeps its label, and a transcript drops each part's line labels (RMK-616)
  • fix(context): every line of a labelled turn opens with its author's label, since merged turns read a Bob: line inside Alice's message as Bob's (RMK-616)
  • fix(voice): StripTechnicalText keeps the words around a removal apart and takes an object whose first key is a bare name, as the jev-thought demo met them (RMK-624)
  • fix(orchestration): per review, who asked heads only a task block, AnswerOnly matches a name without its rank, and an unlabelled joined turn reads as before (RMK-615)
  • fix(voice): per deep review, a reply whose sentence budget is said stops at its first word past it, before the turn asks for a tool call, and its last sentence is cut like any other (RMK-623)
  • fix(tasks): per deep review, only an intelligence channel's hand-back closes on the result-only line, since a realtime injection may open no turn and keep it as a standing instruction (RMK-626)
  • fix(context): a delegated task names who asked for it, a summary-joined turn keeps its label, and a speak policy reads the conversation's labels (RMK-615)
  • feat(voice): VoiceChannel(max_sentences) ends a reply at the first sentence over its budget, as a barge-in does, so the room keeps about what was heard (RMK-623)
  • refactor(tests): the voice-room helpers for streamed replies move to tests/voice/streamed_replies.py (RMK-623)
  • fix(context): per review, an ACP session labels every request once it was sent one, a summary and its conversation share one threshold, and the speaker note is a runtime mark (RMK-614)
  • fix(voice): per deep review, each streamed response filters its text with its own copy of the channel's tts_filter, so rooms streaming at once share nothing a filter buffers (RMK-624)
  • fix(voice): per deep review, StripTechnicalText filters a whole text on its own state, keeps the space after a removal glued to the text before, and says what its rules cannot tell (RMK-624)
  • feat(tasks): a result handed back to a model says the turn gives that result only, another reply taking care of the rest, in the conversation's language (RMK-626)
  • fix(speaking): per deep review, AnswerOnly counts the speaker it answers among the people when no participant record names them so, and refuses one string for its people (RMK-625)
  • feat(voice): StripTechnicalText, a TTS filter that keeps a tool call written as JSON, a note to itself or a separator out of the voice (RMK-624)
  • refactor(examples): tidy the thinking-while-listening example per review, its log lines in functions of their own and the thinker's measure said for what it covers (RMK-627)
  • fix(speaking): per deep review, a thought that comes back after the agent spoke during its call reports the emptied thought it replaces, and fires nothing when it changes nothing (RMK-627)
  • fix(context): the request an ACP agent is prompted with and the lines a memory summarizer reads name a participant's turn's author when several people speak (RMK-614)
  • feat(speaking): AnswerOnly, a speak policy around any other that answers only some people and only listens to everyone else (RMK-625)
  • fix(context): per security review, a rank a source leaves stays its own, so no later source reads like its earlier turns (RMK-620)
  • docs(orchestration): per review, send_greeting's example saves the conversation state with save_conversation_state, not a whole-room write (RMK-620)
  • fix(text): per review, a name keeps no number that reads as punctuation, so "Alice ⑵" does not mimic a rank (RMK-620)
  • fix(context): per review, a name that reads as the agent's label keeps its rank, so two such sources do not share one label (RMK-620)
  • fix(context): per review, a name that reads like two others ranks after both and renumbers neither, a registered name keeps its seat through a rebuild, and a malformed register is read as absent (RMK-620)
  • fix(participants): per review, a voice, video or realtime session started for a participant records its channel, so their turns keep their name (RMK-620)
  • feat(speaking): ON_SPEAK_DECISION carries how long the policy took and whether it decided again once the agent thought, ON_THOUGHT how long the thinker call took (RMK-627)
  • test(voice): the agent-backend channel tests wait for the delegation's spoken output instead of a fixed 0.1 s, which lost the race on a loaded CI runner (found under RMK-557)
  • fix(providers): per review and the owner's decision, a model OpenAI's catalogue tags keeps its tag whatever the config states, and the configs whose provider applies its vendor's rule refuse supports_reasoning_effort_with_tools (RMK-557)
  • feat(providers): a server said to take reasoning_effort beside function tools gets it on a turn with tools, and an effort left out for want of saying is warned of once (RMK-557)
  • fix(context): a turn's author record fixes its name with its rank, names alike through another share no rank, a room from before the register rebuilds it, and a participant id names them on their channels only (RMK-620)
  • fix(context): per security review and the owner's decision, a restricted turn joins the author register, so no later sender takes its rank (RMK-607)
  • fix(context): per review and the owner's decision, registered participants hold the first ranks, only a turn every reader sees joins the register, and the register keeps no id nor name (RMK-607)
  • feat(voice): an Azure Speech speaking rate, and the MAI voice agent example no longer cuts sentences or stops on an "okay" (RMK-618)
  • fix(routing): per second review, the claim knows the sender as the router does, the framework's sender is never recorded, a status skips another correspondent's room (RMK-580)
  • fix(context): a turn's author rank is fixed for the room when it is committed, so it holds as the window slides and across a session (RMK-607)
  • fix(routing): per security review, a claim the room lock outlasts refuses the message, and step 3 never admits the framework's own sender (RMK-580)
  • fix(routing): per deep review, claim the binding as the message is routed, know the sender by their identity, route delivery statuses by recipient (RMK-580)
  • refactor(routing): extract finding or opening a message's room from _route_to_room (RMK-580)
  • fix(context): per deep review, one person through several channels is one source, a broadcast ranks among what its session sees, and a name reading as the agent's label is a participant's (RMK-607)
  • fix(context): one resolver labels a participant's turn wherever a model reads it, and a sender who takes another's name carries a rank (RMK-607)
  • fix(context): per review, accented letters, every spelling with a multi-letter character, case-exact forms and | in a tag are read, and the apostrophe and quote forms are used (RMK-602)
  • fix(routing): on a channel bound to one room, a second correspondent gets a room of their own (RMK-580)
  • fix(context): the forms a tag, a runtime mark and a name are read in come from Unicode's confusables (RMK-602)
  • feat(examples): a microphone voice agent on Microsoft's MAI models, answering with Claude Haiku 5.5 (RMK-608)
  • fix(voice): per deep review, the Gemini TTS docs say what was measured and where untrusted cues are removed, and the transcript block joins the common test (RMK-601)
  • fix(voice): per the live service, MAI-Transcribe detects with the language left out, refuses three listed codes, and a firmed-up word no longer cuts the partial short (RMK-608)
  • fix(voice): a text to speak is a block on the Gemini TTS models that take a prompt (RMK-601)
  • feat(voice): Microsoft's MAI speech models: MAI-Transcribe streaming STT and MAI-Voice through Azure Speech TTS (RMK-608)
  • fix(realtime): two find_tools of one model response reveal all their matches (RMK-606)
  • fix(context): per deep review and the owner's decision, a nameless turn opens with @channel and every distinct source counts toward attribution (RMK-600)
  • fix(tools): an oversized quoted literal is refused, never raised, per security review (RMK-605)
  • fix(tools): a primitive the model's server quoted reaches the tool as its declared type (RMK-605)
  • fix(tools): two find_tools of one round reveal all their matches (RMK-604)
  • fix(context): in a room with several named speakers, a turn without a name opens with its channel's label (RMK-600)
  • refactor(context): reading text as a model reads it moves to roomkit._lookalike (RMK-599)
  • fix(context): per deep review, a tag with a combining iota is neutralised again, ACP room context lines are cleaned, and a mark copied in markup is a copy (RMK-599)
  • fix(context): a participant's copy of any runtime mark is replaced, not only the notes' header (RMK-599)
  • fix(context): a run of a combining iota after a tag's first letter is read in linear time (RMK-590)
  • fix(context): per security review, an opening tag at a fenced block's end or spaced from its bracket is neutralised, and a name with marks around You reads as You (RMK-590)
  • refactor(realtime): the GPT-Live append chunker and tokenizer move to live_append (RMK-590)
  • fix(context): per deep review, a provider never joins what a fence kept apart, any tag name fences again, and more spellings of a tag are read (RMK-590)
  • fix(context): six more renderings found by the chantier's sweep keep text from outside in its frame (RMK-590)
  • fix(context): a closing tag spelled with Cyrillic, Greek or Armenian homoglyphs cannot close a fenced block (RMK-590)
  • fix(context): per security review, a closing tag is neutralised where it starts in any form a model reads, and the frame helpers hold on any text (RMK-590)
  • fix(video): a view that comes back after its video session ended is not kept (RMK-593)
  • fix(voice): per review, a reasoning transcript line keeps 4000 characters so a long request keeps its end, and a blank line is left out (RMK-594)
  • fix(video): per review, a view ends with its video session, the turn reads it from its own context, and a binding the former vision path wrote reads its own prompt (RMK-593)
  • fix(voice): the voice transcript a reasoning backend reads quotes each line after its speaker's role (RMK-594)
  • fix(video): what a camera sees rides an AI channel's turn notes as a block, never its system prompt nor the room's binding (RMK-593)
  • fix(context): a closing tag errs toward what a model reads as one, and tags are read in one pass without a bound (RMK-590)
  • fix(context): per review, a tag is read in linear time whatever follows its name, a longer name is another tag, and every strategy prompt is tested (RMK-592, RMK-590)
  • fix(orchestration): each model's output a strategy hands another model is a block of its own, and the copied goal a block (RMK-592)
  • fix(context): a tag's name is read up to its end without consuming what follows, so a run of invisible characters after it costs linear time (RMK-590)
  • fix(realtime): per review, a framed text splits without ever stalling, a text within the bound stays whole, and every default-ignorable character counts as invisible (RMK-596, RMK-590)
  • fix(realtime): a framed text split into GPT-Live appends keeps its frame in each one (RMK-596)
  • fix(context): a closing tag with an invisible character in it cannot close a fenced block (RMK-590)
  • fix(context): per review, a copy of the notes' header is found whatever its punctuation or invisible characters, over adjacent text parts and in a steered message, and the thinker leaves an image input's notes (RMK-595)
  • fix(realtime): per review, a blank broadcast reaches no session, is a fenced tag, and the full-duplex and Deepgram changes are named (RMK-591)
  • fix(memory): CompactingMemory counts an image by what a provider bills, not its URL or base64 (RMK-589)
  • fix(context): a copy of the turn's notes' header in the conversation is replaced by a mark, the header is the channel's alone (RMK-595)
  • fix(realtime): a text another channel broadcast enters a realtime session as its author's quoted words, never as the application's instruction (RMK-591)
  • fix(speaking): the thinker names a speaker out of the quote, by the name the context gave (RMK-589)
  • fix(memory): both summarizing memories read an event alike, rich content by its text (RMK-589)
  • fix(context): per review, names keep their marks, a cut quote names its block, the tools digest quotes its arguments (RMK-589)
  • fix(tests): a background team's delegations are awaited by count, not a fixed pause (RMK-589)
  • fix(context): text from outside cannot leave its frame in an AI channel's context (RMK-589)
  • fix(tasks): the tasks' note says a task's progress is the latest its worker gave, to answer from without a tool call (RMK-564)
  • fix(tasks): what the tasks' notes give unquoted carries no text of its own (RMK-564)
  • fix(tasks): a task cancelled from outside carries its begun turn under turns, cancelled (RMK-529)
  • feat(tasks): an agent knows its room's background tasks and how far each got, without a tool call (RMK-564, RMK-544)
  • fix(providers): an image transport failure is retryable only when the request never left, and an image delivered as a URI is final (RMK-524)
  • fix(tasks): a re-prompted worker's task carries its last turn's record, and the error it hands on stays marked reported (RMK-529)
  • fix(voice): a continuous STT stream that reconnects on a backlog no longer loses it to Meta's refusal (RMK-581)
  • fix(providers): a paid image generation is retryable only where the vendor did not run it (RMK-524)
  • fix(conference): each connect the realtime provider refuses fires ON_ERROR (RMK-523)
  • fix(orchestration): a synchronous Loop answers with the producer's turn under turns, and a failed producer reaches the caller as the error its turn raised (RMK-529)
  • fix(core): send_event bounds its wait for the room lock by process_timeout and raises ProcessTimeoutError for a pre-commit expiry (RMK-525)
  • fix(voice): a reasoning backend's own names are refused on every door a session tool comes through, and its agent's policy reads the session participant's role (RMK-527)
  • fix(voice): both realtime hosts wait for the model's answer in one place and read its start the same way, and a conference answers a delegation issued while it connects (RMK-528)
  • feat(ai): the agent knows its answer was cut off, and goes on when let (RMK-563, RMK-533)
  • fix(voice): a session its provider ended takes no injection on a conference either, through one check both hosts share (RMK-530)
  • fix(core): a close from a tool during an awaited turn returns, and a task announced as the kit closes ends cancelled on both doors (RMK-526)
  • feat(providers): Claude Haiku 5.5 in the Anthropic catalog, and the examples on it (RMK-578)
  • fix(conference): a full-duplex model's delegation is answered aloud, and the conference is not idle while a tool call runs (RMK-528)
  • fix(voice): a realtime channel refuses a session tool its reasoning backend's agent serves itself (RMK-527)
  • fix(voice): a broadcast is injected through inject_text and announced as the injection it is, and an ended session takes no injection (RMK-530)
  • fix(providers): one reader of a message's refusal, OpenAI's generate reads its response in _response_of, and a summary is never blank (RMK-531)
  • fix(core): kit.close() ends an inline delegation and an awaited room turn as their neighbouring doors do (RMK-526)
  • fix(providers): generate() and the stream hand the loop the same text, reasoning and refusal (RMK-531)
  • fix(providers): an empty stream, a 200 error object and a statusless message read as the RFC says, on every provider (RMK-524)
  • docs(core): regenerate_response says where it waits and what refuses it in the lane (RMK-525)
  • fix(core): a regeneration is refused when its room closes while it waits in the lane, and send_event drops its cascade when its caller leaves (RMK-525)
  • fix(providers): a failed generation reads the same whatever its form and provider (RMK-524)
  • fix(conference): a start that fails or is cancelled reports its held calls, and an ON_ERROR hook may tear the model down (RMK-523)
  • fix(core): regenerate_response runs its re-broadcast in the room's lane, off the lock and unbounded (RMK-525)
  • refactor(core): extract the root pass's report from _post_plan_effects (RMK-525)
  • fix(conference): a conference's realtime model keeps the binding's mute, serves a call issued during connect(), and reports provider errors (RMK-523)
  • chore(ai): speaking turns and classifiers in English throughout (RMK-566)
  • fix(ai): a BEFORE_AI_GENERATION hook's replacement event is what the generation reads (RMK-565)
  • feat(ai): the agent thinks while it listens: a thinker on the AI channel (RMK-562)
  • fix(ai): a speak policy reads only the events its channel may know (RMK-561)
  • feat(ai): a speak policy on a classifier's judgments, composed in code (RMK-561)
  • feat(classifiers): narrow, typed questions answered with probabilities (RMK-255)
  • feat(ai): a speak policy decides whether the agent speaks, offers or stays silent (RMK-560)
  • feat(voice): only words interrupt, if asked: PhraseBackchannelDetector(cut_without_words=False) (RMK-559)
  • fix(tasks): task_status gives a task's whole result, the bus summary cut at a word (RMK-556)
  • fix(voice): a thanks or a reaction does not cut the agent under semantic barge-in (RMK-555)
  • fix(voice): the local backend's AEC runs without pause for the session (RMK-551)
  • fix(voice): the local speaker plays every response on one stream, and cancels the echo tail (RMK-551)
  • feat(voice): debug taps see a transport's own echo cancellation (RMK-552)
  • feat(tasks): a task's hand-back names what was asked (RMK-550)
  • feat(tasks): an agent cancels a task of its room with cancel_task, and is not told twice (RMK-549)
  • refactor(voice): the turn to speak in plain words, not as a floor (RMK-548)
  • fix(providers): a provider error names the provider and the status that answered (RMK-547)
  • feat(events): an answer names the event it answers, and its turn is told that event (RMK-545)
  • fix(core): a status naming a room fires its ON_STATUS_POSTED hooks on a kit not opened with async with (RMK-541)
  • fix(voice): the words that cut the agent off reach the room, never taken for echo (RMK-543)
  • fix(tasks): task_status declines a call for another tool, so the channel's next tool serves it (RMK-538)
  • feat(tasks): a delegated task is followed on the status bus, task_status reads a room's tasks, and the hand-back names its task (RMK-537, RMK-538, RMK-539)
  • refactor(orchestration): what pass 1 hands on lives in its own function, so persist_tool_calls is back under 40 lines (RMK-304)
  • fix(realtime): an activation's hint names no human-input tool on a realtime session, as on a text turn (RMK-304)
  • fix(orchestration): a supervisor's pass 1 stopped by a steering Cancel logs no warning, as a room turn does (RMK-304)
  • fix(core): the text a steering Cancel cuts is stored marked cancelled, as an outside cancel's is (RMK-304)
  • fix(providers): Gemini's context overflow wording is read as one, so its turn is compacted as other providers' are (RMK-304)
  • fix(realtime): a reasoning backend's call naming no tool is refused as unreadable, as a provider's is (RMK-304)
  • fix(realtime): a conference call whose own handler caused the reconnect runs on, through the abandon step both hosts now share (RMK-304)
  • test(realtime): the reasoning-backend test's session carries its state, and the span test waits for the report that now runs to its end (RMK-520)
  • fix(core): a tool call's claimed report runs to its end, so a cut while its observers are told no longer loses it (RMK-520)
  • fix(realtime): per review, a call's context lasts until its outcome, the ending cuts the other calls there and then, a backend's delegation ends after its hang-up call, an AI channel's close spares that call, and spared calls are held only while they run (RMK-520)
  • refactor(realtime): what an ending does to the calls it reaches lives in _realtime_endings, apart from serving a call (RMK-520)
  • test(core): per review, the turn-end matrix covers a failure at the first generation and pins one line with its cause per door (RMK-513)
  • fix(orchestration): per review, a delegated turn marks the failure it reported, so a synchronous Loop hands on a producer failing at its first generation without a second ON_ERROR; one expected-end rule for the realtime backend; the summary counts every failure (RMK-513)
  • fix(tasks): per review, a hand-back whose turn fails leaves the log line to that failure, with its cause and level, instead of taking it to DEBUG for every waiting delivery (RMK-513)
  • test(realtime): a call that ends its session, closes its channel or the kit, from a task of its own or a backend's delegation, runs on; a spared call is waited for or cut at close (RMK-520)
  • fix(realtime): the call that caused an ending is known by its call context on every ending, host and door, its delegation's task spared with it, and a close waits for a spared call still running, then cuts and reports it (RMK-520)
  • refactor(realtime): the close's sweep of the tasks the channel still runs is a step of its own (RMK-520)
  • fix(providers): per review, a streamed call whose function has no name and no arguments reaches the loop as it does through generate(), and every response exit names the model that answered (RMK-510)
  • refactor(gemini): a round's signature diagnostics and its calls with their ids are steps of their own, out of the stream reader (RMK-510)
  • refactor(providers): fold an SDK tool-call fragment in ToolCallSlots.fold_object, the step the OpenAI and Mistral streams each wrote (RMK-510)
  • fix(orchestration): a pipeline's handoff greeting turn is held by the kit, so close() cuts it instead of leaving it running (RMK-514)
  • fix(tasks): per review, the close test pins that a cut hand-back keeps its partial answer cancelled, logs the cut and still runs the task's callback; the delegation host declares the runs it holds (RMK-514)
  • test(core): a turn's end is reported and logged once, door by end: room, regenerate, pass 1, delegated, Loop sync, hand-back (RMK-513)
  • fix(core): a delivery that waits for its turn, a hand-back's, takes its stream's failure to log as a waiting caller does, so it is logged once (RMK-513)
  • fix(orchestration): a supervisor's pass 1 failure is reported as a streamed turn's, once, streaming, with its tool rows' correlation (RMK-513)
  • fix(orchestration): a synchronous Loop hands its producer's failure on without a second ON_ERROR, and its cut with none (RMK-513)
  • fix(core): a turn's expected end, or a failure its own turn already reported, is reported to ON_ERROR and summed up in the broadcast line no more where it surfaces (RMK-513)
  • docs(changelog): Anthropic's SDK retries, the hand-back cut at close, the handoff greeting's depth, and every name a pipeline leaves to its channel (RMK-509, RMK-514, RMK-516, RMK-517)
  • fix(anthropic): AnthropicConfig.max_retries defaults to 0, so RoomKit's RetryPolicy owns the retries and a refused connection is one attempt per try, as on OpenAI (RMK-509)
  • fix(examples): the realtime examples inject through the channel, which announces the text and keeps the chain depth (RMK-516)
  • fix(realtime): per review, a pipeline's handoff greeting goes in at the handing-off call's depth instead of restarting the chain, the realtime greeting has its own step, and the voice-session docstrings name every host (RMK-516)
  • fix(tasks): kit.close() cuts a delegation's hand-back under way, held by the kit as a strategy's run is, and the task still ends (RMK-514)
  • refactor(core): hold_task runs work as a task the kit holds until it ends, the step start_background_run already took (RMK-514)
  • fix(orchestration): per review, a realtime pipeline also leaves to its channel a human-input name it serves without declaring it, the warning says the agent's tool is neither declared nor served, and the test checks the agent's other tools are still served (RMK-517)
  • docs(changelog): RMK-509 covers the stream dropped before its first event, and RMK-510's four response readings (RMK-509, RMK-510)
  • fix(providers): per review, the OpenAI wire stream reads a transport failure after the status as retryable too, through one _wrap_error for both modes; Anthropic maps SDK failures in its own _wrap_error; Mistral and Gemini share one status-or-failure verdict (RMK-509)
  • test(providers): the conformance suite reads a response alike on every text provider: a server's new call id kept, usage on a chunk of its own, the model that answered, and calls with no function no tool round under a schema (RMK-510)
  • fix(gemini): a call takes the server's id when no earlier call of the response took it, through CallIds, a minted one otherwise (RMK-510)
  • fix(gemini): a response reports the model that answered, its model_version, through answered_by as every provider does (RMK-510)
  • fix(mistral): the stream reads a chunk's usage before passing a chunk with no choice, as the OpenAI wire and PolarGrid do (RMK-510)
  • fix(openai): generate() decides a constrained answer is due on the calls it read off the message, as the stream does, not on raw tool_calls entries with no function (RMK-510)
  • fix(examples): the ACP examples' on_tool_result hands on refused_but_ran and error_detail, so a call the agent runs past a refusal is reported by their handler (RMK-512)
  • fix(acp): per review, on_tool_result names what failed error_detail as _fire_on_tool_hook does, so an override handing its keywords on keeps its report; each report method says where its keywords go, the refused branch reads the one detail reader (RMK-512)
  • test(realtime): deliver(), the four injections and every door read the realtime host the same way, on the channel and the conference (RMK-516)
  • fix(realtime): serving_tool_call names the issuing session to the handler on every door, the conference's included (RMK-516)
  • fix(realtime): the greeting, a recovered call's result and a handoff's instructions are injected through the channel, which announces them to ON_REALTIME_TEXT_INJECTED; the unused recovered-call dispatcher goes (RMK-516)
  • fix(delivery): deliver() with no destination prefers any channel hosting a realtime model, a conference with one plugged in included, whatever the attach order (RMK-516)
  • test(orchestration): an agent's tool under a host, human-input or channel-served name is skipped and the install goes through (RMK-517)
  • fix(orchestration): a realtime pipeline reads every name its channel carries, host, human-input and the channel's own, so an agent's tool under any of them is warned about and skipped (RMK-517)
  • fix(providers): a refused or timed-out connection is retryable on Anthropic, Mistral and Gemini as on the other text providers, on generate() and the stream (RMK-509)
  • feat(providers): one recognition of a transport failure through an error's cause chain, is_transport_failure, beside the retryable status codes (RMK-509)
  • fix(acp): a call run past a rejected permission carries what failed in the rejection whether the handler or the channel reports it, through on_tool_result's optional detail (RMK-512)
  • fix(acp): a call the agent runs past a permission RoomKit rejected because its handler raised deciding it is marked refused_but_ran, like one it refused (RMK-512)
  • Begin 0.96.0.dev0 development

Full Changelog: v0.95.0...v0.96.0