Skip to content

Global mutable singletons are not thread-safe — breaks multi-agent concurrency #1260

@MervinPraison

Description

@MervinPraison

Problem

The core SDK has 15+ unprotected global singletons sharing mutable state across all agent instances. In multi-agent or multi-threaded scenarios, this causes race conditions, data corruption, and cross-agent state leakage.

Principle violated: "Multi-agent + async safe by default"

Critical Locations

Singleton File Thread-safe?
_global_client / _global_client_params praisonaiagents/llm/openai_client.py:2198-2224 No — race on param check + client creation
error_logs, sync_display_callbacks, approval_callback praisonaiagents/main.py:27-34 No — concurrent dict/list mutations
_default_emitter praisonaiagents/trace/protocol.py:389-404 No — global set without lock
_telemetry_instance praisonaiagents/telemetry/telemetry.py:642-706 No — force_shutdown_telemetry() kills ALL agents
_global_store + reset_global_store() praisonaiagents/context/store.py:463-480 Partial — reset destroys ALL agents' context
APPROVAL_REQUIRED_TOOLS (eager) praisonaiagents/approval/__init__.py:65-66 No — forces eager init, shared mutable set
_global_registry (eager) praisonaiagents/tools/circuit_breaker.py:438 No — eagerly instantiated at import
_global_health_monitor (eager) praisonaiagents/tools/health_monitor.py:459 No — eagerly instantiated at import
_store_instance praisonaiagents/tools/schedule_tools.py:26 No
_default_registry praisonaiagents/hooks/registry.py:321 No

Impact

  • One agent calling force_shutdown_telemetry() kills telemetry for ALL agents
  • One agent calling reset_global_store() destroys context for ALL agents
  • OpenAI client params can race between threads — potentially using wrong API key/base_url
  • Approval policy set by one agent affects all subsequent agent initializations

Suggested Fix

Introduce a per-agent AgentScope or session-scoped registry pattern. Global singletons should become agent-instance-scoped or at minimum protected with threading.Lock(). Eagerly-instantiated singletons (circuit_breaker.py:438, health_monitor.py:459, approval/__init__.py:65-66) should be converted to lazy getters.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions