Class AgentContext

java.lang.Object
com.tnsai.observability.events.AgentContext

public final class AgentContext extends Object
Thread-local container for the current EventContext.

Framework entry points (e.g. Agent.chat, Tool.execute, McpClient.request) push an EventContext for the duration of their work using enter(EventContext) in a try-with-resources block. Any AgentEventPublisher.publish(...) or logger.info(...) call inside the block automatically picks up the context — the former via currentOptional(), the latter via SLF4J MDC which is kept in sync.

Why thread-local + MDC

Two mechanisms because two consumers:
  • AgentEvent publishers (structured) read currentOptional() to attach typed correlation fields.
  • SLF4J / Logback / Logstash JSON encoders read MDC to enrich unstructured log lines (third-party library logs, consumer logger.info(...)). Synced one-way: context → MDC.

Async boundaries

Thread-local is not propagated automatically across thread boundaries. Use wrap(Runnable) / wrap(Callable) when handing work to ExecutorService, CompletableFuture, or virtual-thread StructuredTaskScope. The wrapper captures the caller's context and restores it on the worker thread, then clears.

Nesting

Scopes nest naturally: an outer agent scope can wrap an inner tool scope, which can wrap an inner LLM scope. Each enter(EventContext) stashes the previous value and restores it on close. There is no global stack — each thread has at most one current context, with restoration order enforced by the try-with-resources structure.
Since:
0.5.0 (issue #78)
  • Method Details

    • current

      public static EventContext current()
      Returns:
      current context for this thread.
      Throws:
      IllegalStateException - if no context is active. Prefer currentOptional() for callers that may run outside a framework entry point (test utilities, off-thread callbacks).
    • currentOptional

      public static Optional<EventContext> currentOptional()
      Returns:
      current context for this thread, empty if no scope is active. Safe to call from any thread.
    • enter

      public static AgentContext.Scope enter(EventContext ctx)
      Pushes the given context for the lifetime of the returned AgentContext.Scope. Restores the previous context (or clears, if none) on AgentContext.Scope.close().

      Standard usage:

      
       try (var ignored = AgentContext.enter(ctx)) {
           // body — every event published or log line emitted in this scope
           // automatically carries `ctx` correlation fields.
       }
       
    • wrap

      public static Runnable wrap(Runnable task)
      Captures the current context and returns a Runnable that runs task with that context applied on whatever thread executes it. Restores the calling-thread state after the task completes (or fails).

      Use when handing work to an Executor or CompletableFuture.supplyAsync(...) so log lines from the worker carry the right correlation IDs.

    • wrap

      public static <T> Callable<T> wrap(Callable<T> task)
      Callable variant of wrap(Runnable).