Skip to main content
Observability Hooks provide a centralized entry and exit point for observability providers (AgentOps, future Langfuse, W&B) in PraisonAI and custom framework adapters. As of PR #3152, AgentsGenerator owns the observability lifecycle. Both the sync (generate_crew_and_kickoff) and async (agenerate_crew_and_kickoff) run paths bracket adapter.setup() and adapter.run()/adapter.arun() in a single observability_session. Adapters no longer initialise or finalize observability themselves — this closes the AutoGen leak (the v0.4 adapter never called finalize, so every run leaked a session) and makes finalize impossible to forget for any future adapter. praisonai.observability.hooks.init_observability(framework_tag, *, tags=None) and finalize_observability(_framework_tag, *, status=...) are public hooks. The generator drives both automatically through observability_session(); custom adapters called directly (not via AgentsGenerator) should use observability_session() themselves.

Quick Start

1

Simple Usage

2

With Configuration

3

Pair init with finalize in custom adapters

observability_session calls init_observability on entry and finalize_observability on exit. Status is derived from sys.exc_info() automatically, so success/failure is tagged correctly with no boilerplate.
This pattern is valid only for standalone callers that invoke an adapter directly, outside AgentsGenerator. Do not copy it into a custom adapter that will be called by AgentsGenerator — the generator already owns init+finalize via observability_session, and a self-finalizing adapter would double-finalize.
4

Branch on availability


How It Works

init_observability(framework_tag, *, tags=None) centralizes observability initialization:
  • Auto-call site: the generator opens observability_session(adapter.name) and runs _run_adapter_setup(adapter) inside the session, so setup events and any setup/import failure are recorded and finalized instead of slipping outside observability. The call sequence is:
    1. _prepare_for_run(config) → validates, resolves adapter; does not run setup, does not init observability
    2. with observability_session(adapter.name):
      • _run_adapter_setup(adapter) (calls adapter.setup(framework_tag=adapter.name))
      • adapter.run(...) / await adapter.arun(...)
  • AgentOps init guard: agentops.init(...) only fires if both (a) is_agentops_available() returns true, and (b) AGENTOPS_API_KEY is set in the env. Init is centralised here — agents_generator no longer double-inits AgentOps (PR #2062).
  • Failure mode: ImportError (no agentops) is logged at DEBUG; any other exception is logged at WARNING and never propagated
  • is_agentops_available() lazy function — prefer over the removed eager AGENTOPS_AVAILABLE constant in this module
finalize_observability(_framework_tag, *, status=...) closes observability sessions symmetrically:
  • Auto-call site: AgentsGenerator.generate_crew_and_kickoff (sync) and agenerate_crew_and_kickoff (async) bracket the entire adapter lifecycle — setup + run/arun — in observability_session(adapter.name). On context exit the session finalizes with status="Failure" when an exception is propagating and status="Success" otherwise. Adapters must not finalize themselves. This closes the AutoGen leak: the AutoGen adapter previously never called finalize_observability, so every AutoGen run left an ObservabilityRun, a _swapped_runs entry, and sink handles alive.
  • AgentOps end guard: agentops.end_session(...) only fires if agentops is importable
  • Failure mode: ImportError returns silently; any other exception is logged at WARNING and never propagated
  • Why symmetric calls matter: without finalize_observability, AgentOps dashboard sessions stay stuck “in progress”
The hook also leaves room for future providers (the source already has placeholder comments for _init_langfuse and _init_wandb), so users may want to know the surface area.

Configuration

init_observability

finalize_observability

observability_session

Returns a context manager (ContextManager[None]). Status is auto-derived from sys.exc_info() — no status kwarg needed.

Concurrent runs

Two overlapping observability_session(...) blocks (parallel agents, nested crews) get their own AgentOps session each — tags don’t leak, and finalizing one no longer ends the other. finalize_observability resolves the owning ObservabilityRun before it calls agentops.end_session(...), so a run whose start_session returned no handle ends nothing instead of tearing down the package-global session a concurrent run still depends on. (PraisonAI #3492)

discover_observability_sinks


Third-party sink plugins

Third-party packages can register an observability sink factory under the praisonai.observability_sinks entry-point group. PraisonAI discovers them lazily via discover_observability_sinks() — broken plugins are logged at DEBUG and never break a run.

Register a sink (plugin authors)

Your factory is a zero-arg (or framework-tag-aware) callable that returns a sink implementing the core SDK’s TraceSinkProtocol.

Discover registered sinks

Invalidating the cache

The factory list is memoized after the first discovery, so a dynamic plugin install or a test needs to invalidate it.
Cache invalidation only affects the factory list — already-running observability sessions keep their existing sinks.

Best Practices

For custom adapters that are called directly (not via AgentsGenerator), observability_session is required. It guarantees finalize_observability always runs — on success and on any failure — with the correct status derived from sys.exc_info(). This prevents AgentOps/other sessions from being orphaned in an “in progress” state on error, KeyboardInterrupt, or rate-limit paths.When invoked via AgentsGenerator, the generator’s own session already covers the run — an adapter that opens its own inner observability_session will nest / double-init and should not.
Use status="Success" for the happy path and status="Failure" in exception cases. The string is passed verbatim to agentops.end_session(...); future providers may map other values. When using observability_session, status is derived automatically.
The generator opens observability_session(adapter.name) once per run, which calls init_observability(adapter.name). Because setup() runs inside that session, calling init_observability again from setup() re-inits with your tags (last call wins for AgentOps). Use this for run-scoped tags only:
Don’t import agentops at the top of your adapter — gate it behind is_agentops_available() or rely on the hook to no-op silently:
You no longer need to serialize concurrent runs to keep tags clean. Each observability_session(...) starts its own AgentOps session via start_session and ends only that run’s handle. If you were adding a wait/lock around parallel AgentsGenerator.generate_crew_and_kickoff() calls specifically to avoid AgentOps cross-contamination, you can remove it.
New providers (Langfuse, W&B, etc.) will be added inside _init_<provider> helpers in praisonai/observability/hooks.py — calling init_observability(...) will automatically pick them up; you don’t need to update adapter code:

AgentOps

AgentOps integration documentation

Framework Adapter Plugins

How to create custom framework adapters

Custom Tracing

ContextTraceSink protocol and third-party sink plugins

Gateway Tracing Hook

Emit OpenTelemetry spans across each gateway pipeline stage