Skip to content

[FEATURE]: [V2] experimental.stopping hook (pre-Complete veto with budget) #60461

Open

Description

@jwitmann

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

As suggested by the bot, I'm opening a V2 issue — I was in #16626, carrying this over since that closed when the bot treated it as V1-only and ignored the follow-ups — including #54487. This is V2-only (dev branch). I've got that PR open with a working implementation.

description

experimental.idle fires after Complete() would return, with only { sessionID, background } — no vote, no bound. A plugin that wants to keep a session alive has to self-bound its loop.

Want experimental.stopping fired before Complete() in packages/core/src/session/runner/llm.ts (maybeContinueStopping), with { sessionID, step, stepsLimit, continuations, decision }, vote shouldContinue + text, capped at 2 continuations/turn.

Types: packages/plugin/src/effect/session.ts + promise mirror. Example consumer contrast: packages/core/src/plugin/goal.ts (uses experimental.idle today).

What it's for

pre-terminal veto, not idle notification. Not a replacement for packages/core/src/plugin/goal.ts: that owns the multi-round objective loop via experimental.idle. stopping is the bounded single-turn gate idle can't express:

  • sql check before success: validate the SQL the turn produced, on failure force one more step with a short fix instruction. Needs a bound so an always-fail validator can't loop, and default-allow-stop when checks pass. idle is too late because Execution.Succeeded is already published.
  • typecheck before end: run check-types and inject errors so the agent fixes them in the same session. idle re-prompt shows as a visible user message instead of a silent continuation.
  • verified-claims gate: scan the final message for high-risk claims and re-enter once with a correction. Emulated today via idle → messages() → prompt(), but idle also fires on abort, the correction pollutes the transcript, and the user can act on the answer during the round-trip.
  • opencode-dev-framework (my plugin, downstream waiter): completion gate runs today on experimental.idle → session.synthetic, bounded by gate.max_blocks, plus a deduped event.subscribe net for the boundaries the hook skips. With stopping the gate becomes a vote with runner-owned budget instead of self-accounting + net.

Without this, every gate re-implements its own bound on top of idle, after the runner already decided to stop.

Activity

  1. opencodejr commented on Oct 30, 2026

    @opencodejr

    Thanks for carrying this over to v2 and for the detailed use cases.

    In short, you're asking for an experimental.stopping session hook that fires before Complete(). A plugin could vote to keep going (shouldContinue plus steer text), and the runner would enforce a limit on continuations per turn. That would replace gates built on experimental.idle, which only fires after the runner has already decided to stop and which can't vote or see a budget.

    I checked dev. experimental.idle ({ sessionID, background }) is currently the only hook of this kind, so a plugin can't do this today. It needs the new hook in the plugin API. I've passed this to the maintainers to consider as a feature request.

    Related:

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