Propagate invocation context through agents#689
Conversation
Adds an `InvocationContext` to link agents together so call tree can be tracked in LLM observability tools
|
I'm going to mark this as a draft for now rather than merge. The reason is sequencing: #652 is mid-flight, and it relocates the recursive tool loop out of the individual gateways into a centralized I'd rather solve this once, properly, on top of the refactor than do it twice. So: keeping this open as a draft for reference. Once #652 settles, we can retarget against |
That makes sense. Probably worth discussing if middleware or events are a better for telemetry. Events might be a bit simpler and allow for latency tracking |
The problem
I was integrating PostHog LLM observability on top of agent middleware and ran
into a wall with sub-agents. When an agent is used as a tool,
AgentTool::handle()calls
$this->agent->prompt(...)and the sub-agent gets a brand newinvocationIdwith nothing tying it back to the agent that called it. So in Posthog the parent
and sub-agent show up as two separate traces instead of one tree, with no way to
reconstruct "this agent delegated to that one."
The solution
I added a
InvocationContextthat carries the current invocation's id plus its parent and root, and keeps it active for the duration of aprompt()orstream(). A sub-agent'sprompt()runs on the same call stack, so it inherits them automatically. The ids are exposed asparentInvocationIdandrootInvocationIdonAgentPromptandAgentResponsefor middleware or a listener to read. Streaming re-establishes the context inside the lazy generator, and the queue path serializes the ids onto theInvokeAgentjob so a queued agent dispatched inside an invocation still nests under it.I built this for PostHog, but I checked the tracing models for a few other tools (OpenTelemetry, Langfuse, LangSmith, Helicone) and they all group by a root/trace id and nest children under a parent, so the same parent/root invocation ids map onto them too. Helicone is the one slight outlier, it nests by a session path rather than a parent id, but the lineage still feeds it.
Heads up
I mostly work in React but we have a Laravel backend, so some of my PHP might less than ideal. Happy to
adjust anything. I did use Claude to help me through some of this, but I have gone through and reviewed it.