Skip to content

πŸ¦‹ New version release - #553

Merged
brentrager merged 1 commit into
mainfrom
changeset-release/main
Aug 26, 2026
Merged

brentrager merged 1 commit into
mainfrom
changeset-release/main

Conversation

@brentrager

@brentrager brentrager commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@smooai/smooth-operator@1.58.6

Patch Changes

  • 512cb01: fix(server): a prod failure is diagnosable from logs alone (th-694c22)

    A live "session not found" incident produced ZERO server log lines β€” chat-ws
    emitted one line in six hours of continuous traffic, because the server's
    decision points were silent: every one of the ~30 protocol::error(...) emit
    sites sent the client-visible error frame without logging, and the session
    read-through, confirmation/interaction parks and resolves, OTP verification,
    and turn starts logged nothing at info.

    One warn at the single error-frame construction site now covers every
    client-visible failure, present and future (the frame's human text rides as
    detail β€” message is tracing's reserved event-message field). Info lines
    land at the decision points an incident responder actually needs: session
    primed from storage (the cross-pod resume working as designed), turn requested
    (session + requestId), confirmation parked / live-resolved / durably resolved,
    interaction parked / resolved-with-values, and OTP verified. No debug spam β€”
    one line per event that changes state.

  • e0d4213: fix(server): a pending Rich Interaction survives the pod that parked it (th-db0816)

    The interaction sibling of the durable-confirmation fix. A raise's park is a
    channel into a turn on ONE pod, so a visitor whose refresh reconnected them to
    another replica β€” or whose pod rolled β€” got NO_PENDING_INTERACTION for the
    card they were just shown, and the identity they typed evaporated.

    The interaction bridge now persists every raise into the session's durable
    metadata.pendingInteraction (interaction id + kind + spec β€” the full
    validation contract), retired when the turn ends. submit_interaction with no
    live park validates against that record exactly as it would against the park
    (mismatched interactionId still rejected, per-field validation still routed
    to the kind's server-side validator, invalid submits leave the record for a
    retry) and resolves it there: submitted values retire the record fail-closed
    and then run the kind's host effect; declined retires it with an ack. No
    continuation turn β€” the host effect is the durable outcome, and a dead raise's
    model acknowledgment is forgone rather than fabricated.

    attach_session_identity now also writes through to storage: a captured
    contact (name/email/phone) used to live only in one pod's map, so any pod roll
    forgot a visitor who had just introduced themselves β€” even on the same pod.

    Two-instance tests (tests/durable_interactions.rs) drive the real
    handle_frame on two AppStates over one storage adapter: submit-on-the-other-
    pod attaches the identity durably and retires the record, decline retires it,
    mismatched ids are still rejected, and the negative control proves
    NO_PENDING_INTERACTION is still reachable with no record. Positive control:
    with the durable read disabled, both cross-instance tests fail reproducing the
    production error while the negative control still passes.

    No wire-protocol change.

  • 2904a8a: feat(server): a host can install the turn's AgentExecutor β€” the missing seam on the emitted reply

    TurnRequest::executor has been a public field since ADR-030, and
    runner::turn_executor has always honored it, but the server's sole
    TurnRequest construction site (handler.rs) hardcoded executor: None. So the
    seam existed on paper and was unreachable in practice: nothing outside this crate
    could supply one.

    That gap is what left chat-ws with no host-side seam on the emitted text. When the
    runner owns the whole turn and streams plain text from inside the published crate,
    a host has no point at which to inspect what the agent said next to what it
    actually did β€” so the TS general agent's post-response guard (which STRIPPED an
    escalation claim when notify_humans had not fired) and the voice stall-reply
    retry had nowhere to run. The consequence was live: an agent could tell a customer
    "I've passed it along" with nothing behind it, and the only available fix was
    prompt prevention, not enforcement.

    AppState::with_executor installs one, and the handler passes it onto every turn.
    Two things arrive through it:

    • a durable backend (ADR-030) β€” the case the trait was written for; and
    • a decorator: an executor that delegates to InProcessExecutor and then
      inspects or edits the returned Conversation before the runner reads its final
      assistant message. Conversation.messages is public and carries the turn's tool
      calls, so this is the one place a host can guard a reply against the tools that
      actually ran.

    One boundary is worth stating plainly rather than discovering later: tokens the
    turn streamed have already left over the events channel by the time the
    conversation is returned, so an edit here changes the persisted message and the
    eventual_response β€” not what already streamed. A decorator that needs the stream
    too can pass its own channel down and forward.

    Default behavior is unchanged: None is still the in-process executor, which is a
    verbatim delegation to Agent::run_with_channel. The lambda flavor keeps None
    alongside its other None injection seams (it has no AppState to install one
    on).

    rust/smooth-operator-server/tests/executor_seam.rs drives the real
    handle_frame offline with an escalation-guard executor and pins both halves: the
    installed executor is the one that runs the turn and its rewrite reaches BOTH the
    persisted outbound message and the eventual_response; with no executor installed
    the model's text survives byte-for-byte.

@smooai/smooth-operator-web-chat-example@0.0.119

Patch Changes

  • Updated dependencies [512cb01]
  • Updated dependencies [e0d4213]
  • Updated dependencies [2904a8a]
    • @smooai/smooth-operator@1.58.6

@brentrager
brentrager force-pushed the changeset-release/main branch from ae76e05 to e3bd531 Compare August 26, 2026 00:32
@brentrager
brentrager force-pushed the changeset-release/main branch from e3bd531 to de806a0 Compare August 26, 2026 00:55
@brentrager
brentrager merged commit 11e604b into main Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant