Skip to content

fix(tracing): thread context not propagating to spans/events #54

Description

@dremnik

Problem

The tracing system uses AsyncLocalStorage to maintain span context, but context is not being properly established in the Thread execution loop.

Root Cause

  1. run() in dispatch.ts establishes async context via AsyncLocalStorage.run()
  2. span() and event() default to parent: "current", which looks up context via gctx.getStore()?.spanId
  3. In thread.ts, spans are created and enter() is called, but enter() only notifies the subscriber - it does NOT establish AsyncLocalStorage context
  4. The run() function is imported but never used

Consequences

  • event() calls with default parent resolve to null (e.g., thread.error event at line ~213)
  • Tool implementations that call span() or event() won't find the parent span
  • Child spans work only because explicit parent IDs are passed (this._span!.id)

Additional Complexity

AsyncLocalStorage context can be lost across yield boundaries in async generators when the generator is consumed from outside the original context.


Proposed Solution

Combine explicit parent passing in Thread with run() wrapper for tool execution.

Changes

  1. Add spanId to Context class:
// context.ts
export class Context<TContext = UnknownContext> {
  // ... existing fields ...
  spanId?: SpanId;
}
  1. Fix explicit parent in event calls:
// thread.ts line ~213
event({
  kind: "thread.error",
  message: err instanceof Error ? err.message : String(err),
  stack: err instanceof Error ? err.stack : undefined,
}, this._span?.id ?? null);
  1. Wrap tool execution in run():
// thread.ts executeTools()
const ctx = new Context(this.namespace, this.context.context);
ctx.agent = this.agent;
ctx.approve(call.callId);
ctx.spanId = s.id;  // pass span to context

// Wrap in run() for automatic context propagation
const res = await run(s.id, () => tool.invoke(ctx, call.arguments, call.callId));

Benefits

  • Thread internals use explicit IDs (reliable across async boundaries)
  • Tools get automatic context via run() wrapper
  • Tools can also access span via ctx.spanId if needed explicitly

See packages/kernl/src/tracing/CONTEXT_PROPAGATION.md for full analysis.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions