Problem
The tracing system uses AsyncLocalStorage to maintain span context, but context is not being properly established in the Thread execution loop.
Root Cause
run() in dispatch.ts establishes async context via AsyncLocalStorage.run()
span() and event() default to parent: "current", which looks up context via gctx.getStore()?.spanId
- In
thread.ts, spans are created and enter() is called, but enter() only notifies the subscriber - it does NOT establish AsyncLocalStorage context
- 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
- Add
spanId to Context class:
// context.ts
export class Context<TContext = UnknownContext> {
// ... existing fields ...
spanId?: SpanId;
}
- 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);
- 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.
Problem
The tracing system uses
AsyncLocalStorageto maintain span context, but context is not being properly established in the Thread execution loop.Root Cause
run()indispatch.tsestablishes async context viaAsyncLocalStorage.run()span()andevent()default toparent: "current", which looks up context viagctx.getStore()?.spanIdthread.ts, spans are created andenter()is called, butenter()only notifies the subscriber - it does NOT establish AsyncLocalStorage contextrun()function is imported but never usedConsequences
event()calls with default parent resolve tonull(e.g.,thread.errorevent at line ~213)span()orevent()won't find the parent spanthis._span!.id)Additional Complexity
AsyncLocalStorage context can be lost across
yieldboundaries 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
spanIdto Context class:run():Benefits
run()wrapperctx.spanIdif needed explicitlySee
packages/kernl/src/tracing/CONTEXT_PROPAGATION.mdfor full analysis.