feat(protocol)!: align the SDK to the supported protocol scope - #1
Merged
Conversation
a7vinx
force-pushed
the
feat/protocol-scope
branch
from
August 8, 2026 13:49
63ae2cd to
9982abc
Compare
a7vinx
force-pushed
the
feat/protocol-scope
branch
2 times, most recently
from
August 8, 2026 14:58
858256f to
218c39e
Compare
The SDK modelled 33 server-to-client events selected from roughly 60 the backend emits, with nothing marking which of them carry a compatibility guarantee. The supported scope names 19, and the two sets were not in a containment relationship. ## Protocol surface The modelled surface is now the supported scope and nothing else. Four supported events were absent and are added, with models: `session:llm_thinking`, `session:tool_status`, `session:required_action` and `session:restriction`. Between them they carry what a task is doing and why it stopped. The reasoning stream previously ran on `session:work_log`, which is outside the scope and is a distinct event from `session:llm_thinking` rather than a former name for it. Events outside the scope are unmodelled but still delivered, unchanged and in order. `is_supported_event()` distinguishes the two surfaces and `emit_event()` sends an unmodelled event. This follows the scope's own position: tolerating an unsupported event is required, depending on one is not. ## Requirements Three requirements the scope states as MUST were unmet: - **Recovery.** `session:join` now carries `since_revision` `"0"` and ignores the incremental-synchronisation fields. `rebuild()` pages through history until the cursor is exhausted, on every join and every reconnect. A short or empty page does not indicate exhaustion. - **Deduplication.** Events are keyed on the event identifier together with the message type. Identifiers collide across types, so keying on the identifier alone discards valid events. - **Blocking conditions.** `InputState` exposes the reason from `session:input_state`, including the two conditions the scope calls out. Turn control no longer depends on unsupported events. It previously hinged on `session:ask_for_location`, `session:interactive_auth_confirmation`, `session:three_way_call` and `session:reward`. ## Tests Three layers. `tests/protocol/fixtures` holds one envelope per supported event, with provenance recorded — 14 captured from live sessions, 4 derived from the protocol definition for conditions an ordinary session does not reach. `test_contract.py` validates one envelope at a time. `test_flow.py` drives sequences through the real transport and pins each MUST by name, including that an unrecognised event arrives verbatim without disturbing ordering. `tests/integration` was two overlapping copies of one live script, one of which asserted behaviour the SDK had already dropped. It is now a single suite that also serves as the fixture recorder. It places one call to a number in the range NANP reserves for fiction, which is what makes `session:task_ready`, `session:tool_status` and `session:task_finished` observable. It stays out of CI: it requires a token and consumes credits. CI gains a lint step. `ruff` was configured but never invoked. ## Fixed Sessions joined through `join_session()` were never re-joined after a reconnect. Membership was tracked on the fire-and-forget emit path, while joining is a request/response call. ## Breaking `send_auth_confirmation()`, `send_location_response()` and `send_location_selection()` are removed, along with `NotificationEvent`, the `session:reward` and `session:payment` models, the unsupported event constants, the `action` argument on `chat()`, and `request_work_log` on `get_history()`. Each remains reachable through `emit_event()`. `pine-mcp-server` calls the three removed methods and depends on `pine-assistant>=0.3.2`. It requires a corresponding change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a7vinx
force-pushed
the
feat/protocol-scope
branch
from
August 8, 2026 15:04
218c39e to
329b16d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The SDK modelled 33 server-to-client events selected from roughly 60 the backend emits, with nothing marking which of them carry a compatibility guarantee. The supported protocol scope names 19, and the two sets were not in a containment relationship.
This aligns the modelled surface with that scope, implements the requirements the scope states as MUST, and adds a test layer that pins both.
Protocol surface
Four supported events were absent and are added, with models:
session:llm_thinkingsession:tool_statussession:required_actionsession:restrictionThe reasoning stream previously ran on
session:work_log, which is outside the scope. It is a distinct event fromsession:llm_thinking, not a former name for it.Events outside the scope are unmodelled but still delivered, unchanged and in order.
is_supported_event()distinguishes the two surfaces;emit_event()sends an unmodelled event. This follows the scope's own position: tolerating an unsupported event is required, depending on one is not.Requirements implemented
Recovery.
session:joincarriessince_revision"0"and ignores the incremental-synchronisation fields.rebuild()pages through history until the cursor is exhausted, on every join and every reconnect. A short or empty page does not indicate exhaustion.Deduplication. Events are keyed on the event identifier together with the message type. Identifiers collide across types, so keying on the identifier alone discards valid events.
Blocking conditions.
InputStateexposes the reason fromsession:input_state, including insufficient credits and outstanding phone verification.Turn control no longer depends on unsupported events. It previously hinged on
session:ask_for_location,session:interactive_auth_confirmation,session:three_way_callandsession:reward.Tests
Three layers, separated because they fail for different reasons.
tests/protocol/fixturesholds one envelope per supported event, with provenance recorded: 14 captured from live sessions, 4 derived from the protocol definition for conditions an ordinary session does not reach.test_contract.pyvalidates one envelope at a time — a failure means a payload shape moved.test_flow.pydrives sequences through the real transport and pins each MUST by name, including that an unrecognised event arrives verbatim without disturbing ordering.tests/integrationwas two overlapping copies of one live script, one of which asserted behaviour the SDK had already dropped. It is now a single suite that also serves as the fixture recorder. It places one call to a number in the range NANP reserves for fiction, which is what makessession:task_ready,session:tool_statusandsession:task_finishedobservable. It remains outside CI: it requires a token and consumes credits.CI gains a lint step.
ruffwas configured but never invoked.Fixed
Sessions joined through
join_session()were never re-joined after a reconnect. Membership was tracked on the fire-and-forget emit path, while joining is a request/response call.Breaking changes
Removed:
send_auth_confirmation(),send_location_response(),send_location_selection(),NotificationEvent, thesession:rewardandsession:paymentmodels, the unsupported event constants, theactionargument onchat(), andrequest_work_logonget_history(). Each remains reachable throughemit_event().pine-mcp-servercalls the three removed methods and declarespine-assistant>=0.3.2. It requires a corresponding change before this is released.Verification
ruff checkclean.python -m buildandtwine checkpass. CI green on Python 3.10–3.13.session:error,session:message,session:restrictionandsession:rich_content. Nothing in an ordinary session reaches an account restriction or produces a rich content document.🤖 Generated with Claude Code