microsoft/semantic-kernel · warning · RuntimeError

The invocation has already been completed.

Error message

The invocation has already been completed.

What it means

OrchestrationResult.cancel() refuses to cancel after the result event is already set, i.e. the invocation has already completed (success, failure, or prior cancel). Cancel-after-complete is a no-op at best and a logic error, so it raises.

Source

Thrown at python/semantic_kernel/agents/orchestration/orchestration_base.py:78

        if self.value is None:
            if self.cancellation_token.is_cancelled():
                raise RuntimeError("The invocation was canceled before it could complete.")
            if self.exception is not None:
                raise self.exception
            raise RuntimeError("The invocation did not produce a result.")
        return self.value

    def cancel(self) -> None:
        """Cancel the invocation.

        This method will cancel the invocation.
        Actors that have received messages will continue to process them, but no new messages will be processed.
        """
        if self.cancellation_token.is_cancelled():
            raise RuntimeError("The invocation has already been canceled.")
        if self.event.is_set():
            raise RuntimeError("The invocation has already been completed.")

        self.cancellation_token.cancel()
        self.event.set()


@experimental
class OrchestrationBase(ABC, Generic[TIn, TOut]):
    """Base class for multi-agent orchestration."""

    t_in: type[TIn] | None = None
    t_out: type[TOut] | None = None

    def __init__(
        self,
        members: list[Agent],
        name: str | None = None,
        description: str | None = None,
        input_transform: Callable[[TIn], Awaitable[DefaultTypeAlias] | DefaultTypeAlias] | None = None,

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Only cancel when the run is still in flight: check `if not result.event.is_set(): result.cancel()`.
  2. Move cancellation out of the success/completion path; only cancel on actual abort/timeout.
  3. Await get() with a timeout and cancel only if get() raised TimeoutError.

Example fix

// before
value = await result.get()
result.cancel()  # raises 'already been completed'

// after
try:
    value = await result.get(timeout=30)
except asyncio.TimeoutError:
    if not result.event.is_set():
        result.cancel()
Defensive patterns

Strategy: validation

Validate before calling

# Only cancel while the run is still in flight
if not result.event.is_set():
    result.cancel()

Type guard

def result_still_running(result) -> bool:
    return not result.event.is_set()

Prevention

When it happens

Trigger: Calling `result.cancel()` after the orchestration already finished and set the event — e.g. after `await result.get()` returned, or after a prior cancel()/completion.

Common situations: A finally block that cancels unconditionally even on the success path. A timeout handler firing after the run already completed. Cancel wired into both success and error cleanup.

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/cd8687c4709cccc9. Report an issue: GitHub.