Skip to content

[FEATURE]: Cancel experimental.idle continuations when the user stops the Session #62050

Open

Description

@andrei-hasna

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Related: #56115 (plugin inbox.cancel and a busy snapshot), #58776 (conditional admission with an expected-turn guard, closed by its author), #60461 / #54487 (experimental.stopping), #61884 (execution continuity). References are to dev at 893e8dc43f05e872d4cac603486ce8a7181170fa, from reading the source; the repro below has not been executed.

Problem

A plugin can use experimental.idle as a completion reviewer: after the model's last step it decides, often with a model call, whether to continue, and admits ctx.session.synthetic(...) to keep the same execution going. The built-in /goal plugin works this way.

If the user stops the Session after the continuation is admitted but before it is promoted, the continuation survives the stop:

  • SessionRunCoordinator.interrupt (packages/core/src/session/run-coordinator.ts) interrupts the execution fiber and claims its recorded wakes.
  • Settlement of a non-shutdown interrupt runs jobs.cancel (packages/core/src/session/execution.ts), but pending inbox rows stay pending.
  • With interrupt({ resume: true }) the continuation is promoted on the resume wake; without it, it is promoted with the user's next prompt.

inbox.cancel from #56115 would let a plugin retract the row, but a plugin reacting to the interruption races the resume wake. Promise-API session hooks also get no AbortSignal (packages/plugin/src/promise/adapter.ts, host.session.hook wraps the callback in Effect.promise(() => ...)), so a promise handler can still call synthetic after the stop.

Proposal

An opt-in synthetic option, scope: "execution". The runner records the inbox ids admitted with it during an execution. When that execution ends by a non-shutdown interrupt, settlement cancels those rows that are still pending, through the existing SessionInbox.cancel and its InboxCancelled event, before any resume wake reads the inbox. The default, "session", keeps today's behaviour, and a shutdown interrupt is unchanged, since restart continuity is intended there.

This mirrors how interrupt already claims the interrupted intent's wakes.

Smaller, related:

  • Promise-API session hooks could receive an AbortSignal that is aborted when the execution is interrupted.
  • A user prompt sent while the reviewer runs is promoted beside its continuation, because pending steers are published together (SessionInbox.promote, packages/core/src/session/inbox.ts). The conditional admission asked for in [FEATURE]: conditional steering admission with an expected-turn guard #58776 would close that race too. Note that SessionInbox.admit does not take the per-Session lock (SessionInbox.serialized) that promote holds, so such a check would need admission to take it.

Repro sketch (not executed)

In the style of packages/core/test/session-runner.test.ts:

  1. Register an experimental.idle handler that waits on a deferred, then calls ctx.session.synthetic({ sessionID, text: "continue" }).
  2. Prompt the Session and let the model finish one step, so the handler blocks.
  3. Resolve the deferred, then call interrupt({ sessionID, resume: true }) before the runner promotes the row.
  4. Observe that "continue" is promoted and a model step runs after the stop.

Expected with scope: "execution": the row is cancelled and no step runs.

Activity

  1. opencodejr commented on Nov 5, 2026

    @opencodejr

    Thanks for the detailed write-up and the code references!

    To restate the request: you'd like an opt-in scope: "execution" option on ctx.session.synthetic. A continuation admitted by an experimental.idle hook (for example a /goal-style reviewer) would then be cancelled if the user stops the Session before it's promoted, including with resume: true. Shutdown interrupts and the default "session" scope would keep today's behaviour. You also suggest an AbortSignal for promise-API session hooks, so a handler can tell that the execution was stopped.

    This can't really be done in a plugin today. Reacting to the interrupt races the resume wake, and promise hooks don't see the stop. So it would need support in core or in the plugin API. Maintainers will take a look and consider it.

    Related issues:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions