Repository navigation
fix: a failed turn keeps what it delivered, the system prompt holds still, a turn's words reach its next round, and wryme is held to the specs it speaks - #69
Merged
Conversation
…ds still A turn that delivers a finished job's result or another window's note records it on the turn's assistant row as a tool call and its result. When that turn's request failed, finish_streaming dropped the row as an empty reply, and the job or note was gone: it had already left its queue. A row with tool events now survives a turn with no reply. Open book pages rode in the system prompt with "Last inked: 7m ago", an age that changed every minute, so no two requests shared a system prompt: prompt caches missed, and hum began a new claude session each turn. The preamble now gives the time a page was last inked. On the Responses protocol the instructions were the first system message, which is the first open page when a station has no prompt of its own, and the pages were then added again as input items, so a page was said twice and a bookkeeping prod was dropped. The instructions are now every system message, as the chat protocol's system prompt reads them, and pages are said once. A warm window no longer stores the pages again each turn.
…and the words said before a call reach the next round The handler for response.output_item.done read `output_item`, a field no server sends: the API names it `item`, as the handler for output_item.added already reads it. So in the full window nothing finished was ever seen. Reasoning, mcp_call and hosted items were never carried into the next round, a call the server ran never showed its result, and the tests passed only because their fixtures used the same wrong name. Argument deltas read `output_item_id` alone, where the API sends `item_id`. The words the model said before a tool call were never carried either, so within a turn a stateless endpoint never saw them, and the next turn replayed them: the conversation looked edited between the two, and hum began a new claude session for it. An assistant message item is now carried into the next round like reasoning.
…and its tests are held to them The specs ride in spec/, pinned: OpenAI's OpenAPI cut to what wryme's clients reach, and Open Responses whole; spec/pull fetches them again. Every test fixture, event and request is checked against them (src/spec.rs), so a fixture can no longer agree with a bug, as the `output_item` ones did. What the specs found: - Hush asked for a reasoning summary of "none", which no spec allows; it now asks for none. - At the tool depth limit wryme sent max_tool_calls: 0, which caps only built-in tools and is below Open Responses' minimum; function calls kept coming. It now sends tool_choice: "none". - Message items went without `type`, which Open Responses requires. - With store false, a carried reasoning item without encrypted_content, or a message item with its server id, cannot be resolved; reasoning is carried only with its encrypted content, and words as a plain assistant message. - MCP argument events are response.mcp_call_arguments.*, not mcp_call.arguments.*. - Open Responses streams raw reasoning as response.reasoning.delta. - A failed mcp_call's error is an object, shown as its words. - A call the provider ran reports its result under its call_id.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Running wryme against hum's claude-cli models showed four wryme-side problems:
Deliveries were lost when a turn failed. A finished job's result or a window's note is recorded on the turn's assistant row as a tool call and result (
due_calls). If the request failed,finish_streamingtreated the row as an empty reply and deleted it. The item had already been claimed from its queue, so it was gone for good.The system prompt changed every minute. Open book pages render
Last inked: 7m agointo the system prompt on every request, so no two requests had the same system prompt. Prompt caches missed, and hum started a new claude session each turn.Responses protocol: pages said twice, prod dropped.
instructionswas the first system message, which is the first page when the station has no prompt of its own. The pages were then added again as input items, so each page appeared twice, and the bookkeeping prod (already taken byapi_messages) was dropped. In a warm window, the page items were also stored again on every turn.Responses, full window: nothing finished was ever carried. The
response.output_item.donehandler readoutput_item, a field no server sends (the API's isitem, which theoutput_item.addedhandler already reads). Reasoning,mcp_calland hosted items were never carried into the next round, and a call the server ran never showed its result. The tests passed only because their fixtures used the same wrong name. The words the model said before a tool call weren't carried at all, so within a turn a stateless endpoint never saw them and the next turn replayed them; to hum the conversation looked edited, and it began a new claude session. Argument deltas readoutput_item_idalone, where the API sendsitem_id.Fixtures agreed with bugs. No test checked wryme against the specs it speaks, so problem 4 hid behind fixtures with the same wrong field name. An audit against both specs turned up more (below).
What changes
finish_streamingkeeps a turn's rows that hold tool events, so a delivered result or note stays in history after a failed turn. A turn with nothing at all is still dropped.The preamble shows when a page was last inked as a local time (
2026-10-28 20:31), so it stays the same until the page is inked again. Thebook opentool result still shows the age.Responses:
instructionsis every system message joined (the station prompt, open pages, prod), the same as the chat protocol's system prompt. Pages are no longer added again as input items.Responses: a finished item is read from
item. An assistant message is carried into the next round like reasoning, so the model's words before a call stay in the turn. Argument deltas are matched byitem_id.Specs, pinned, in
spec/: OpenAI's OpenAPI (openai/openai-openapi @ 26a2991) cut to the 392 schemas wryme's clients reach, and Open Responses (openresponses/openresponses @ 8907bc3, 2026-04-24) whole.spec/pullfetches them again.Tests held to them:
src/spec.rs(test-only,jsonschemaas a native dev-dependency) checks every fixture event, every chat chunk and every request wryme builds (full and warm windows, tools, history, deliveries) against both specs. Fixtures are built by constructors that check themselves; anything beyond both specs is markedbeyond_spec. The checker's own tests prove it rejectsoutput_item,output_item_id, a missingsequence_numberandsummary: "none".What the audit fixed:
reasoning.summaryisauto/concise/detailed"none"max_tool_callscaps built-in tools only, and is ≥ 1 in Open Responses0at the tool depth limit, so function calls kept comingtool_choice: "none"type: "message"(Open Responses){role, content}store: false, a reasoning item needsencrypted_content, and a server id can't be resolvedresponse.mcp_call_arguments.*mcp_call.arguments.*response.reasoning.deltamcp_call'serroris an objectcall_idcall_idKept on purpose: the Open Responses effort enum lacks
minimalandmax(Swift and Max stations); Chatreasoning_content,multi_agentitems, and function calls the provider ran are extensions beyond both specs.Testing
cargo test: 179 pass, twice. New tests:a_failed_turn_keeps_what_it_delivered(fails without the fix),an_open_page_is_said_the_same_until_inked_again,every_system_message_is_said_once_as_instructions,the_words_said_before_a_call_are_carried_into_the_next_round, andarguments_stream_in_by_their_item_id. Every fixture is now built by constructors checked against the specs.cargo clippy --all-targetsandcargo check --target wasm32-unknown-unknownare clean.Not included