Repository navigation
v0.109.0
What's Changed
- docs(changelog): promote 0.109.0
- docs(changelog): the Postgres private-state race as two probes measured it (RMK-631)
- docs(changelog): a reconnection mid-stream is BREAKING; RMK-780's mixed-version limit (RMK-491, RMK-780)
- fix(turns): export OwnerEvents and OwnerHost, which TurnClient takes (RFC §10.6.3, RMK-952)
- test(api): the signature snapshot leaves out pydantic's hooks (RMK-952)
- fix(voice): debug taps refuse an output_dir containing '..' (RFC §12.3.15, RMK-569)
- fix(channels): an agent publishes its answer text only when the host turns it on (RFC §6.4, RMK-951)
- test(inbound): the scope read before a room's own message draws is bounded by process_timeout (RFC §13.6, RMK-487)
- docs(storage): SQLite does not find an integer recipient beyond 64 bits (RFC §10.4, RMK-942)
- fix(strategies): claim_room_strategy() sees a discussion another process stored (RFC §19.7.5 rule 16, RMK-780)
- fix(host): a first post past its reentry budget binds nothing (RFC §9.5.1, §10.5, RMK-947)
- fix(inbound): a message refused for its organization draws nothing from its room's work budget (RFC §17.2, RMK-487, RMK-954)
- fix(store): Postgres writes no private state for a room deleted while it writes (RFC §14.1, RMK-631)
- docs(changelog): review of the 0.109.0 Unreleased section
- fix(events): send_event() reads a blank key as no key before refusing a keyed INSTRUCTION (RFC §13.4, RMK-942)
- fix(routing): a group binding's recipient is not read, and step 3's refusal is pinned (RMK-965)
- docs: a recipient that is not one is someone else's to routing, no value names no one (RMK-965)
- fix(routing): a binding recipient that is not one reads as someone else's; no sender claims the room (RFC §5.7, RMK-965)
- fix(discussion): an uninstall cut while it posts leaves that result to the kit; the replay proved settled (RMK-780)
- fix(discussion): a result taken and not committed is kept privately, and any process that uninstalls posts it (RMK-780)
- fix(discussion): uninstall_strategy posts the task results it holds before detaching their agents (RMK-780)
- fix(websocket): each send checks the registration as it starts; a gone registration's failures count against none (RMK-491)
- test(api): the snapshot pins cancel_task's organization_id (RMK-487)
- fix(tasks): cancel_task takes organization_id; the eviction is a Fixed entry (RMK-487, RMK-491)
- test(turns): a refused regeneration draws nothing from the hook's budget (RMK-490)
- fix(websocket): a stream sends only to the registrations it started with (RMK-491)
- test(supervisor): the mock kit's claim_room_strategy is a coroutine (RMK-487)
- test(api)!: the snapshot takes the room hook verbs and claim_room_strategy made async (RMK-487)
- fix(websocket): disconnect_websocket and unregister_connection take organization_id (RMK-491)
- fix(hooks)!: room hook verbs and claim_room_strategy are async and take organization_id (RMK-487)
- docs(websocket): the refusal does not say whose a connection id is (RMK-491)
- fix(websocket): a new scope or an eviction forgets the rooms an id followed (RMK-491)
- test(api): the snapshot pins organization_id on the verbs of RMK-487 (RMK-488, RMK-490, RMK-491)
- docs(c7): the WebSocket registry's organization, and start_task, submit_feedback and steer take organization_id (RMK-487, RMK-491)
- fix(websocket): a connection's organization is fixed at registration, and only it subscribes the socket (RMK-491)
- fix(host): start_task, submit_feedback and steer take organization_id (RMK-487)
- fix(websocket): a connection subscribed for an organization receives only its rooms (RMK-491)
- docs(c7): membership, deliveries, turns, ephemerals and connect_websocket take organization_id (RMK-488, RMK-490, RMK-491)
- fix(realtime): ephemeral publishes, subscriptions and connect_websocket take organization_id (RMK-491)
- fix(turns): deliver, greeting, regeneration and delegation take organization_id (RMK-490)
- fix(membership): add, remove, rename and roster reads take organization_id (RMK-488)
- docs: the register moves at the room's next turn that names its sender; a private-state key reserves the same metadata key (RMK-631)
- test(store): set_private_state removes the metadata's copy of its own key, any key, and keeps a writer's value its own (RMK-631)
- test(api): pin get_private_state and set_private_state in place of the register's pair (RMK-631)
- docs: a room's private state in the store; the register of authors is its author_register key (RMK-631)
- feat(store): a room's private state, kept apart from its metadata; the register of authors is its first key (RMK-631)
- fix(context): a context reads the register of authors whatever history it loads (RMK-631)
- test(api): pin get_author_register, set_author_register and RoomContext.author_register (RMK-631)
- docs: the register a process from before writes is merged by the next named turn; Postgres lets one start, a rollback ranks from the timeline (RMK-631)
- fix(context): a context that reads no history reads no register of authors (RMK-631)
- fix(store): PostgresStore's move of the registers keeps a copy it did not move (RMK-631)
- fix(authors): a register a process from before wrote to the metadata is merged, not ignored (RMK-631)
- fix(authors): the store keeps the register of authors apart from the room's metadata (RMK-631)
- test(video): kit.close() is held to stopping every recording and closing the recorder (RMK-568)
- docs(video): the PyAV recorder example stores its MP4 encrypted with --encrypt; c7 names RecordingEncryption's package (RMK-568)
- fix(video): a recording that fails to stop leaves the others encrypted; a refused one binds no session (RMK-568)
- test(api): pin VideoRecordingConfig's encryption and storage_encrypted_at_rest (RMK-568)
- fix(recorder): a recording that fails to close leaves no plaintext (RMK-568, RMK-569)
- feat(video): a session's video recording can be encrypted at rest (RMK-568)
- fix(recorder): a WAV storage with '..' is refused, never moved to the temp dir (RMK-569)
- fix(image): an error status after an SDK retry is ambiguous too (RMK-453)
- fix(openrouter): OpenRouterImageProvider takes transport=, in its own httpx client (RMK-453)
- fix(providers): a model listing whose 200 is not JSON raises, as before the SDK client read it (RMK-453)
- fix(image): a request the SDK retried is never read as unsent, whatever its last failure (RMK-453)
- test(image): an image attempt that timed out once sent is unknown, the SDK's error as raised (RMK-453)
- fix(image): an OpenAI or Azure image attempt that never left is failed, not unknown (RMK-453)
- fix(openrouter): list_models() goes through the provider's transport=, by its SDK client (RMK-453)
- fix(litellm): list_models() goes through the provider's transport=, by its SDK client (RMK-453)
- fix(providers): the image and vision clients on the openai SDK take transport=, for a host's outbound policy (RMK-453)
- test(mcp): the alias policy tests' docstring states when a server-scoped policy holds (RMK-493)
- docs(mcp): a policy that names a server holds over a catalogue of aliases alone (RMK-493)
- fix(mcp): refuse a server label ending with '_'; pin a catalogue of aliases (RMK-493)
- docs(c7): MCPToolProvider(label=) and a policy that names one server (RMK-493)
- fix(mcp): a labelled MCPToolProvider serves only the aliases naming its server (RMK-493)
- test(api): pin the hook engine's budget parameter after the public surface landed (RMK-954)
- docs: deliver(chain_depth=) at least the floor of a turn or a hook body; the CHANGELOG names the turn case (RMK-954)
- test(delivery): a deliver() queued from a tool call carries the turn's depth (RMK-954)
- test(events): the regenerate case of writes_nothing can fail (RMK-954)
- docs(hooks): the reentrance and lane docstrings name a hook body's depth floor (RMK-954)
- docs(c7): a hook's calls one deeper than its event; one hook counter for all the cascade's hooks (RMK-954)
- fix(hooks): a ReentryLoopCapError a hook's body lets escape is logged as a warning (RMK-954)
- test(events): a direct change past the cap writes nothing, for each of the three; work outside a hook body draws nothing (RMK-954)
- fix(hooks): a call from an ASYNC hook's body is one deeper than the hook's event (RMK-954)
- test(orchestration): warm the mark patterns before timing a discussion's install under the lock
- docs(c7): two counters per cascade, direct changes from a hook, ReentryLoopCapError (RMK-954)
- docs(hooks): the hook-body docstrings name the cascade's hook calls and a direct change's work (RMK-954)
- fix(host): post()'s refusal of a speaker says why, never suggesting the refused id (RMK-954)
- test(verification): a released message's ON_EVENT_UPDATED hooks run on a budget (RMK-954)
- fix(events): a direct change from an ASYNC hook's body draws a hook call; ReentryLoopCapError past the cap (RMK-954)
- test(hooks): the mutation loop over EDIT and DELETE; the frame inert after a body that raises or times out (RMK-954)
- fix(hooks): a cascade counts its ASYNC hook bodies' calls apart from its passes (RMK-954)
- test(hooks): test_reentrance.py's docstring names an ASYNC hook's body among the room's work (RMK-954)
- docs(c7): an ASYNC hook's body on its cascade's budget; post()'s chain_depth and speaker (RMK-954)
- test(hooks): an ASYNC hook that opens a room after every event stops at the cap (RMK-954)
- test(host): post()'s deadline is shared with its send_event, its binding read bounded by it (RMK-954)
- fix(host): post() refuses a speaker its host channel does not reach (RMK-954)
- feat(host): post() takes chain_depth, the chain the post continues (RMK-954)
- fix(hooks): an ASYNC hook's body draws on its cascade's reentry budget while it runs (RMK-954)
- revert(api): no dir on the lazy packages; help() and getmembers() never import a lazy name (RMK-948)
- docs(changelog): dir() and help() list the lazy names whose extras are installed (RMK-948)
- test(api): the docstring import check reads the all tables and imports nothing (RMK-948)
- fix(test): make api-snapshot drops a roomkit module removed on purpose (RMK-948)
- fix(api): dir() lists a lazy name only when its module would import, so help() survives a missing extra (RMK-948)
- build: make typecheck and CI run the lazy-exports typing test (RMK-948)
- docs(changelog): what a star import binds now, roomkit.tools.fence, and the lazy names' errors, dir() and types (RMK-948)
- docs: llms.txt announces no custom transcoder; AGENTS.md lists a new channel in roomkit.channels.all (RMK-948)
- build(api): make api-snapshot pins the names only an extra resolves, roomkit.webrtc's (RMK-948)
- fix(api): a type checker refuses a name a lazy package does not export, and dir() lists the lazy ones (RMK-948)
- fix(test): the import check reads code by its syntax and survives a dependency's own ImportError (RMK-948)
- feat(api): export HookEngine, HookRegistration, room_tasks and the speak-policy blocks (RMK-948)
- docs(changelog): the exports the API reference needed, the docstring imports and the WebSocket transport page (RMK-948)
- docs(c7): the WebSocket realtime transport is served by the application (RMK-948)
- test(api): the docstrings under src import roomkit names from public paths (RMK-948)
- feat(api): export the names the API reference documents that an application writes (RMK-948)
- docs(agents): AGENTS.md and CONTRIBUTING.md follow the one export rule (RMK-948)
- docs(changelog): the public-API rule, the exported names and the documented imports that failed (RMK-948)
- test(api): examples and documents import roomkit names only from public paths (RMK-948)
- feat(api): roomkit.store exports PostgresSchemaError beside SQLiteSchemaError (RMK-948)
- docs(api): examples, README, AGENTS.md and docs/c7 import roomkit names from public paths (RMK-948)
- feat(api): export from their packages the names documents use and no all listed (RMK-948)
- docs(api): one rule says which import paths are public (RMK-948)
- docs(c7): a replayed post() into a room closed or detached since raises; one process_timeout per post (RMK-947)
- test(api): the name guard pins every exported name, written by make api-snapshot (RMK-947)
- refactor(turns): PreviousTurn's scope record is the owner's own (RMK-952)
- fix(host): the first post's attach waits for the lock within process_timeout and runs to its end (RMK-947)
- docs(c7): post()'s refusals, idempotency_key and the speaker's name; HostChannel(channel_id=) (RMK-947)
- fix(host): post() attaches under the room lock, takes idempotency_key, and the host channel refuses what RFC §10.5 forbids (RMK-947)
- feat(channels): an agent's answer text, published live as TEXT_DELTA (RMK-951)
- fix(turns): a turn's ephemerals reach no one its answer would not reach (RMK-952)
- feat(channels): kit.post() and HostChannel, the host's own voice in a room (RMK-947)
- chore(probe): compare host channel shapes for RMK-947
- test: a blank key on a message is no key, in every store (RMK-942)
- test: a whole float reads back as its store's JSON holds it, an integer on Postgres past 1e16 (RMK-942)
- test: a record is stored as given but for its author record (RMK-943)
- docs(changelog): what 0.108.0 did with a replay past the cap (RMK-942)
- fix(reentrance): a send_event() replay of the room's own work is read before the budget and draws no pass (RMK-942)
- docs(c7): an adopted call's delivery waits behind the work under the lock, and runs ahead of the event in admission work (RMK-943)
- fix(store): the ABC's target writes keep the recorded results, and its key refusal asks check_idempotency when the lookup finds nothing (RMK-942)
- perf(events): a direct update or delete reads the room's context only for a hook of its trigger (RMK-943)
- fix(threads): a streamed row is rooted at its commit, under the room lock (RMK-943)
- test: an admission work's wait returns with its results or ends at process_timeout; a hard delete keeps a change of a change (RMK-943)
- fix(reentrance): a replay past the reentry budget returns what its key committed; changelog: thread-root entry under Changed, the Postgres float range named (RMK-943)
- docs(changelog): one Changed and one Fixed heading for RMK-942 and RMK-943, without the reverted wait entries
- docs(c7): a direct change stands when its room cannot be read after the lock (RMK-943)
- fix(visibility): an answer scope of internal a channel gives its response reads as none (RMK-943)
- fix(events): a direct update or delete whose room cannot be read after the lock stands and fires with the context it held (RMK-943)
- docs(changelog): how to migrate to a parent rooted on every path (RMK-943)
- fix(close): what the channels' closes start ends before the store is released, on a budget of its own; a run cut on a closing kit runs no work (RMK-943)
- fix(threads): an injected event is rooted before its hooks' context is built; tests on public paths only (RMK-943)
- docs(delivery): from admission work, a wait on an adopted call whose delivery needs the room ends at process_timeout (RMK-943)
- Revert "fix(delivery): a wait from an off-lock hook's body returns at once, as under the lock (RMK-943)"
- Revert "fix(delivery): a wait from any of an event's admission work returns at once, its parse and its report's handlers included (RMK-943)"
- fix(close): a hook task started as a channel closes is cut once started, before the store is released (RMK-943)
- fix(threads): a parent whose root a hard delete removed is dropped, the reply posted at the top level (RMK-943)
- docs(c7): admission work's wait returns at once, every path roots a reply's parent, commit_event's parent rooted (RMK-943)
- fix(delivery): a wait from any of an event's admission work returns at once, its parse and its report's handlers included (RMK-943)
- fix(threads): a reply's parent is rooted to its thread's root on every path that commits an event (RMK-943)
- fix(delivery): a wait from an off-lock hook's body returns at once, as under the lock (RMK-943)
- fix(lanes): an indexed record and step 2's attach in a room that exists are completed when their caller leaves (RMK-943)
- fix(close): a run held late in close() or after it is cut once started, its end run (RMK-943)
- test(store): the index test reads the store's tables only (RMK-942)
- test(store): drop the SQL-text check of the unique events index (RMK-942)
- docs(changelog): how to find and rewrite a binding persisted with a float recipient (RMK-942)
- test(store): a taken key refuses add_event and add_event_auto_index too (RMK-942)
- fix(store): the ABC's default commit_event refuses a key its channel holds (RMK-942)
- fix(store): the ABC's default delivery record checks itself instead of a context flag (RMK-942)
- fix(models): a blank idempotency key is no key (RMK-942)
- fix(store): Postgres init() on an up-to-date schema waits on no writer (RMK-942)
- fix(store): a held id stays the driver's refusal on Postgres whatever the index order (RMK-942)
- docs(changelog): reflow the recipient entry (RMK-942)
- docs(store): IdempotencyKeyTakenError names the record commit_event() commits (RMK-942)
- fix(store): an event is committed with the delivery results it brings (RMK-942)
- fix(store): Postgres init() takes no table lock on an up-to-date schema (RMK-942)
- test(routing): an integer recipient and its digits name one address (RMK-942)
- fix(store): the ABC's default record_delivery_results still writes the results (RMK-942)
- fix(store): Postgres reads and drops the names of its own schema only (RMK-942)
- fix(routing)!: a recipient is a string that is not blank, or an integer (RMK-942)
- fix(store)!: only record_delivery_results writes delivery_results (RMK-942)
- fix(store)!: a key its channel holds refuses another event with IdempotencyKeyTakenError (RMK-942)
- docs(release): the release prep pins the signatures the cycle added
- test(api): a signature change that breaks a caller fails a test
- Begin 0.109.0.dev0 development
Full Changelog: v0.108.0...v0.109.0