Skip to content

Move agent lazy initialization from send_message() to server startup #2588

Closed

Description

@simonrosenberg

Problem

The agent-server's /health endpoint returns "OK" unconditionally (just checks the process is alive), but the actual agent initialization — plugin loading, ACP subprocess startup, protocol handshake, session creation — happens lazily on the first send_message() call via _ensure_agent_ready().

This creates a timing mismatch: Kubernetes marks the pod as ready (health check passes), the client connects and sends a message, but the server blocks for 1-5+ minutes doing initialization while the HTTP response hangs. If the client has a shorter timeout (e.g. 60s default api_timeout), the request fails with httpx.ReadTimeout.

Call chain on first send_message()

POST /api/conversations/{id}/events
  → event_service.send_message()                    # event_service.py:308-312
    → run_in_executor(conversation.send_message)     # blocks HTTP response
      → _ensure_agent_ready()                        # local_conversation.py:435-481
        → _ensure_plugins_loaded()                   # network I/O for plugins
        → agent.init_state()                         
          → ACPAgent._start_acp_server()             # acp_agent.py:568-677
            → subprocess_exec(claude-code-acp, ...)  # spawns ACP subprocess
            → conn.initialize()                      # protocol handshake
            → conn.new_session(cwd=working_dir)      # session creation (slow for large repos)
            → conn.set_session_mode()                # permission setup

Impact

In swebenchmultimodal evaluation runs on 2026-03-26, this caused 100% failure rates for large-image instances even at resource_factor=8:

Repo Instances failed Failure rate
Automattic/wp-calypso 37 100%
processing/p5.js 16 100%
markedjs/marked 11 100%

Eval job references:

Datadog log query to reproduce:

kube_job:eval-23598243168-claude-son "failed after 3 retries" "read operation timed out"

Proposed fix

  1. Move _ensure_agent_ready() (or its heavy parts) to run during server startup in api_lifespan(), before mark_initialization_complete() is called
  2. The /ready endpoint should only return 200 after the conversation is actually ready to handle messages, not just after the process starts
  3. Consider adding a /warm-up endpoint that pre-initializes the agent for a given configuration, so the readiness probe can gate on actual readiness

Workaround

Increasing api_timeout on the client side (OpenHands/benchmarks#579). This is a band-aid — the server should be ready before accepting traffic.

Related code

  • /health endpoint: openhands/agent_server/server_details_router.py:73-76 — always returns "OK"
  • Lazy init: openhands/sdk/conversation/impl/local_conversation.py:435-481 — _ensure_agent_ready()
  • ACP startup: openhands/sdk/agent/acp_agent.py:568-677 — _start_acp_server()
  • Server lifespan: openhands/agent_server/api.py:48-142 — mark_initialization_complete() called before conversations are ready

Activity

  1. enyst commented on Mar 26, 2026

    @enyst
    Member

    The proposal

    • Move _ensure_agent_ready() (or its heavy parts) to run during server startup in api_lifespan(), before mark_initialization_complete() is called
    • The /ready endpoint should only return 200 after the conversation is actually ready to handle messages, not just after the process starts
    • Consider adding a /warm-up endpoint that pre-initializes the agent for a given configuration, so the readiness probe can gate on actual readiness

    is somehow in contradiction with #2523

    I think maybe 2523 is on the right track, but it seems to imply that the server startup is even more decoupled from the rest of workspace initialization than today.

  2. VascoSch92 commented on Apr 7, 2026

    @VascoSch92
    Member

    @simonrosenberg

    Should we move this issue into the Benchmark repo?

  3. github-actions commented on May 18, 2026

    @github-actions
    Contributor

    This issue is stale because it has been open for 40 days with no activity. Remove the stale label or leave a comment, otherwise it will be closed in 10 days.

  4. simonrosenberg commented on May 18, 2026

    @simonrosenberg
    MemberAuthor

    Closing this issue since we fixed the issue with large swebenchmultimodal instances.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions