Skip to content

[FEATURE]: Plugin API: seal a session and await its owned jobs #62712

Open

Description

@mradwankhalil

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

Please expose a plugin session operation that closes admissions, cancels and awaits the session's execution and owned background jobs, and preserves its history.

I'm building a plugin that runs workers in separate sessions. After takeover/reload, it must stop an old worker before another worker writes to the same workspace. interrupt followed by wait is not enough: a foreground shell can move to the background through the UI/API, and the session can become idle while its job continues. A tool's after hook can also be skipped on interruption, so a plugin-side handoff flag is not authoritative.

Desired contract; naming is only illustrative:

  • Callable from the plugin session API, idempotently for a session ID.
  • Atomically prevent further prompt/synthetic admissions and job creation/handoff from escaping cleanup, including requests already in flight.
  • Cancel and await the session's active execution and all host-managed owned jobs, including background shells and restored jobs. Define ownership explicitly; don't terminate unrelated user-created child sessions.
  • Return a positive sealed result only after termination. Bound the wait and distinguish timeout, unknown/not-found and unsupported guarantees from success. Preserve the session and its conversation; persist the seal across restart.

This would let orchestration plugins keep the built-in shell instead of reimplementing process management. OS-detached processes outside OpenCode's ownership can remain explicitly out of scope; a job listing alone is not a termination barrier.

Checked against dev at da8c0211f7c00afbf1dd96e629801fe84d205541: plugin session types expose interrupt/wait, not this operation. Core already has internal Job.cancelSession. A deterministic test using the real, unchanged run coordinator and a modeled host-scope job reproduced waitResolved: true, jobRunning: true; this is not a full Job-service integration test.

Please cover handoff racing interruption, late admission, reload/takeover, restored jobs, repeated calls and timeout without falsely reporting success.

Related, not duplicates: #60375 adds job controls/queries; #36348 concerns shell restart semantics; #60491 adds durable job markers. This request is the plugin-reachable sealing/termination barrier across those resources. No implementation PR is proposed without design approval.

Tested upstream

dev da8c021: pinned source audit and unchanged real coordinator witness with modeled host-scope job, exit 0; upstream-session-quiesce-evidence-20261108.md

Activity

  1. opencodejr commented on Nov 8, 2026

    @opencodejr
    Contributor

    Thanks for the detailed write-up! You're asking for a plugin session operation that seals a session. It would stop new prompts and job creation or handoff, cancel and wait for the active run and the background jobs the host owns, keep the session history, and return a bounded result that tells success apart from timeout, not-found and unsupported.

    Right now a plugin can't build this itself. The plugin session API only exposes interrupt and wait, and core's Job.cancelSession isn't available to plugins. Maintainers will consider it.

    Related issues:

  2. mradwankhalil commented on Nov 8, 2026

    @mradwankhalil
    ContributorAuthor

    Thanks—those are useful related threads. To clarify the scope of this request: the missing piece is a plugin-callable, session-scoped retirement barrier, not another general background-job feature.

    The overlap and differences are:

    • v2: shell: interrupting a session doesn't cancel its background shell jobs #60107 / fix(core): cancel a session's background shell jobs on interrupt #60119 — cancelling owned jobs on interrupt. That fixes an important part of cleanup. It does not, by itself, promise to close prompt/synthetic admissions or prevent an already-in-flight request from waking the session after cleanup. An interrupt is normally reusable: the user can send another prompt afterwards. Here the plugin needs to retire that worker identity permanently, while keeping its history, and continue in a new session.

    • [FEATURE]: location "close and drain" so a client can know nothing in a directory can still run #60286 — location-level close and drain. This is the closest semantic match, and sharing its implementation would make sense. The requested boundary here is different: one worker session, callable from plugin code, while other sessions in the same location keep working. A location may contain the user's main chat and several independent runs; draining all of it to replace one worker is too broad. It should also have explicit ownership rules rather than recursively terminating unrelated user-created child sessions.

    • feat(core): move long-running tools to the background after a wait #60375 — job inspection/control and auto-yield. Those controls would be valuable building blocks. But listing jobs, stopping them individually and waiting for a session are separate observations unless the host supplies a common fence. Between those steps, another admission or foreground-to-background handoff can change the resource set. This request is for a positive completion guarantee across that race, not simply more visibility or a job_stop tool.

    • V2: define restart semantics for foreground and background shells #36348 — shell restart semantics. This addresses what happens to foreground/background commands when the server restarts. The plugin also needs this guarantee during ordinary worker replacement or plugin takeover, without restarting the server, and needs the retired session's seal to survive a subsequent restart.

    • refactor(core): store background job markers in a SQLite job table #60491 — durable job markers and deletion cleanup. Persisted ownership/recovery records are helpful, but recording a job is not the same as proving it has terminated. Cleanup via session deletion is also not the requested operation: we need to retain the conversation and prevent future admissions to that same identity.

    A concrete race the contract must rule out is: cleanup finishes for the currently known work; an older prompt/synthetic submission still in preparation is then admitted and wakes the worker; the orchestrator has already released its workspace. Making interrupt cancel more jobs improves the first step, but does not establish that the final wake cannot happen. Likewise, a tool's after hook cannot be the sole authority for background ownership when interruption can skip it.

    The result we need is: after a successful seal, this session cannot start further work and none of its host-owned executions/jobs can still write; timeout, unknown/not-found and unsupported guarantees are not success. History stays readable. OS-detached work outside host ownership can remain explicitly out of scope.

    I'm not asking for parallel implementations of the linked work. If a session-scoped plugin entry point over the close/drain and job-lifecycle machinery can provide this contract, that would satisfy the request and let us keep the standard shell rather than build a replacement.

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