Skip to content

ExecutionProvider: declare supported runtimes, execute synchronously - #104

Merged
udaychandra merged 14 commits into
mainfrom
agent/v3-simplify-execution
Oct 22, 2026
Merged

udaychandra merged 14 commits into
mainfrom
agent/v3-simplify-execution

Conversation

@udaychandra

@udaychandra udaychandra commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Spec: v3-simplify-execution. ExecutionProvider declares its supported runtimes, and execute runs synchronously.

What changes

  • Runtimes: ExecutionCapabilities (the record and its builder) is deleted. ExecutionProvider.supportedRuntimes() replaces capabilities(). ExecuteArguments still refuses any other runtime before it builds a request, with the same message.
  • The execute contract: execute returns ExecutionResult and runs on the tool call's own thread. Its javadoc states the contract:
    • on an interrupt or a cancelled token it stops and throws CancellationException, keeping the interrupt status;
    • it waits on its own futures with Future.get, never join;
    • it throws a RuntimeException only when the provider itself is broken.
  • ExecuteTool.dispatch calls the provider directly:
    • CancellationException → Execute: cancelled;
    • any other RuntimeException → Execute: provider failed: <message or simple name>;
    • an Error propagates as itself and ends the session through the tool worker.
  • LocalProcessExecutionProvider:
    • No extra thread. Its runtimes are the key set of an unmodifiable EnumMap, so they iterate in Runtime declaration order.
    • maxTimeout is a private field that only the clamp reads.
    • withNetworkAllowed / withFilesystemWriteAllowed are removed.
    • An interrupted execute now SIGKILLs the child and its descendants. Before, the provider's own thread kept waiting and the timeout escalated to SIGKILL. With execute on the tool thread, the interrupt is the session's only stop, so SIGTERM alone would have left a TERM-ignoring child running and untracked.
  • JShellExecutionProvider:
    • SessionExecution.run runs the snippet on the caller's thread.
    • An interrupt now closes the session's sandbox and throws CancellationException, on every exit path. That includes a sandbox that answers the interrupt with its own exception, which was Sail's round-1 finding.
    • A kill the token fires after the call is over leaves the session open.
    • The two flags are removed; the javadoc points to JvmSandboxConfig.Builder.withSandboxPolicy / SandboxPolicy.noEgress().
  • NoopExecutionProvider: it supports no runtimes and returns its refusal directly.
  • Docs:
    • CLAUDE.md has a new ExecutionProvider row. The provider-thread exception is gone from its "Loop crash semantics" row, which also no longer lists ExecuteTool as a waiting path.
    • The provider-thread exception is also gone from SessionEnding's javadoc and from the 3.0 CHANGELOG paragraph.
    • The execution package-info now says requests run on the calling thread.
    • CHANGELOG 3.0 Breaking has four new rows and an in-place fix to the declaration-order note.

Tests

  • New shared fixture: session.test.ExecutionCall. It runs execute on a virtual thread of its own and records the interrupt status at the end. It is shared with repl through the session test-jar.
  • Acceptance tests 1 to 9 are covered:
    • Unsupported runtime: zero provider calls.
    • Local:
      • Interrupt: the child is killed.
      • Cancel, including during the permit wait.
      • Interrupt during the permit wait: the interrupt status is kept.
    • JShell: interrupt, interrupt answered by a sandbox exception, cancel, and a late kill.
    • ExecuteTool: every outcome mapping.
    • Through a session:
      • A tool timeout ends TimedOut, and inflightCount() is 0.
      • A provider Error fails the result as the same instance.
  • Added in the pre-merge review:
    • interruptKillsAChildThatIgnoresSigterm
    • interruptKillsTheChildsDescendants
    • interruptWhilePermitWaitThrowsCancellationAndKeepsTheInterrupt
    • executesOnASharedTokenLeaveNoCallbackOnIt
    • anErrorTheProviderThrowsFailsTheResultAsItself
    • a strengthened unmodifiable-runtimes check
  • Deleted:
    • ExecutionCapabilitiesTest;
    • the flag assertions in both builderWithAllOptions;
    • supportedRuntimesIsStable, which compared a constant with itself.
  • Coverage: all 48 changed executable lines are covered.
  • Mutants: 46 of 51 hand mutants killed. The five survivors:
    • two > → >= clamp mutants, which behave identically;
    • a pre-existing race-only guard;
    • two lines that had no effect, one of them now deleted.
  • Flakiness: about 18,000 repetitions of the interrupt and cancel tests, some under full CPU load, had 0 failures.

mvn -B clean verify javadoc:jar: BUILD SUCCESS, 7863 tests run, 0 failures, 0 errors, 62 skipped (assumption-gated and live-key tests).

Recorded, not changed (theoretical)

  • Local provider: an interrupt that lands after the child exited returns the result with the interrupt flag set, instead of throwing. A session never sees this: DispatchedCall.cancel settles the answer before it interrupts.
  • JShell: a close that runs on an interrupted thread skips its exit waits. In a session the token's callback thread normally does the close.

Size

Net main-code lines (excluding tests and docs): −286 (+173 / −459): session −236, repl −50. The spec budgeted about −260.

🤖 Generated with Claude Code

udaychandra and others added 14 commits October 22, 2026 02:24
…nously

ExecutionCapabilities and the unenforced network and filesystem flags
go: a provider declares supportedRuntimes() and keeps its own timeout
clamp. execute runs on the tool call's thread and must stop, throwing
CancellationException with the interrupt status kept, when that thread
is interrupted or the call's token fires. An interrupted JShell execute
now closes its sandbox session.
…inert

ReplSession.close is idempotent, so the interrupt path closes the session
directly instead of running the callback through its compare-and-set; the
flag now only keeps a kill that fires after the call open, which the one
remaining comment says.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The handlers are held in an unmodifiable EnumMap, whose key set is the
supported runtimes in Runtime declaration order, so the second field that
copied them goes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
It compared Set.of(JSHELL) with itself, which supportsJShellOnly already
pins; the section heading no longer names the deleted capabilities.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
… CHANGELOG paragraphs

ExecuteTool no longer waits on a provider future: a provider's Error now
leaves through the tool worker like any tool's, which the row already
covers. The two 3.0 paragraphs edited in place had lines past 100
columns.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The tool call's interrupt is now how a session stops a running
execute, and execute untracks the child before it throws. Its SIGTERM
left a child that ignores TERM running unbounded and out of reach of
close() and the shutdown hook; before, the provider's own thread kept
waiting and the timeout escalated to SIGKILL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Guarantee 5 of the synchronous execute was covered only at the tool
level; this drives it through a session, where the tool worker records
the provider's Error and the result fails with the same instance.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A key set refuses add whether or not its map is modifiable, so only a removal proves the set is unmodifiable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The token's registration is removed a few lines later, and a SIGTERM
the token sends to an already SIGKILLed child does nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@udaychandra
udaychandra merged commit df41608 into main Oct 22, 2026
2 checks passed
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