How a span is attributed to a call site
Exactly one thing attributes a span: thetessary.call_site.id span attribute. Tessary never infers a call site from a file path, a span name, or the shape of a prompt. Guessing would mis-attribute production traffic, and a baseline fitted on mis-attributed traffic is worse than no baseline.
Four properties of the tag decide whether attribution works:
- The key is
tessary.call_site.id, dotted. Tessary silently ignores an underscore variant, so the span looks tagged and resolves to nothing. - The value is a literal, never an f-string, a variable, or an enum lookup. A tag computed at run time cannot be traced back to code.
- The tag goes on the span that covers the model call, not on a parent request or handler span. A handler making three different calls is three call sites, and one tag on the handler collapses them into one.
- A shipped id is frozen. The id is the key every finding and every already-ingested span holds, so renaming one orphans all of them silently. A call site that moved in the code keeps its id.
A trace takes the first call site it reached
Spans carry the tag; traces inherit one. When a trace’s rollup runs, Tessary copies one call site onto the trace itself:- The root span’s call site, if the root span has one.
- If not, the call site of the tagged span that started first. A tie on start time goes to the lower span id.
- If no span in the trace has a tag, the trace has no call site.
malformed_output and groundedness still run on the summarizer’s span with the summarizer’s call site. A classifier limited to some call sites runs on a span with no tag of its own when any span in its trace is at one of those call sites.
Tessary reads the call site only after the root span arrives. Until then, the trace keeps the call site it had, so a rollup that runs before the root arrives cannot pick a call site from only some of the spans.
Why untagged spans are not usable
Untagged spans are not rejected. Ingest is fail-open, so they are stored, they appear under Traces, and they advance a new project’s onboarding milestone fromlistening to fitting. What they cannot do is carry a claim about a call site.
Traces with no call site collect under a single unattributed pile, and that pile is deliberately not a bucket at turn grain. It is a mixture of everything the instrumentation missed, so its distribution moves whenever the mix moves. A finding drawn from it would be an artifact of instrumentation coverage rather than a statement about your agent. The actionable fact about that pile is its size, not its shape.
The same gap closes other checks quietly:
This is also what the connect gate in Set up Tessary is testing. The gate does not open when spans arrive. It opens when a tagged span arrives, and until then it shows a live count of spans received against spans tagged, because the only useful fix in that state is to tag the traffic that is already arriving.
Tool-grain measurements are keyed on the tool, not on the tool within a call site. A shared tool called from five entry points stays one population, because splitting it per call site would shatter it into populations too thin to watch. Tessary files a tool finding under the call site that supplied the traffic in the window the finding fired on.
Call-site shapes
A call site’s shape says what kind of work the model does there: answer from retrieved documents, summarize, pick a route, and so on. Tessary doesn’t infer a shape from traffic or code, and the interface doesn’t show it. It comes from the.tessary/ bundle you import, in the format the evals plugin defines.
Groundedness is the one classifier that reads it. It scores answers only at a call site whose shape is rag_answer, summarize, or extract, because those are the shapes whose answer should be supported by a source the trace carries. A call site with any other shape, or with none, is never scored. Which answers are scored has the full rule.
Two boundaries decide whether groundedness runs. A summary returned as structured fields is
extract, not summarize. An answer written from retrieved documents is rag_answer, not draft, even when it reads like a draft.
Set or change a shape
A shape is theshape field in the call site’s file, .tessary/pipeline/call_sites/<id>.yaml, where id is the call site’s tessary.call_site.id value, verbatim. The bundle format describes every field.
- Set
shapein the call site’s file. - Open Settings → Sources and select Import bundle. You need the owner role on the project.
- Select Choose directory and pick your
.tessary/directory. It must containpipeline/meta.yaml. - Keep Upsert, which adds and updates call sites and deletes none, and select Import bundle.
Choosing which call sites to tag
The set of call sites you tag is a decision about what you want watched, and it is worth making deliberately rather than tagging everything reachable. Split a call site where the answers would be different: a summarizer and a router that share one wrapper function are two intents and belong apart, because a change in one has nothing to say about the other. Do not split where only the content varies, because two thin populations resolve smaller moves far worse than one thick one does. Calls that leave the process without a span at all, such as a shelled-out CLI agent or a raw HTTP request to a provider, are often the highest-risk calls in a repository precisely because nothing watches them. They need a span opened around them with the tracer your repository already configures.Read next
Classifiers and findings
What a classifier watches per call site, and what a finding records.
How Tessary works
The whole chain, from an arriving trace to an explained case.
