Repository navigation
Lift [tc] prompt headers into every agent span's metadata (1.3.0) - #5
Merged
Merged
Conversation
A launcher's first prompt line "[tc migration=m batch=b fqn=f phase=p]" now
reaches Langfuse as observation metadata on the agent it belongs to, at any
depth, for workflow and background agents alike:
- tc_<key> per header pair, read from the agent's full prompt rows (never
from the span input, which is truncated to CC_LANGFUSE_MAX_CHARS); empty
values ("pr= issue=") are dropped instead of voiding the header.
- tc_source own | inherited | none. An agent with no header inherits the
effective header of the agent or main-thread turn that launched it; a
registered agent keeps what it inherited when a later firing refreshes
it outside its launcher. An own header replaces the inherited one whole.
- A workflow agent's computed task is read before the relayed user request.
- The main-thread turn's own header goes on its root span, and the root
gets tc_session_agents (agents / own / inherited / unattributed) when it
ends, so a reader can show an unattributed line rather than a zero.
- migration and work become tc:<key>=<value> trace tags.
- The backfill dry run prints agent_tc_sources, to check coverage first.
CC_LANGFUSE_TC_HEADERS=false turns it off. Version 1.3.0.
Tests: tests/integration/test_tc_headers.py over a fixture sliced from real
Claude Code 2.1.x transcripts with every body scrubbed to its header line
(three-deep agent chain, an agent whose prompt is followed by api-request
echo rows, a headerless agent), plus the workflow fixture with the real
two-row workflow prompt shape. Each assertion was checked against a
mutation: no inheritance, empty values rejected, first prompt row winning,
and the 1.2.1 echo-row reading all fail the tests that name them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ga4FKjMftJRxoPaQaiPLHy
…a zero Without deterministic ids no agent is registered, so tc_session_agents would read as 'no agents' instead of 'not counted'. The root now leaves it out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ga4FKjMftJRxoPaQaiPLHy
Collaborator
Author
|
Refute pass (independent): GO at 95b848f. No Medium or higher findings. The Lows are below. What was verified
Lows
The repro test for L1 and the two-firing check is kept locally (not pushed). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked: Border-Innovation/topcodearchiteture#596 (the reader that consumes these fields).
What this changes
A launcher's first prompt line,
[tc migration=m batch=b fqn=f phase=p]or[tc work=harness pr=1 issue=], now reaches Langfuse as metadata on the agent it belongs to. This holds at any depth, for workflow agents and for background agents.tc_<key>for each header pair. The header is read from the agent's full prompt rows. It is never read from the span input, which is truncated toCC_LANGFUSE_MAX_CHARS. Empty values (pr= issue=) are dropped; they no longer void the whole header.tc_sourceisown,inheritedornone.tc_session_agents: the session's agents so far, counted as agents, own, inherited and unattributed. A reader can then show an "unattributed" line instead of a zero.migrationandworkalso become trace tags, for exampletc:migration=m1.agent_tc_sources, so coverage can be checked before anything is uploaded.CC_LANGFUSE_TC_HEADERS=falseturns the feature off. The version is now 1.3.0.Why headers never reached Langfuse: root cause, with evidence
The evidence is a read-only probe of this machine's Langfuse (v2 observations API) and its transcripts, for one long session.
api-request-blobrows as user turns.api-request-shape,api-request-blobandapi-requestrows after each prompt. A blob row hasmessage.role: "user"and holds the injected<system-reminder>context (CLAUDE.md, memory), about 117,000 characters long.get_user_or_assistant_role_from_rowfalls back tomessage.role, so a blob starts a new turn. The real prompt turn then has no assistant reply, andbuild_turn_from_statedrops it.<system-reminder>.is_request_echo_rowinread_subagent_jsonl, plus "a typed row is never a chat row". But 1.2.2 never ran on this machine. 1.2.2 writeslangfuse_agents.<key>.jsonbeside the state file on every firing, and~/.claude/statehas none. Running sessions keep the hook version they started with, including afterclaude plugin update(1.2.2 was installed on Oct 17, 13:55).[tc work=<kind> pr=<n> issue=<n>]convention is often sent with blanks, such aspr= issue=. A strictkey=value+pattern rejected the whole header.agent_tc_sources = {own: 472, inherited: 459, none: 1912}. The 1,912 agents with no header to own or inherit are mostly older Workflow rounds and pre-convention agents. That count is the true unattributed population, not a defect of the lift.Tests
tests/integration/test_tc_headers.pyhas 19 tests.uv run --group dev pytest: 378 passed, on Python 3.11 and 3.10.ruff F,E9check and theecho '{}' | uv run --script hooks/langfuse_hook.pysmoke check are clean.Owner: install (not done by this PR's author)
After merge:
claude plugin marketplace update langfuse-observability claude plugin update langfuse-observability@langfuse-observability claude plugin list # expect langfuse-observability 1.3.0 for both the project and the local install entriesls ~/.claude/state/langfuse_agents.*.jsonlists a file. In Langfuse, an agent span then carriestc_source.uv run --script hooks/langfuse_hook.py --backfill <session>.jsonl --dry-runfirst, and readagent_tc_sources.--namespaceand--replace-session-traces, as described in the README's backfill section.🤖 Generated with Claude Code
https://claude.ai/code/session_01Ga4FKjMftJRxoPaQaiPLHy