microsoft/autogen · error · RuntimeError

Host runtime is not started.

Error message

Host runtime is not started.

What it means

GrpcWorkerAgentRuntimeHost.stop() tears the server down (grpc stop with grace, cancel of the serve task) but only if a serve task exists: when self._serve_task is None it raises RuntimeError('Host runtime is not started.'). This catches stopping a host that was never started or was already stopped (stop() resets _serve_task to None, so a second stop() also raises).

Source

Thrown at python/packages/autogen-ext/src/autogen_ext/runtimes/grpc/_worker_runtime_host.py:42

        self._server.add_insecure_port(address)
        self._address = address
        self._serve_task: asyncio.Task[None] | None = None

    async def _serve(self) -> None:
        await self._server.start()
        logger.info(f"Server started at {self._address}.")
        await self._server.wait_for_termination()

    def start(self) -> None:
        """Start the server in a background task."""
        if self._serve_task is not None:
            raise RuntimeError("Host runtime is already started.")
        self._serve_task = asyncio.create_task(self._serve())

    async def stop(self, grace: int = 5) -> None:
        """Stop the server."""
        if self._serve_task is None:
            raise RuntimeError("Host runtime is not started.")
        await self._server.stop(grace=grace)
        self._serve_task.cancel()
        try:
            await self._serve_task
        except asyncio.CancelledError:
            pass
        logger.info("Server stopped.")
        self._serve_task = None

    async def stop_when_signal(
        self, grace: int = 5, signals: Sequence[signal.Signals] = (signal.SIGTERM, signal.SIGINT)
    ) -> None:
        """Stop the server when a signal is received."""
        if self._serve_task is None:
            raise RuntimeError("Host runtime is not started.")
        # Set up signal handling for graceful shutdown.
        loop = asyncio.get_running_loop()
        shutdown_event = asyncio.Event()

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Only call stop() after a successful start(); track a started flag and clear it after stopping
  2. Make teardown idempotent: catch RuntimeError from stop() and ignore it when the intent is 'ensure stopped'
  3. Sequence restarts as start -> stop -> start, never stop on a never-started or already-stopped host
  4. In signal handlers, coordinate with manual shutdown via an asyncio.Event so stop() runs once

Example fix

# before
await host.stop()  # RuntimeError: never started (or already stopped)

# after
started = False
host.start(); started = True
# ... teardown:
if started:
    await host.stop()
    started = False
# or simply:
try:
    await host.stop()
except RuntimeError:
    pass
Defensive patterns

Strategy: try-catch

Validate before calling

started = False
host.start(); started = True
# ... later:
if started:
    await host.stop(); started = False

Try / catch

try:
    await host.stop()
except RuntimeError as e:
    if 'not started' in str(e):
        pass  # never started or already stopped — nothing to do
    else:
        raise

Prevention

When it happens

Trigger: Calling await host.stop() before any host.start(); calling stop() twice; calling stop() after a previous stop() completed (the flag was cleared); cleanup code in finally blocks after startup failed early.

Common situations: Symmetric cleanup in try/finally where start() failed so the task never existed; double teardown in tests (fixture finalizer plus explicit stop); signal handlers racing with manual shutdown code both calling stop().

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/9865f12f531571f2. Report an issue: GitHub.