Skip to content

Coordinate loop scheduling across OpenCode plugin instances - #2

Open
Romnet wants to merge 1 commit into
prevalentWare:mainfrom
Romnet:fix/loop-scheduler-ownership
Open

Coordinate loop scheduling across OpenCode plugin instances#2
Romnet wants to merge 1 commit into
prevalentWare:mainfrom
Romnet:fix/loop-scheduler-ownership

Conversation

@Romnet

@Romnet Romnet commented Sep 8, 2026

Copy link
Copy Markdown

Summary

Coordinate persisted loop scheduling across multiple OpenCode plugin instances.

Loops are stored in shared state. Previously, every plugin instance rehydrated all active loops on startup,
including loops belonging to sessions that the instance had never observed. A scheduler could therefore resume
a loop from another session while list_loops in the current session showed nothing.

This PR adds session ownership leases, owner-aware run claims, and cross-process state coordination. Rehydrated
loops remain dormant until an event, hook, or tool call establishes that the plugin instance has observed the
corresponding session.

The existing public loop tool APIs remain unchanged.

Changes

Session ownership

  • Added per-session ownership leases with heartbeat renewal.
  • Allow only the current lease holder to schedule loops for a session.
  • Require an authoritative session observation before rehydrated loops can run.
  • Cancel local timers and pending dynamic runs when session ownership is lost.
  • Release session leases during plugin cleanup.
  • Applied the same ownership behavior to the V1 and V2 plugin APIs.

Run coordination

  • Added owner revisions and per-run claim tokens.
  • Claim due runs only while the scheduler-owner lease is current.
  • Validate session ownership and the run claim immediately before prompt delivery.
  • Reject stale state transitions after ownership changes.
  • Prevent stale owners from recording a run as sent, failed, deferred, or stopped.
  • Prevent non-owner instances from cleaning up another session's dynamic loops.

Shared state

  • Serialized state mutations across plugin processes with an atomic filesystem lock.
  • Made conditional state updates operate against one consistent state snapshot.
  • Preserved compatibility with existing state files by providing defaults for new fields.

Failure handling

  • Added exponential retry backoff for provider and model failures.
  • Added a configurable maximum retry delay.
  • Pause loops after a configurable number of consecutive failures.
  • Reset failure state after a successful run or explicit resume.
  • Persist only a safe generic failure category instead of provider responses or prompt details.

Documentation and distribution

  • Updated the README with ownership and failure-recovery behavior.
  • Rebuilt and committed dist/server.js.

Concurrency guarantees

The shared-state lock makes lease acquisition, run claiming, and conditional state transitions atomic across
cooperating plugin processes.

Owner revisions and run claim tokens fence persisted state updates. After ownership changes, a stale process
cannot record its run as sent or failed, reschedule it, or stop it.

The scheduler also checks its session lease and run claim immediately before submitting a prompt. This prevents
normal concurrent rehydration and scheduling from producing duplicate sends.

Known limitation

Prompt delivery itself remains a check-then-send operation. The current OpenCode prompt API does not expose a
receiver-side lease token, expected ownership epoch, or equivalent compare-and-set condition.

A process that is paused after the final claim validation, remains paused until its lease expires, and then
resumes could theoretically submit a stale prompt after another process has acquired ownership. The stale process
would be unable to commit any subsequent loop-state update, but the external prompt submission could already have
occurred.

Therefore, this PR provides cross-process coordination and fencing for persisted loop state, but it does not claim
strict exactly-once prompt delivery or receiver-side fencing. Closing that final race requires support from the
OpenCode session API.

Verification

bun test
60 pass, 0 fail

bun run lint
passed

bun run typecheck
passed

bun run build
passed

bun run pack:dry-run
passed

Checklist

  • Regression coverage added for session ownership and concurrent plugin contexts
  • Cross-process state mutation and lease acquisition covered
  • V1 and V2 behavior covered
  • bun test passes
  • bun run lint passes
  • bun run typecheck passes
  • bun run build passes
  • dist/server.js rebuilt and committed
  • README updated for changed behavior and recovery rules
  • Remaining prompt-delivery race documented

@Romnet
Romnet force-pushed the fix/loop-scheduler-ownership branch from 1f508a4 to ca19b35 Compare September 8, 2026 20:20
@Romnet
Romnet force-pushed the fix/loop-scheduler-ownership branch from ca19b35 to 99b0ef9 Compare September 8, 2026 20:23
@Romnet Romnet changed the title Prevent loops from running in the wrong OpenCode session Coordinate loop scheduling across OpenCode plugin instances Sep 8, 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