Skip to content

Reconcile a dispatched control operation instead of reporting it as failed #3234

Description

@Astro-Han

Problem

Every Runtime Host control operation reports a connection loss after dispatch as an unknown outcome that must not be retried:

// packages/runtime-host/src/client/connection.ts
this.retryable = mode === 'query';

That is the correct contract — the Host may already have committed and durably flushed the mutation before the response was lost. What is missing is anything a caller can do about it. There is no operation identity on the wire and no reconciliation helper, so every control caller passes the interruption straight through to the user as a failure, and a retry can only produce a conflict.

Desktop has one facility here, isReconnectableReadFailure in apps/desktop/src/main/ipc-reconnect-policy.ts, and it applies to queries only.

Where it shows

Raised in review of #3199 for goal.arm, which is the sharpest case because a committed Goal spends tokens: the dialog says the Goal could not be set, while the Session may in fact be armed, and the user's next ordinary Turn then runs under it. The Goal indicator (useSessionGoal) does show the real state with a one-click clear, so the outcome is visible and reversible, but the message the user was given is wrong.

The same shape applies to the other control operations — goal.control, session continuity, hosted execution, message and turn mutations.

Suggested direction

Two levels, either of which is worth having on its own:

  1. Reconcile after a dispatched interruption. goal.query is a retryable query, so a caller can reconnect, read the canonical state, and compare it against the known pre-state and the request it sent. That needs no protocol change and would let the dialog say "connection lost; the current Goal is X" instead of "the goal could not be set".
  2. Durable operation identity. A per-operation id the Host records with the commit would give the stronger contract — an unambiguous answer to "did my mutation land", rather than an inference from state. This is a public contract change and needs its own architecture decision.

Reviewer's framing on #3199: #3199 (comment)

Not in scope for #3199

Building either of these for goal.arm alone would add a public contract inside a feature PR rather than extend an existing seam, so #3199 keeps the shared contract and this is tracked separately.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions