| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
A user message sent while the agent is working is absorbed by the run already in flight — the agent loop re-reads the session's history at the top of every step, so the message joins the current turn instead of starting a new one. Laminar had no record of it: `chat.message` returns early when a turn span is already open, and the turn span's `input` is serialized once at creation and cannot grow. The only evidence a follow-up ever arrived was the LLM call's message array getting longer between one step and the next. Mark it with a zero-duration `injected_input` span nested in the live turn, carrying the message parts. It lands between the steps it arrived between, so the trace shows where in the conversation the follow-up was picked up, and injections become queryable by span name.
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Sorry, something went wrong.
`input.messageID` is the caller's optional override. Every v4 run omits it — the worker POSTs /prompt_async with just parts and model — so opencode generates the id at prompt.ts:657 and the hook's `messageID` is undefined, which JSON.stringify then drops from the span input entirely. Read `output.message.id` instead: the id the message was actually persisted under, present whether the caller supplied one or not. Applies to the turn span too, which had the same gap.
| Back | FazBrowse Home | New Git URL |
Issue for this PR
Closes #
Type of change
What does this PR do?
A follow-up sent while the agent is working is now absorbed by the run already in flight rather than cancelling it — the agent loop re-reads the session's history at the top of every step, so the message joins the turn already in progress. Laminar had no record of that happening.
Two reasons, one line: chat.message returns early when a turn span is already open (plugin.ts), and the turn span's lmnr.span.input is serialized once at creation so it cannot grow afterwards. The only evidence that a follow-up ever arrived was the LLM call's message array quietly getting longer from one step to the next.
Here is a real staging run. The follow-up ("skip the file for the letter O") landed at ~18:00:46.8, between step 3 and step 4, and appears nowhere:
v4.run 0.8s └── turn 43.9s input = {message: "Make 26 files..."} ├── session.llm #1 6.5s user_msgs=1 ├── session.llm #2 9.0s user_msgs=1 ├── session.llm #3 18.0s user_msgs=1 (apply_patch ends 18:00:46.794) │ · · · · · · · · · · · INJECTION HERE — INVISIBLE ├── session.llm #4 3.7s user_msgs=2 <- first call that can see it ├── session.llm #5 2.6s user_msgs=2 └── session.llm #6 3.2s user_msgs=2After this change:
v4.run └── turn input = opening message (unchanged) ├── session.llm #1 ├── session.llm #2 ├── session.llm #3 ├── injected_input <- NEW: input = { messageID, message, parts } ├── session.llm #4 ├── session.llm #5 └── session.llm #6Changes:
Why it nests correctly: Laminar's UI nests by the lmnr.span.path attribute, not by OTel parentSpanId. processor.ts computes that path in onStart from spanIdToPath.get(parentSpanId), so a hand-parented child inherits [v4.run, turn] and becomes [v4.run, turn, injected_input]. The AI-SDK re-parenting branch does not interfere — it only fires for spans that carry ai.telemetry.metadata.sessionId and have no parent, and this span is the other way round on both counts.
The turn span's input is left alone. Rewriting a serialized JSON attribute in place to append a second message would be fragile, and "the message that opened this turn" is a well-defined thing for it to hold. The full input set for a turn is now turn.input plus its injected_input children.
This fires for interactive TUI follow-ups too, not just the v4 cloud path — same hook, same semantics.
How did you verify your code works?
bun typecheck (16/16) and bun run lint on packages/bcode-laminar/src — 16 warnings / 0 errors, byte-identical to the count on main, so no new lint findings.
There is no test harness or CI test job in this package, so rather than commit a test nothing runs, I verified both halves against the real vendored processor with an InMemorySpanExporter and against the real plugin hook, then deleted the scripts. Span mechanics, using the v4 parent-context shape:
Hook behavior, calling chat.message twice on one session plus once for a sub-agent:
Not yet observed end-to-end in Laminar: the v4 sandbox image pins bcode 0.1.17, so this needs a release and a Dockerfile bump in cloud before it shows up on staging traces.
Screenshots / recordings
n/a — trace structure, diagrammed above.
Checklist
Summary by cubic
Record mid-run follow-ups by emitting a zero-duration injected_input span under the active turn. Also capture the resolved message ID from output.message.id on both turn and injected spans for reliable correlation.
New Features
Bug Fixes
Written for commit f7c7348. Summary will update on new commits.