Skip to content
andydataguy
Wiki Corpus

The Observability Manifesto. A successful request can still charge a customer the wrong price.

AI & TECHNICAL · Definitive~63 min read · 14,846 words

A successful request can still charge a customer the wrong price. The server returns normally. The timing looks ordinary. Somebody opens a support ticket, and the evidence says the calculation completed. It says nothing about the rule that produced the number.

An observable system keeps enough context to answer that later question. The useful record connects an operation to the policy it used, the branch it took, the dependencies it called and the result it left behind. That record has to exist before someone asks for it.

Still from the hero animation. One checkout request drawn as a nested trace. POST /checkout runs 412 ms, and beneath it auth, cart, pricing and payment each carry their own timing, with tier.fetch highlighted at 262 ms as the child that spent most of the parent's time.
The same system, drawn twice in one continuous shot. On its own the box shows pressure and shadow. A scan passes through it and the work resolves into a nested trace of timed spans, with one span carrying the time the operation actually spent.

A successful run leaves questions a timer can't answer

Consider a pricing function that checks a customer's tier, regional eligibility and an older account's special terms. This is an illustration, not a report of a customer incident. Two calls can complete without exceptions while applying different policies. One correctly excludes a promotion. Another incorrectly excludes it. Their durations can be indistinguishable.

The support question is specific. Which policy applied to this order, and why? A log saying the function completed gives the investigator almost nothing to work with. A record containing the policy version, the relevant tier and a stable exclusion reason gives them something they can compare with the intended rule.

The same distinction applies to a content pipeline. A job can fetch a document, split it, embed the pieces and write them to a database without throwing an exception. Search can still get worse because the splitter produced different pieces, the wrong source revision arrived or the embedding configuration changed. Execution and correctness are separate claims. Each needs evidence suited to it.

A print statement only answers the question written into it

Print debugging can help with a local, reproducible problem. Its limits become expensive when a failure depends on production data, timing, concurrency or an external service. The investigator puts a statement near a suspected cause and waits for another run. The output narrows the search, so they add another statement somewhere else.

Every turn requires the failure to happen again under conditions that reveal something useful. A deployment might be part of that turn. It might also need an affected customer to retry, a scheduled job to run or a provider to return the same unusual response. There's no universal duration for this process. The cost follows from the missing evidence and the difficulty of recreating it.

The question also keeps changing. A statement that reports the input size won't answer whether the failed calls shared a feature flag. A statement that reports the flag won't establish which policy version the worker loaded. An investigator can be careful and still need several turns, because the important question becomes apparent only after earlier answers arrive.

A person digs a narrow hole by lantern light in a dark field. Around them, many small mounds of refilled earth mark earlier holes that were dug and filled back in. At the far edge, the end of a pipe pokes out of the ground, unnoticed.
Each print statement is a test hole: one guess, one deploy, one wait for the bug to come back. When the fault turns up the prints are deleted, and the next person digs the same field from scratch.

Deleting the temporary statements removes that evidence source from future runs. Keeping every statement forever produces another problem, a stream of text without stable fields or useful connections. The improvement is to design a durable record of the operation, with fields someone can query together.

Instrumentation makes a condition readable

A car's fuel gauge is a useful comparison, with a limit. The gauge doesn't add fuel or repair the engine. It exposes a condition so the driver can act before running out. Software instrumentation does similar work when it records a queue's age, a dependency's latency or a job's quota consumption.

The limit's that software can be wrong in ways a fuel gauge doesn't describe. A process can be running and returning valid responses while applying the wrong business rule. Instruments for availability alone won't explain that failure. The observation has to match what the system is supposed to do.

Two identical car interiors at night. On the left the instrument panel is a blank unlit slab and the windscreen shows only black road. On the right the same panel carries lit gauges, a fuel needle and an oil-warning icon, with the light spilling onto the driver's hands. The engine is the same in both.
The engine is identical in both cars. The lit panel on the right changes nothing about that engine and is the only reason its condition can be read.

I use LogFire as part of my Python engineering approach because structured observations can connect operations across a distributed system and make the records available to the tools working on it. That preference comes from the same data-model habit I use with Pydantic, deciding what data crosses a boundary and giving that data a defined shape. The tools support the habit. Their presence doesn't establish that a particular application has useful instrumentation.

A useful check begins with an operation somebody cares about. Can an authorized operator find a run, identify its input revision, inspect its important decisions and see whether its intended result persisted? Every unanswered part names a gap in the record. The gap can be in capture, correlation, retention, access or the query tools. Installing another exporter won't necessarily fix it.

A change in shape can precede an exception

Suppose a transcript source changes its punctuation. A chunker may produce more pieces from otherwise comparable documents while continuing to report success. The count alone doesn't prove a defect. Different source lengths could explain it. But recording source identity, input length, splitter version and output count makes it possible to distinguish those explanations.

Two stone posts stand in a dark river. The left post carries a long row of daily notches at one level, then a fresh glowing notch much higher, lit by a hanging lantern. The right post is bare, with the water at the same raised level and no marks on it.
Chunks per document rose by thirty percent and nothing threw an error. A pipeline that records the number on every run sees the jump the day it happens; one that records nothing hears about it from a customer weeks later.

A warning becomes useful when someone can investigate the conditions behind it. An aggregate count can show a change. Individual records explain which inputs changed and which stayed comparable. The design problem is deciding what to retain so that a later investigator can make that comparison without modifying the application first.

Monitoring and observability prepare for different questions

A queue that keeps filling needs an alert. The team knows the condition matters before it happens, can choose a measure and can decide who should respond. That's monitoring doing a useful job. A trace store without an alerting policy won't automatically tell anyone that the queue needs attention.

An incident creates other questions that weren't specified in advance. Did the failing requests share a deployment version? Were they all from accounts affected by a migration? Did one dependency become slow only for a particular input shape? Answering those questions requires records that preserve the distinctions between requests.

Monitoring and investigation therefore need different kinds of preparation. Monitoring defines conditions worth watching. Observability, in the practical sense used here, preserves enough queryable evidence to investigate a question formed after execution. An alert can start an investigation, and an investigation can reveal a condition worth alerting on. Neither removes the need for the other.

A retained field determines which comparisons remain possible

An error-rate chart aggregates individual outcomes into a population. That aggregation is useful. It makes a change visible without asking someone to inspect every request. It also removes distinctions unless the metric was split by those dimensions or the individual events remain available elsewhere.

Suppose the error rate rises after a release. A chart grouped by service and region can show which region changed. It can't identify a particular account if account identity never entered its data. An event record that retains an appropriately protected account reference, deployment version and outcome can support that query, subject to sampling and retention.

The distinction is practical rather than absolute. Nobody can promise to answer every unforeseen question. An event can only answer questions supported by the fields it kept, and access controls can appropriately prevent some people from seeing those fields. Observability expands the available questions within those limits.

Still from the animation. Two panels side by side. On the left a dashboard of four gauges chosen in advance, with customer id, region and plan tier struck through as questions it has no series for. On the right the same question answered straight out of wide event records that kept every field.
A dashboard holds four pre-chosen questions and answers them instantly. A new question arrives that none of the four covers, and the dashboard has nothing to say. The same question thrown at a field of wide events resolves, because the field was never narrowed to four.

A useful readiness exercise is to choose a recently completed request and ask something the default dashboard doesn't show. The question should matter to the product, such as whether a policy flag changed the result. If answering requires a new deployment, the historical execution may already be unavailable. Adding the field improves future evidence; it doesn't recover the past.

Metrics, logs and traces represent different aspects of execution

OpenTelemetry provides a vendor-neutral framework for collecting and exporting telemetry. Its trace documentation describes a request as connected units of work. Those connections are the reason a downstream database call can be understood as part of an upstream operation instead of as an isolated duration.

Metrics summarize measurements over time. A counter can record completed jobs, and a histogram can retain a distribution of durations. Structured logs record particular occurrences with named fields. Spans describe units of work with timing and context; connected spans form traces.

These signals can describe the same execution, but they aren't interchangeable copies. A metric can answer a population question without retaining every event. A log can record an occurrence that has no meaningful duration. A trace can connect a slow request to the work it triggered across processes. The right representation depends on the question and on the cost of retaining the necessary detail.

A ship docks at a pier under a single lantern. Three paths lead away from it: to a glass jar filling with identical pebbles, to a rolled letter closed with a wax seal, and to a parcel with wax stamps along its string, tied by a thread to a larger parcel.
A metric counts the arrivals into a jar, a log line writes one arrival down as text, and a span wraps one with its timing and its parent. All three describe the same events, which is why observability starts from the event.

The mistake is treating ownership of three telemetry products as proof of investigative capability. A team can collect all three signals and still have incompatible names, missing context, inappropriate retention or no way to query an important field. Collection is one part of the path between execution and a useful answer.

Width helps when fields describe the same operation

A wide event holds several related facts about one operation. Request type, tenant reference, deployment version, feature configuration, input size and outcome can then be compared together. Their co-location matters. Separate uncorrelated records may contain the same facts without preserving which facts belong to which execution.

Honeycomb's guidance on choosing an attribute or a span favors adding information to an existing span when it describes that span's operation. The article also makes its cost argument conditional on the provider's pricing model. A field can increase investigative value without requiring another span, but its bytes, indexing and retention still have a cost.

Width needs design. A record with several well-defined, safe attributes is useful. A serialized payload dropped into a single field may be large, sensitive and difficult to query. Recording every accessible value would also preserve data nobody had authority to copy. The goal is a bounded set of distinctions that explain behavior.

A standard export format doesn't preserve information by itself

OpenTelemetry separates instrumentation from the place that stores and queries the results. That separation can reduce backend dependence. It doesn't guarantee that the backend retains every field, keeps records long enough or exposes them to the people investigating an incident.

A collector can filter attributes. A sampler can discard a trace. A storage service can aggregate away the individual events. A retention policy can expire them before the support question arrives. These are sometimes sensible decisions, but each changes what an investigator can learn.

Evaluate the entire route. Follow one intended attribute from the code that emits it through collection, processing, storage and the query an operator will use. Check whether its meaning survived. A value that exists only in an application log on an inaccessible machine won't answer a question in the shared trace tool. A value removed during export won't become available because its name appears in the instrumentation code.

That route starts with a small object worth understanding precisely, a span and the context attached to it.

A span connects a unit of work to its context

A span represents an operation with a beginning and an end. It might describe handling a request, executing a query or processing a message. A trace connects spans belonging to a logical execution. The trace can stay inside one program or cross service boundaries.

The difference between a useful span and an empty timing bar is the context someone can inspect. A duration tells an investigator where time accumulated. An operation name, input shape and recorded decision can explain what kind of work consumed that time. Neither should be inferred from the appearance of a waterfall chart.

Identity and timing establish the execution being examined

A trace identifier associates spans with a trace. A span identifier distinguishes one unit of work within that trace. A parent reference connects a child operation to the operation that initiated it. A root span has no parent within that trace. This identity structure makes it possible to follow a request without joining unrelated work merely because it happened nearby in time.

Start and end timestamps describe the recorded interval. The duration is useful for comparing like operations, but nested bars need interpretation. A parent's recorded interval can overlap its children's intervals. Adding every bar's duration can therefore count overlapping time more than once. Concurrent children also overlap with each other.

A trace showing a slow parent and a slow child suggests where to investigate. It doesn't prove that optimizing the child will improve the whole request by the child's full duration. Work might overlap, wait on another dependency or move after a change. The recorded structure helps form a hypothesis; a comparable measurement establishes the effect of a correction.

Still from the animation. One span for pricing.resolve with its six field groups laid out beneath it. Identity, timing, classification, events and links each close after three entries or fewer, while the attributes column keeps growing past ten and is marked as the one that never closes.
One span opens as a bare bar on a timeline, then each field lands on it in turn: identity, timing, classification, attributes, events, links. The attribute block keeps growing after the others stop, which is the point.

Classification and attributes answer different questions

A span's name describes its operation. Its kind describes its role, such as an incoming server operation, an outgoing client call or internal work. Producer and consumer kinds describe work created for later processing and the processing of that work. These classifications help a backend interpret the record.

Status describes the operation according to the instrumentation's conventions. OpenTelemetry has Unset, Error and Ok statuses. A normal operation doesn't need an explicit Ok assignment merely to be valid. More importantly, a span's status doesn't independently verify a business outcome. The pricing function can finish without a technical error while applying the wrong rule.

Attributes carry additional named facts. OpenTelemetry permits scalar values and supported arrays, with implementation and backend limits that need to be respected. Attribute count and string length are resources to budget. An attribute block is extensible, not unlimited.

A useful span might identify the policy version, the customer's permitted account reference and whether the relevant branch applied. Another operation needs different attributes. An embedding step might record input length, model identifier and output dimension. Copying the pricing fields onto it would add width without adding an answer.

A span event records a meaningful occurrence at a particular time within the operation. A retry scheduled after a failed attempt is one example. An attribute can hold the total attempt count; events can preserve the order and reasons for attempts. Use the event when the timestamp adds information.

A link associates a span with another span's context without making it the sole parent. That matters for asynchronous work or a batch assembled from several upstream operations. Forcing every relationship into a single parent can misdescribe how the work arose. A trace tree provides one kind of structure; links preserve additional relationships.

OpenTelemetry's documentation distinguishes attributes, timestamped events and links. The choice follows the fact being recorded. A property of this operation belongs in an attribute. A meaningful point during it can be an event. A relationship to another execution can be a link.

Names describe operation classes; attributes distinguish instances

A route template such as GET /users/{id} groups comparable work. A name containing one user's identifier creates a separate operation name for each instance. That complicates comparisons and can multiply time series when a backend derives metrics from span names.

The identifier can remain available as an attribute when its use is authorized and the storage system can support it. Stable operation names and detailed instance attributes solve different problems. Keeping both lets an investigator move between a population and an individual execution.

THE NAME IS THE CLASS GET /users/8814ff20 ONE SERIES PER USER, BILLED GET /users/{id} user.id = 8814ff20 ← attribute ONE SERIES, STILL FILTERABLE
The identifier moves out of the metric name and into an attribute, where it stays queryable and stops multiplying series.

The same rule applies outside HTTP. Name the operation that publishes an order event, then attach the appropriate message reference separately. A new identifier shouldn't require a new operation name. A new operation with different semantics may deserve one.

Shared attribute meanings keep queries from silently changing

OpenTelemetry semantic conventions define names and meanings for common observations. Using a shared HTTP method field lets a query compare calls across libraries and services. A private synonym can require a translation before those records can be analyzed together.

Custom fields remain necessary for business decisions and data transformations. Give those fields a stable namespace and document what they mean. A field named input_count is incomplete unless its unit's clear. Documents, characters, tokens and bytes are different populations. A count that changes meaning across services can produce a correct sum of incompatible quantities.

Attribute renames also affect saved queries and alerts. Treat a change in meaning as an interface change. Identify consumers, record the transition and avoid silently mixing old and new definitions. The same caution applies to evolving semantic conventions. Check the version being emitted and the version the backend understands instead of assuming an example from a different SDK is current.

A span is consequently a small data contract. Designing it with the same care as an API response makes the later investigation possible. The next problem is applying that contract consistently without scattering observation code through the business logic.

A wrapper can observe work without owning its business rules

A Python decorator can wrap a function, call it and record what happens around that call. Middleware and interceptors offer similar places to observe requests in other systems. The advantage is a shared implementation of recurring behavior. Timing, span creation and approved error handling can follow one policy instead of being copied into every function.

That separation needs to be real. A wrapper that changes arguments, catches an exception and returns a substitute, or retries an unsafe operation has changed business behavior. It may still be useful, but it needs an explicit contract for that change. Calling it observation doesn't make the change harmless.

Preserve the operation's behavior before adding its record

A timing wrapper should return the original result and preserve the original failure. An asynchronous function needs a wrapper that awaits the work being measured. A wrapper that times creation of an awaitable without timing its execution records a different interval from the one its name promises.

Cancellation and cleanup also belong to the operation's behavior. A task cancelled by its caller shouldn't continue merely so an exporter can finish. An exception shouldn't disappear because an observation helper catches too broadly. The instrumentation must close its own resources without obscuring the outcome an upstream caller needs to handle.

These details make an existing instrumentation library preferable to a private decorator when the library already handles the framework correctly. Use the supported integration, then add the domain fields it can't know. A custom wrapper earns its place when it expresses a genuine shared policy, not when it duplicates span creation with a different spelling.

Still from the animation. A two-line pricing function sits untouched at the centre while three dashed rings labelled timing, quota and lineage close around it, each contributing its own fields to a recorded list of eight on the right.
A bare function call runs and returns nothing but a value. Three wrappers close around it in turn, and each one adds its own band of recorded context to the same operation without touching the code in the middle.

Stacked wrappers need an understood order. A span outside a retry wrapper may cover the whole logical operation, including backoff. A span inside it may describe individual attempts. Both can be useful. They answer different duration questions, and confusing them can make a latency comparison meaningless.

The order also affects quota accounting and validation. Count actual external attempts when measuring provider consumption. Counting only successful logical operations can hide the retries that spent the allowance. Counting attempts as separate completed customer jobs inflates the work delivered. A shared implementation helps only if those units are defined correctly.

Capture a designed set of fields

The easy capture policy is to serialize all arguments and the return value. It also creates an uncontrolled copy of production data. Requests can contain credentials, personal information, private documents and text that becomes sensitive only in context. Telemetry often has different readers, export destinations and retention periods from the application database.

Choose fields from an allowlist and define how each value is produced. An input character count can explain a performance difference without exposing the input text. A result count and a truncation indicator can describe an incomplete response without retaining its contents. A bounded policy code can explain a decision without recording an entire customer profile.

On the left, opened crates have been dumped into one tangled heap on a warehouse floor, with house keys and an open wallet lying on top. On the right, closed crates stand in a row, each measured with a tape and tied with a small plain tag under a warm lamp.
Recording every payload in full costs CPU, copies passwords and personal data into a weaker store, and hides the two useful fields in kilobytes of noise. Sizes, counts and named fields from an allowlist keep what queries actually use.

An allowlist reduces accidental capture. It can't guarantee safety by itself. An approved error_message field can still receive a password embedded by a library. A permitted document title can still identify a person. Review the values and transformations as well as the field names, and apply redaction before export where possible.

Bounds are part of that policy. Set limits for string length, collections and attribute counts, and make truncation visible. A value cut off without an indicator can be mistaken for the complete value. A huge record that the backend silently drops can erase the evidence for the very input that needed investigation.

Exceptions need context and the same privacy discipline

An exception type is useful for grouping failures. A stack trace can show the path through code. A sanitized message can distinguish a timeout from an authorization rejection. These are often more useful than an unstructured line saying something went wrong.

Exception records can also contain user input, connection strings or response bodies. Capturing the fullest possible exception without scrutiny contradicts safe capture everywhere else. Preserve the diagnostic information the operator needs while removing secrets and unnecessary personal data. Restrict access to any detail whose retention is justified.

The caller and callee may need different records. A database driver can explain the local exception. The caller can explain which customer operation failed because of it. A shared trace connects those perspectives without requiring either component to pretend it knows the other's internal state.

Duration categories need an explicit meaning

A raw duration supports distributions and comparisons. A category can support a policy someone has already chosen, such as whether an interactive request exceeded its performance budget. Keeping both can make an alert understandable without discarding the measurement behind it.

Categories should identify the context they describe. A waiting user and an overnight batch have different constraints. A duration can be unacceptable in one and harmless in the other. Choose boundaries from the product's requirements, record the policy version where it matters, and avoid giving a label such as slow a different hidden definition in each service.

In front, a person in a coat waits at a lit shop counter, fingers drumming beside an hourglass that has nearly run out. Through a doorway behind, the same kind of hourglass runs on a shelf in an empty bakery at night, next to rows of rising dough.
A very slow call on a path where a person is waiting is a fire; the same duration in a nightly batch job is a fact. Categories anchored to human waiting, around a tenth of a second, one second and several seconds, tell them apart.

A category based only on recent behavior has another weakness. It can normalize a gradual regression. If the normal band follows the system as it gets slower, the dashboard can keep saying normal while the product's original promise becomes less true. Keep an agreed budget alongside historical comparisons.

The wrapper's job is to make these observations consistent. It can't decide every useful business field in advance. Some facts belong where the application makes the decision, attached to the active span. That addition leads to a storage question, because an identifier that's useful in an event can be damaging as a metric label.

Cardinality changes the cost of a metric label

An investigator asks which users experienced a failure. Adding a user identifier to a metric looks like a reasonable response. The same field can be useful on an event and expensive on a time series because the storage systems organize those facts differently.

Cardinality describes the number of distinct values in a set. For a labeled metric, the consequential quantity is the number of distinct combinations of label values. A time-series backend maintains separate series for those combinations. One additional label can therefore multiply the work the backend does instead of merely adding a column.

Count combinations before introducing a label

Suppose a service has six method values, eight status values, three regions and forty route templates. If every combination occurs, those dimensions produce 5,760 combinations. A quarter of a million user values could raise the theoretical product to 1,440,000,000.

Those are chosen example inputs, not measurements of a deployed service. Many combinations may never occur. A route may accept only one method, and a user may appear in only one region. The observed count is the number of combinations the workload actually produces. The product is an upper bound under the stated assumptions, useful for seeing how a large dimension can change the scale.

The arithmetic can be inspected without relying on a vendor benchmark:

6 methods × 8 statuses × 3 regions × 40 routes = 5,760 combinations
5,760 × 250,000 user values = 1,440,000,000 combinations

A request identifier is an even more obvious case. If nearly every request has a different identifier, a metric labeled by that identifier defeats much of the benefit of aggregation. The store maintains distinctions at the same granularity as individual executions while the chart still tries to present a summary.

Still from the animation. The same user id field added two ways. As a metric label it multiplies five existing dimensions into 1.44 billion series, drawn as a grid too fine to resolve. As a span attribute it adds one row to one record.
A metric grows a label at a time. Method, status and region tile a readable grid. The fourth label lands and the grid shatters into a haze too fine to resolve. The same label dropped onto a span adds one line to one record and nothing shatters.

The failure may appear away from the changed metric. A backend can spend more memory maintaining active-series indexes, more work resolving selectors and more storage retaining small series. Queries can slow, queues can grow or a process can reach a resource limit. These are possible consequences of excess series, not a guaranteed sequence for every backend.

A change review should therefore ask how many values a proposed label can take, how those values combine with existing dimensions and whether the resulting series have a use. A label with a bounded set of operation classes is different from a label containing arbitrary input text.

An attribute preserves an instance without precomputing every group

An event store can retain an identifier as a field on the record of an operation. It doesn't necessarily create a separate precomputed time series for every combination involving that identifier. An investigator can then filter the retained events when the question arises.

That difference explains why the same identifier can be inappropriate as a metric label and appropriate as a span attribute. It doesn't make the attribute free. Storage, indexes, query execution and the provider's pricing model still matter. A backend may impose limits or charge for the resulting data volume. Verify the behavior of the system receiving the field.

Privacy can also rule out a field regardless of its technical usefulness. A pseudonymous account reference may be enough to join authorized records without placing a name or email address in the trace store. Even a pseudonymous identifier needs an access and retention policy if it can be connected back to a person.

The design choice is to retain a distinction where it can be queried at a supportable cost and under the right authority. Removing every high-cardinality field would make many individual questions unanswerable. Promoting every field into a metric dimension would make aggregation expensive and difficult to operate.

Compare the affected population with a relevant control

Suppose failed requests share a deployment version. That observation is useful only in relation to the successful requests. If every request used the version, its presence doesn't distinguish failures. If failures disproportionately involve a particular flag or input class, that difference suggests a narrower investigation.

The comparison also needs a fair denominator. A region with more failures may simply have more traffic. An account class with a larger raw error count may have a lower error rate. Group by the relevant fields and compare both the failed count and the population that could have failed.

Two pantry shelves. On the upper one, a short row of spoiled jars with bulging lids all share the same cyan lid. On the lower one, a long row of good jars have plain lids, with only one or two cyan. A hand lifts a spoiled jar into the lamplight.
An error rate moving from 0.1 to 0.4 percent says something broke. Comparing failing requests with succeeding ones, field by field, shows what they share: a version, a flag, a region. Only a field recorded on every request can turn up in that comparison.

Attributes can reveal an association without proving its cause. A feature flag may correlate with failure because only a particular tenant group receives it, and that group may use a separate dependency. The event record helps identify candidate explanations. Code inspection, a bounded experiment or another direct observation is needed to distinguish them.

This is why width is useful beyond customer identifiers. Deployment version, dependency endpoint, configuration revision and input shape preserve different possible explanations. The relevant combination isn't always the one selected when the dashboard was built.

Early aggregation and later investigation require a deliberate trade

Aggregation reduces volume by retaining a summary. Detailed events retain distinctions that an investigator may need later. Both have a place. A team can keep inexpensive population metrics for alerting and a selected set of detailed traces for diagnosis, provided it knows which questions each dataset can support.

The loss needs to be explicit. Once individual records have been collapsed, a later query can't separate groups whose identities were discarded. An export standard won't reconstruct those identities. A longer retention period for the aggregate won't restore them either.

Choose metric dimensions for repeated population questions. Choose event attributes for distinctions that help explain individual executions and compare affected cohorts. Then inspect the actual storage and query behavior. The purpose of that discipline is to preserve enough context to explain a decision without turning every possible context into its own permanently maintained series.

Record the rule that produced the result

The pricing question can now be stated as a record-design problem. A duration and status describe execution. An investigator explaining the charge needs the rule bundle, the relevant inputs and the reason one policy applied while another didn't. Those facts belong together on the operation being investigated.

A reason code makes a rejected branch queryable. REGION_EXCLUDED is a bounded classification someone can count. An arbitrary explanation string can drift in wording, include private information or become difficult to group. Keep a stable code for the recurring condition and provide a controlled description where an operator needs one.

A decision has a version as well as an outcome

A rule can change without its name changing. A promotion may expand to another region. An eligibility threshold may move. Comparing decisions across that change without recording the policy version mixes different meanings in one group.

The decision record should identify the rule set that ran, not whichever version happens to be current when the query is made. A worker can lag behind a deployment, hold cached configuration or process a job created under an older policy. Capturing the effective version at execution time distinguishes those possibilities.

The relevant inputs should describe why the branch was eligible to fire. A customer tier, a feature state or a permitted age band may be sufficient. An entire customer record usually contains more than the query needs. Record a safe representation that preserves the distinction without exposing unrelated details.

Still from the animation. Two calls take opposite routes through the same pricing tree, one ending in applied and one in rejected, yet both report 8.1 ms and OK. Only the recorded attributes beneath them, promo applied true against a region excluded reject code, tell the two apart.
Two identical calls enter a branching pricing function, take opposite paths through six gates, and exit with the same duration and the same OK status. Timing instrumentation renders them as two identical bars. Decision instrumentation lights the divergent path on each.

A negative outcome deserves an explicit meaning. A promotion rejected by policy differs from one never evaluated, one skipped after an earlier error and one applied unsuccessfully. A single false boolean can collapse those states. Choose a field or outcome code that distinguishes them when the difference affects support or correctness.

This is the same data-model discipline I described in my Python work, deciding what moves through the system before treating it as a collection of scripts. The observation record is another consumer of that model. It should preserve the distinctions the application itself depends on.

Put observations where uncertainty enters or data changes

A span around every helper can produce a large trace whose rows say little. A span around a meaningful boundary can answer a specific question. The difference is whether the unit of work has an independent duration, outcome or failure mode worth examining.

External calls are natural candidates. A database query, a provider request, a queue operation or a storage read crosses into behavior the caller doesn't fully control. Record the attempt, its outcome and the context needed to connect it to the caller's purpose. The dependency can fail, stall or return data the caller can't use even when the transport succeeds.

Transformations deserve attention for a different reason. Parsing, validation, normalization, chunking, ranking and business-rule application can change what a record means. The useful observations include the input and output shapes, the effective parameters and any rejection or loss. A transformation that silently drops records can be more consequential than a dependency that throws.

A long road at night crosses dark countryside. Lanterns light only two places: a customs post at a bridge over a border river, where a cart waits at the barrier, and a watermill where grain sacks go in and flour sacks come out. The road between them stays dark.
Put a span where control crosses into something you can't reason about, such as a request, a query or a third-party call, and where data changes shape or a decision is made. The helpers in between ride along as attributes.

A local helper may add an attribute or event to the enclosing span instead of opening another span. That choice keeps facts attached to their operation. Create another span when it marks work an investigator needs to distinguish, not merely because another function was called.

Establish context before multiplying observations

Start with an entry point that names the operation people care about, such as a request handler, a command or a scheduled job. Give it identity and an outcome. Then follow it through its dependencies and transformations, checking that each observation remains connected to that root or linked appropriately.

Propagation needs to be part of this work from the beginning. An instrumented database call that can't be associated with the originating job leaves an investigator joining by timestamps. Adding more isolated spans doesn't repair that relationship.

An incremental implementation can follow this order:

  1. Identify the useful job and the entry point that starts it.
  2. Establish trace context and the safe operation-level attributes.
  3. Observe external boundaries and important transformations along that path.
  4. Verify context at asynchronous handoffs and link separate executions where needed.
  5. Derive population metrics and alerts from correctly understood outcomes.

The order describes dependencies between observations, not a mandate to delay all monitoring until tracing is complete. An obvious queue-capacity alert can remain useful while the trace path is being improved. The end-to-end question determines which missing connection matters next.

Make the observation answerable by someone else

A field's consumer needs to understand its meaning. A code named policy_3 may be stable and still require the original developer to explain it. Record the policy identity, keep its meaning discoverable and include the version needed to interpret old decisions.

Likewise, a branch record doesn't independently prove that the code followed the intended policy. It records what the instrumentation reports. A defect can exist in the decision or in the observation. Reconcile a meaningful sample against actual results and the governing rule before treating the record as authority for money, access or another consequential action.

The distinction becomes especially important when a decision changes data that will outlive the request. A trace might expire after the incident window. The transformed record may stay in a search index for much longer, and someone can ask about its quality after the trace is gone. That record needs durable lineage as well as an execution trace.

Lineage preserves how a record became its current version

A trace follows an execution. Lineage follows data through the operations that produced it. The two can share identifiers and checkpoints, but they answer different questions. An on-call engineer may need to know which dependency delayed a request. A retrieval engineer may need to know why a particular document was split and embedded in a way that now produces poor results.

I built a local retrieval system on Archon for a mixed collection of my own writing, textbooks and YouTube material. The source types needed different treatment. Replacing a static chunking approach with document-specific processing was part of that work. That experience supplies the practical context for this example; it doesn't establish a measured improvement from any particular lineage scheme.

Follow the transformations instead of jumping from URL to vector

Consider a video-ingestion path. A URL identifies an intended source. A metadata fetch returns a particular response. Parsing converts that response into the fields the application accepts. Transcript retrieval brings in text from another source. Alignment and chunking produce pieces. Embedding converts those pieces into vectors, and a write makes the derived records retrievable.

Each step can change the result while completing normally. A parser may omit fields by design or reject them according to its validation policy. A transcript may be incomplete. A splitter can use different overlap settings. An embedding operation can run against a different model or preprocessing revision. The final record doesn't necessarily reveal which happened.

Still from the animation. One record crossing eight stages from validate through store, changing shape at each, with a checkpoint hanging below every stage that names the parameters it ran under.
One record moves left to right through eight labelled transformations, changing shape at each. Without checkpoints the intermediate forms evaporate behind it and only the final shape survives. With checkpoints each stage leaves a marker carrying its parameters, and the chain stays walkable in both directions.

Rerunning the pipeline answers what the current pipeline does with the currently available inputs. It only reproduces the historical execution if the relevant inputs, versions, configuration and external behavior are also reproducible. An original URL is a reference to a source, not a guarantee that the same source bytes can be fetched again.

Lineage preserves those distinctions. It can show that one generation of chunks came from a particular transcript revision, using one splitter configuration and one model configuration. The investigator can compare that generation with records produced under other conditions instead of guessing from the final vector alone.

A checkpoint needs enough detail to distinguish outcomes

A timestamp proves that something recorded an event at a time. It doesn't say what data entered the stage or how the output differed. A useful checkpoint identifies the record and stage, the input revision, the output revision or reference, the parameters that mattered and the stage's outcome.

Shape measurements help localize a defect. Input character count, item count, rejected-item count, output count and vector dimension can reveal a large change without retaining every intermediate payload. The checkpoint should name the units. A size field that alternates between bytes and characters can falsely suggest a regression.

Along a workshop wall, five paper outlines trace a log becoming a chair leg, each with the tool that made that cut hanging beneath it: saw, axe, plane, chisel and rasp. Below, a grey strip of the same wall holds only five identical bare tacks.
A checkpoint that holds only a timestamp proves the record passed through each stage, which nobody doubted. One that keeps the shape in and out and the parameters each step ran with points to the stage that went wrong.

Parameters should identify the actual configuration, not a label pointing at a mutable default. A configuration hash or immutable version can be useful when its contents remain available under the same retention policy. A hash without the referenced configuration confirms a distinction but can't explain what changed.

Input provenance also needs more than the name of the service. Which source revision, fetch or cache generation supplied the input? Was the source complete? Did the stage use a fallback? A result assembled from several sources needs to preserve those relationships separately, because one stale contributor can explain an otherwise puzzling output.

Keep payloads in the storage designed to protect them

A checkpoint needn't duplicate the full source text. A protected content store can retain approved snapshots, while lineage records keep references and hashes. That arrangement separates diagnostic metadata from the material whose access and retention need stricter control.

Shapes and parameters can narrow a problem without proving semantic correctness. Two transcripts can have the same length and different words. Two chunk sets can have the same count and different boundaries. An investigator may need authorized access to the exact source and derived content to settle the question. The record should make that possible where justified, rather than claiming counts are always sufficient.

This also avoids making telemetry a second, less controlled document corpus. Copying a private transcript into every span can send it through exporters and storage systems that were never approved for that content. Recording a safe reference preserves the ability to investigate while keeping the content in its intended authority.

Retention follows the obligation to answer about a particular record

Sampling can preserve a useful picture of execution across a population. It can't guarantee evidence for a named record that was omitted. If the product promises to explain every retained document's derivation, its required lineage records need coverage for every document within that promise.

On the left, a person lifts a small net of fish from a crowded stream by lantern light. On the right, another person holds a lantern up to a tall archive shelf where most box positions are empty, searching one empty gap for the box they came for.
A small sample of traces still shows what the system did this week. Lineage gets questioned one record at a time, and the record someone asks about is reliably the one the sample threw away.

The requirement doesn't imply keeping every intermediate artifact forever. Define what must remain explainable, for how long, and under which privacy and deletion obligations. Some payloads may expire while approved metadata remains. Other metadata may also need deletion. Make the resulting investigative limits explicit.

A complete lineage record for a required population is different from a sampled diagnostic trace. Store each where its retention and query needs can be met. Lineage often belongs beside the application data because record identity, versions and retention can then be managed together. A short-lived trace backend may still provide a useful link for the immediate investigation.

Version the record's history rather than overwriting its explanation

Reprocessing creates another generation. Replacing the lineage fields with the newest configuration would make old results appear to have been produced by code they never encountered. Preserve the relationship between each retained output generation and the configuration that produced it.

The same care applies to failures and partial writes. A stage marked complete should identify the result that actually persisted. A successful model call followed by a failed database write is a different outcome from a retrievable new generation. Recording only the model's success can hide missing work downstream.

Lineage therefore complements the execution trace. One explains a particular data product across time; the other connects work across the services that performed it. A pipeline needs both perspectives when a defect can begin in one dependency and appear much later in another component.

Propagated context makes distributed failures reconstructable

A component can behave according to its local rules while contributing to a failed workflow. A queue accepts work, a worker retries a dependency, a connection pool enforces its limit and a health check reports a timeout. Looking at each component separately can leave an investigator with several plausible local explanations and no account of how they relate.

Consider a hypothetical ingestion cascade. A transcript provider slows down. Downstream work waits. The provider recovers and releases delayed responses, so chunking and embedding receive a burst. Database writes contend for a connection pool. Requests sharing that pool now wait as well, and a readiness check using the same constrained resource can fail.

The visible symptom appears at the database or load balancer. The initiating delay happened upstream. This is a possible mechanism, not a claim about an observed incident. Establishing that it happened requires evidence connecting the delay, the burst, the resource pressure and the affected requests.

Timestamps alone leave several explanations open

Events near each other in time may be related. They may also be independent effects of another cause, such as a traffic increase. Clock differences and buffering can further complicate apparent order. Joining records because their timestamps look close is a hypothesis about identity and causality.

Trace context preserves an explicit relationship between operations. A caller passes context with a request, and a receiving service uses it when creating its own spans. The shared trace identifies the logical execution; parent and link relationships explain how units of work connect.

Still from the animation. Seven components in a row from transcript service to load balancer, every one reporting OK, with the alarm firing at the right end and a single traced thread running back through all seven to a slow third party at the left as the origin.
Seven labelled components sit in a row, each showing a locally healthy indicator. A slowdown enters at the left and propagates. Every component stays individually green while the alarm fires at the far right. Then one thread lights back through all seven and names the origin.

OpenTelemetry documents injection and extraction of context across process boundaries. Supported instrumentation can do much of this work. The important check is whether the relationship survives the actual path the application uses, including any custom transport or worker code.

An identifier printed in two places is weaker than a correctly propagated span relationship. It can help an operator search, but it may omit parentage, sampling state or other context. Use the supported propagation mechanism rather than rebuilding a partial one from a copied trace-ID string.

Queues need an explicit handoff

An asynchronous job can outlive the request that created it. The job envelope or message metadata needs to carry the appropriate context, and the worker needs to restore or link that context when processing begins. The mere presence of instrumentation on both ends doesn't ensure that they agree about the relationship.

Batches require additional care. A worker may combine messages from several origins, and one parent can't describe every cause. Links can preserve associations with those upstream executions. The application should also retain job and attempt identity where retries or redelivery can create multiple executions of the same intended work.

Relay runners pass one glowing baton hand to hand along a track at night. At a waiting bench the handoff fails: the next runner is sitting down and the baton lies on the ground, its glow fading. Two runners beyond the bench stand empty-handed.
One trace identifier, created at the entry point and passed through every hop, turns a slow-search complaint into a lookup. At a queue or a worker pool it falls to the ground unless the job carries it and the worker picks it back up.

The distinction between a job and an attempt matters for diagnosis. An operator may need to see that one job caused several provider calls and eventually produced one persisted result. Without those identities, a trace query can look like several independent jobs or one unexplained long duration.

Thread pools, custom schedulers and manually managed tasks deserve the same check. Context can be available in the submitting function and absent in the executing one. Inspect the supported behavior of the runtime and framework instead of assuming every asynchronous boundary copies execution context automatically.

Record errors where they occur and where they matter

A stack trace describes the local code path at an exception. A distributed explanation also needs the caller's intent and the earlier observations that led to the exception. The database can report its local write failure. The caller can record that the write was meant to publish a particular generation of search records.

Attach the error to the affected span with a groupable type and safe diagnostic details. At the caller, record the failed dependency outcome and the effect on the intended operation. Preserve the connection between the records so an investigator can distinguish an initiating failure from its downstream consequences.

Retries need to remain visible even when they succeed. A final success status can hide failed attempts and backoff. Record the count and meaningful attempt events. Where the instrumentation convention gives each client attempt its own span, keep those spans connected to the logical operation instead of imposing an arbitrary ban on attempt spans.

A side view of a pond at night. Above the waterline a duck glides calmly over smooth water under a soft moon. Below it, the duck's feet paddle hard, churning a long trail of bubbles and swirls through the water.
A call that succeeds on its fourth attempt reports the same status as one that worked the first time. Record retries as span events and a rising retry rate shows the strain while every status still reads success.

A rising retry rate is a reason to investigate. It doesn't prove a future outage, but it can reveal dependency stress before final failure counts change. Retries also consume quota and can add load. The eventual success of one job doesn't establish that its retry policy is safe for the system around it.

Shared resources require a view across products

Separate products can compete for one model allowance, database pool or storage tier. A product-scoped dashboard may show latency while hiding the other job that consumed the resource. Shared resource identity and caller attribution let operators compare the competing workloads.

That comparison doesn't require pretending independent jobs share one trace. Preserve each execution's identity, then group them by the shared dependency, quota account or resource pool. Context follows causally connected work; resource attributes support comparisons between work that merely competes.

Access boundaries still apply. A common telemetry backend shouldn't automatically give one product's team access to another product's private payloads. Use safe resource identifiers and authorized views. OpenTelemetry's propagation guidance also warns about incoming untrusted context and outgoing baggage. A trace header isn't proof of authorization, and baggage shouldn't carry secrets.

The practical result is a path an operator can inspect. They can locate the affected operation, follow its recorded calls, distinguish attempts and compare pressure on a shared dependency. The next question is whether the system is consuming a finite external allowance faster than the workload can sustain.

Quota accounting connects external limits to the work spending them

An external service can impose a daily allowance, a request-rate limit, a concurrency limit or a contracted volume. These constraints have different units and recovery conditions. A counter called api_usage won't distinguish them unless its meaning is defined.

The useful observation connects consumption to an operation. Which job made the call? Which allowance did it spend? How much did that operation cost in the provider's units, and what evidence supports that amount? A billing total can show that a limit was reached without answering any of those questions.

Establish the authoritative limit and its window

A published default is a starting point, not necessarily the allowance on the account running the workload. Plans, approved extensions and provider changes can alter it. Record the effective limit from the appropriate account configuration or provider response, together with its unit and reset behavior.

A fixed daily window differs from a rolling request window. A concurrency cap differs from a volume allowance. Waiting for a reset can help with the first kind of exhaustion and do nothing about a permanently saturated connection pool. The error response and the limiting resource need to remain distinguishable.

The same applies to model usage. Input tokens, output tokens, cached input and provider requests may be metered differently. Record the provider's actual usage where available. An estimate based on source length should remain an estimate with its assumptions, not become a measured charge merely because the system wrote it into a span.

Derive a projection from consumption, with assumptions visible

A known ceiling and a comparable consumed amount give a remaining allowance. A recent consumption rate can suggest when that allowance will be exhausted. The projection is conditional on future work resembling the observed window.

A scheduled backfill can invalidate that assumption. So can a changed retry rate, an unexpectedly large input or another product using the same account. Retain the measured window and the workload attribution behind a projection so an operator can see why it moved.

Still from the animation. The same day of consumption drawn twice. Untracked, the curve meets the ceiling and stops dead at 100 percent spent with no warning. Tracked, tier lines at 80 and 90 percent and a projected crossing surface the same limit at 75 percent spent, while budget remains.
A day's quota drains as a bar. The untracked run meets the ceiling with no warning and stops. The tracked run shows the same consumption with a projected crossing line moving ahead of it, so the crossing is visible while there is still budget left to change course.

A useful quota record separates the following facts, because each supports a different decision:

  • Consumption in the provider's defined units, within the relevant window.
  • The account, resource and effective ceiling to which that consumption applies.
  • The calling job, operation and attempt that spent it.
  • The provider's response or usage evidence, including uncertainty where it remains.
  • The reset or recovery condition, and any required reservation for other work.

A warning should lead to an action the operator has authority to take. They may defer a backfill, reduce concurrency, stop an optional workload or request an approved allowance change. Observing consumption doesn't authorize new spend or permit dropping important work without a policy.

Cost per useful item exposes work hidden by request counts

A pipeline can make several calls per document. Retries, pagination and enrichment add attempts. Counting API requests therefore describes provider activity, while counting successfully persisted documents describes useful output. Their relationship is a capacity-planning measure.

Keep both populations. A cost-per-document figure needs a defined denominator, such as completed documents in the same measurement window, and a clear treatment of failed or partially completed work. Otherwise the unit cost can look better when expensive failures are omitted or when successful calls are counted as completed jobs.

Input classes can explain variation. A long transcript and a short note may consume different amounts. A source requiring several pages of retrieval differs from one available in a single response. Grouping those operations by safe shape fields supports a more useful forecast than one average over unrelated work.

Scheduling only helps when the limiting resource has spare capacity elsewhere

A consumption history can reveal that unrelated jobs compete during the same period. Moving deferrable work can reduce contention. That's a possibility to test against the resource's actual rules, not a universal fix.

The same river ferry twice. On the left, at midday, carts crowd the landing and the overloaded ferry cannot leave the bank. On the right, in the evening, the same carts are spaced along the road and the ferry carries two at a time under a lamp.
Untracked, a daily quota is a wall hit at whatever hour scheduled jobs happen to pile up. Counted per job against the ceiling, it becomes a timetable: non-urgent work moves to the quiet hours and batch sizes fit the real cost.

Moving work within a day can help a concurrency or rate constraint. It won't increase a fixed daily allowance. Delaying work until after a reset changes which window pays for it, but the work still consumes quota. A queue can make that delay manageable only if its retention, freshness and completion obligations permit it.

A schedule should also preserve the useful job. A backfill that runs so slowly its source expires has avoided a quota error while failing its purpose. Record completion age and backlog alongside consumption. Resource efficiency has to be evaluated against the output the system owes.

Put accounting beside the external call

The moment of the call is where the system knows its caller, purpose, attempt and input shape. Record permitted consumption evidence there, then aggregate by job and allowance. Reconstructing those relationships later from a monthly bill may be impossible if the provider never received the application-level identity.

The accounting path needs its own failure behavior. A missing usage response should appear as unknown, not zero. A telemetry outage shouldn't silently imply that no work consumed the allowance. Where a hard budget must be enforced, its authoritative accounting belongs in a mechanism designed for that obligation, with telemetry providing explanation around it.

An observation can support a decision without being strong enough to enforce it. The same caution applies to performance, where one recorded duration needs a population and a history before it can explain what a user experienced.

Latency needs a distribution and a comparable history

One duration answers how long one recorded operation took. An average summarizes a population. Neither by itself describes the spread of experiences within that population. A service can have a stable mean while a small group of requests becomes much slower.

Consider a hypothetical latency distribution. A population with 98% of requests taking 90 milliseconds and 2% taking 15 seconds has a mean of 388.2 milliseconds. Those values are hypothetical. A second service could have every request take 388.2 milliseconds and share that exact mean while producing a very different experience.

A tail contains individual operations worth examining

The calculation is a weighted mean, with both durations expressed in the same unit:

0.98 × 90 ms + 0.02 × 15,000 ms = 388.2 ms

The mean doesn't tell an operator which requests were in the slow group. A distribution shows that a tail exists. Individual events with relevant attributes can reveal whether that tail is concentrated in an input class, region, dependency or account group.

Still from the animation. Two services sharing one average. Service A is a tight bell around it. Service B is a narrow spike at 90 ms with a tail of scattered requests running off the right edge of the frame toward a p99 of fifteen seconds.
Two services report the same average as a single identical dot. The dot expands into the distribution behind it and the two shapes have nothing in common: one a tight cluster, one a fast spike with a long tail running far off the right of the frame.

A percentile identifies a position in an ordered set of measurements. A p95 near a particular duration says roughly 95% of observations are at or below it, subject to the estimator and ties. It doesn't identify a fixed group of users, and one user can contribute many requests. Keep that distinction when translating request performance into claims about customer experience.

The measurement window matters too. A percentile over a quiet period can differ from one over a burst. A low-volume operation can have a noisy tail estimate. A chart should identify the operation class, window and measurement method before its number is compared with another chart.

Use attributes to separate different reasons for slowness

A slow request involving a large input might be expected to do more work. A slow request with the same input shape as its peers suggests another explanation. Record the distinctions needed to make that comparison rather than treating every operation sharing a route as equivalent.

A queue introduces another distinction. Execution time after a worker starts differs from the time a customer waits from submission to completion. Both can be useful. An efficient worker can sit behind a long backlog, and its local duration won't describe the delay the user experienced.

Dependency timing adds context without resolving every cause. A long outbound span can include network wait, provider processing and retry behavior. The application's record may not separate all of them. State the interval being measured, then follow the evidence available at the dependency boundary.

An observed association should remain a candidate explanation. Requests in one region might be slower because that region receives larger jobs. Comparing equivalent input classes can challenge the first interpretation. High-cardinality attributes help an investigator do that work; they don't replace it.

A threshold and a trend answer different performance questions

An alert can report that a duration crossed an agreed limit. That's valuable even if the cause is gradual. The claim that thresholds can't detect a slope is too strong. A gradual regression can eventually cross a threshold. The failure is relying on that crossing as the only evidence of change.

A system can worsen materially while staying below a generous limit. It can also have its adaptive normal band move with the regression. Comparing current behavior with a retained historical baseline helps identify that change before an absolute budget is violated.

A row of lamp posts runs along a straight road at night. The first stands upright and each one after leans a little more, until the last few tilt sharply. A tripwire with a bell hangs silent across the road. A person at the far end looks back along the row.
An endpoint that slows from a hundred milliseconds to five hundred over months never trips an alert, because no single month changes much. A daily ninety-fifth percentile kept for years shows the lean as soon as today is compared with the first post.

Keep recent detail for investigation and an appropriate longer history for trends. Coarser summaries can make long retention affordable, but choose what they preserve deliberately. A daily percentile is useful for a daily comparison. It generally can't be averaged into a correct monthly percentile. Retain a suitable distribution representation if later aggregation is a requirement.

The baseline must describe comparable work. A product that added a much larger input class can have a higher overall latency without making existing jobs slower. A deployment that changes the population can produce the same effect. Record enough context to distinguish a changing mix from a changing implementation.

Performance budgets should express the product's obligation

A user waiting at checkout and a background indexing job have different consequences for the same elapsed time. The budget should state the operation, the population and the condition it covers. A generic label such as very_slow can hide those differences.

Choose the budget before an incident creates pressure to explain it away. Then keep it distinct from a descriptive baseline. A baseline says what the system has been doing. A budget says what it's required to do. Both can be useful in the same view.

Human waiting is relevant to interactive work, but a universal set of perception thresholds won't settle every product requirement. Task complexity, feedback and the ability to continue other work change the experience. An observed completion rate or a defined service objective can be more useful than borrowing a number without its context.

An investigator should finish with a narrower statement than the system is slow. They should be able to identify the affected operation, the measured interval, the changed population and the time over which the change occurred. That gives the next correction a defined target. It also exposes a limit of per-operation success, because a workflow can fail often even when each step looks individually respectable.

Workflow reliability depends on how step outcomes relate

A workflow that requires every step to succeed has a series structure. A successful fetch followed by a failed parse still fails to deliver the intended result. Reporting the fetch's success rate alone can make an incomplete workflow look healthy.

The familiar multiplication example is useful with its assumption attached. Five independent steps that each succeed with probability 0.9 have an end-to-end success probability of 0.59049. That's about 59%. The inputs are hypothetical, and independence is part of the example rather than a fact to assume about a real pipeline.

Multiply conditional probabilities when steps depend on earlier success

The general relationship follows the sequence of work. The probability that all steps succeed equals the probability that the first succeeds, multiplied by the probability that the second succeeds given the first's success, and so on. Those conditional probabilities reduce to independent marginal probabilities only when the relevant independence assumption holds.

For the independent example, the calculation is:

P(all five succeed) = 0.9 × 0.9 × 0.9 × 0.9 × 0.9
                   = 0.59049

A production pipeline often measures later stages only for items that reach them. That conditional population can be the right one for the chain calculation, provided each stage uses the same workflow cohort and a consistent definition of success. Mixing a first-stage weekly rate with a later-stage rate from another workload doesn't produce a meaningful whole.

Still from the animation. Five steps in series, each labelled ninety percent, with survivors falling from a hundred to 90, 81, 73, 66 and finally 59, so four workflows in ten fail and no single step looks like the reason.
A hundred requests enter a chain of five gates, every one of them labelled with the same respectable ninety percent. A tenth of whatever is left falls out of the bottom of each gate and settles on the floor. Fifty-nine arrive. Then a shared database lights underneath three of the five, and those three stop being independent of each other.

Retries complicate the unit further. An attempt success rate differs from the fraction of logical jobs eventually completed. A stage may fail several attempts and still deliver its result. The end-to-end measure needs to identify which outcome it counts and how long a job is allowed to remain pending.

Shared failures change the model without a universal direction

A shared database, a release or a traffic spike can make component failures dependent. That observation invalidates an unexamined independence calculation. It doesn't establish that the true series-system success probability must be lower than the independent product.

A simple counterexample makes the limit clear. Suppose all five components succeed together 90% of the time and fail together the rest. Each component's marginal success rate is 90%, and the whole workflow also succeeds 90% of the time. Multiplying the marginals as though they were independent would incorrectly predict about 59%.

The example isn't a recommendation to share failure modes. Simultaneous failure can have a much worse operational consequence than isolated failures, and shared dependencies can remove the benefit expected from redundancy. Probability of success and severity of a bad period are different questions. Both need the dependency structure stated.

Use the actual conditional relationships or measure end-to-end outcomes directly. Then examine shared causes to understand concentration, recovery and the limits of a proposed failover. The correct response to dependence is a better model, not an automatic adjustment in whichever direction makes the warning sound stronger.

End-to-end and per-step measures explain different parts of the problem

An end-to-end completion rate tells an operator whether the useful job happened. Per-step measures locate where work was lost or delayed. Keeping both distinguishes a widespread collection of modest losses from one stage that dominates failures.

The same overall completion rate can arise through different paths. One workflow can lose a little work at several transitions. Another can pass most stages reliably and lose a large share at one boundary. The aggregate result doesn't identify which correction offers the greatest benefit.

Two hoses run from the same tap to two buckets filled to the same low level. The upper hose leaks a little at five evenly spaced points along its length. The lower hose is whole except for one wide split near the middle that sprays hard.
A workflow that succeeds fifty-nine percent of the time could have five steps at ninety percent, or four near perfect and one at sixty-two. The bucket looks the same either way; per-step success rates show which hose you are holding.

Measure stage entry, accepted input, successful output and rejection or failure reasons with consistent identities. A discrepancy between one stage's output and the next stage's input can expose a handoff defect that neither stage's local status reveals. A successful write to a queue doesn't prove a worker consumed the job.

Prioritize corrections using those measurements and the cost of changing them. Improving a weak stage can improve the overall result substantially, but the gain depends on how its failures relate to other stages. The easiest function to optimize isn't necessarily the one limiting delivery.

Redundancy and retries need a distinct failure path

For independent alternatives where any one success is sufficient, the probability that all alternatives fail is the product of their failure probabilities. Subtracting that result from one gives the probability of at least one success. This is the parallel counterpart to a series calculation.

Real retries rarely provide perfectly independent alternatives. The same invalid input, exhausted allowance or unavailable database may fail again. Backoff can help a transient overload recover, while immediate retries can add pressure to the same resource. Observe the reason, attempt count and elapsed delay before assuming the policy improves reliability.

Retries around consequential actions also need protection against duplicate effects. A timed-out payment request may have succeeded remotely before the response was lost. Retrying without the appropriate idempotency mechanism can produce another action. Telemetry should connect the attempts, but the application has to enforce the business safety rule.

An operator needs a result they can trust at the workflow level and enough stage evidence to explain it. Retaining every detail of every execution may be expensive. Sampling can reduce that cost, but it changes which conclusions the retained traces can support.

Sampling reduces volume and changes the evidence you retain

Instrumentation does real work. Creating spans allocates memory. Attributes need encoding. Export consumes network capacity, and collectors need resources to process what arrives. The relevant overhead depends on the runtime, configuration, workload and amount of information retained. A universal percentage is a poor substitute for measuring that path in the application that will pay for it.

Measure the consequences separately. Added CPU, request latency, memory, export volume and backend cost can move differently. An exporter can use more background CPU without adding the same amount to request latency. A large buffer can protect the request path temporarily while increasing memory and the amount of evidence lost on a crash.

Move export off the request path and bound the backlog

Batched asynchronous export can avoid making a customer request wait for each telemetry network call. It still consumes resources. Batch size, queue capacity, flush intervals and shutdown behavior determine when records leave and what happens if the destination becomes unavailable.

A synchronous exporter puts its work directly into the observed path. That can be useful for a controlled diagnostic, but it needs an explicit reason in a production configuration. A setting left over from debugging can make the instrumentation contribute to the latency being investigated.

On the left, a cashier leaves the counter mid-sale to walk a single coin across the street to a bank while the queue of customers grows. On the right, the cashier keeps serving and drops coins in a drawer, and a courier carries one full bag out the back.
A synchronous exporter sends telemetry across the network inside each request, so users wait for it on every call. Batched asynchronous export does the same work out the back, which is why restoring that default after debugging matters.

Bound export queues and make dropped records visible. An unbounded queue can turn a telemetry outage into an application memory problem. A bounded queue can lose evidence under pressure. The choice should protect the system's primary job while reporting the limitation, rather than silently claiming complete traces.

Span selection is another cost decision. Boundaries and important transformations give investigators meaningful units of work. Instrumenting every small helper increases volume and can make the trace harder to read. Before adding a span, identify the question that needs that separate operation.

Head sampling chooses before the full outcome is known

Head sampling makes a decision early in the trace. A consistent probability policy can keep a fraction of traces while propagating the decision so connected services follow it. This avoids much of the recording and export work for dropped traces when the decision happens at the SDK.

The limitation is information available at that point. A sampler can't select a trace because of an error that hasn't occurred yet unless another policy or later stage retains it. A rare failure can fall into the dropped population. Increasing retained detail on the traces that remain won't recover that missing execution.

OpenTelemetry's sampling guide distinguishes early selection from selection using later spans. It also identifies the operational cost of sampling itself. Low-volume workloads may gain little from introducing a complex sampling system.

A uniform random trace sample can support population estimates with sampling uncertainty. The unit matters. If each trace represents a different number of requests or jobs, a trace-level sample isn't automatically a simple random sample of those other units. Define the population being estimated and account for its selection process.

Tail sampling uses outcomes and needs state to do it

Tail sampling examines all or part of a trace before choosing whether to retain it. A policy can favor traces with errors, unusual durations or attributes associated with a new deployment. Those records can be particularly valuable for investigation.

The collector must hold enough state and wait long enough to evaluate its criteria. Late spans, split routing and long-running traces can leave it judging incomplete evidence. A distributed collector design needs to route the relevant trace data consistently and handle resource pressure explicitly.

Still from the animation. One stream of traces splitting at a fork. The head path decides at the root and keeps a proportional slice, discarding the one trace that mattered. The tail path holds all ten spans in memory, waits for stragglers, and selects the rare ones once the facts are in.
One stream of traces meets a fork. On the head path the decision lands at the entry point and a proportional slice survives, including one red trace that is discarded before anyone could know it was red. On the tail path everything is buffered, the red traces are selected, and the buffer itself is shown as the cost.

Tail sampling also can't recover traces already discarded by an earlier head sampler. Combining the two can control volume, but the later policy operates on the population that survived the earlier one. A policy described as keeping every error needs that qualification if earlier decisions can remove errors before the tail sampler sees them.

These constraints make the sampling component part of the observed system. Track its queue pressure, decisions and loss. An apparently quiet error population can mean fewer errors, a changed policy or lost telemetry. Without evidence about the collection path, the application chart alone may not distinguish them.

A selected debugging set is a biased denominator

A policy that retains all failures and only some successes deliberately enriches the stored dataset for failures. Counting failures as a fraction of that stored set overstates the fraction in the original workload unless the analysis correctly accounts for selection probabilities.

For example, keeping every error and 5% of successes multiplies the failure-to-success odds in the stored set by twenty. That doesn't mean the resulting error percentage is simply twenty times the original percentage. The denominator changed too. The example describes a sampling policy, not a measured production result.

A harvest cart heaped with apples stands in the middle. On the left, a crate holds only the bruised and split apples pulled from it, with a magnifying glass on top. On the right, a person counts a small basket filled by one random scoop from the cart.
Keeping every error and five percent of successes leaves errors twenty times over-represented. That set is right for studying what failed and wrong for measuring how often things fail, so reliability rates come from an unbiased sample.

Compute service-level indicators from a source suited to the population measure, such as correctly defined metrics collected across the required traffic or an appropriate unbiased sample. Selected traces can explain failures alongside that measure. Their concentration of unusual work is useful for debugging precisely because it differs from the original distribution.

The same warning applies to latency. Selecting slow traces creates a retained latency distribution that's intentionally different from all traffic. A p95 over that set describes the selected set. Label it accordingly rather than presenting it as the product's p95.

Choose retention according to the question being preserved

A sample can support a population trend while failing to retain the one execution a customer asks about. Required audit or lineage records have a different obligation from diagnostic traces. Keep the necessary complete records for their required population and period rather than assuming a sampling policy suitable for debugging satisfies every evidence need.

There's no single correct retention rate. Workload volume, event rarity, investigative questions, privacy obligations and cost all matter. State what the policy keeps, what it can miss and who reviews those tradeoffs. A cheaper trace store that can't answer the intended question has saved bytes without meeting the engineering requirement.

The budget discussion can then use real quantities, the cost of observations that answer a named set of questions, and the cost of operating without those answers.

Budget for useful evidence and reusable operating patterns

An observability bill can grow with ingested bytes, retained events, active time series, query work or a combination of those quantities. Self-hosting changes who operates the infrastructure; it doesn't remove storage, compute or engineering cost. The appropriate budget starts with the evidence the product needs and the actual terms of the system retaining it.

A low bill alone doesn't establish efficiency. A high bill alone doesn't establish waste. A quiet application, a regulated archive and a high-volume distributed service have different obligations. Compare cost with coverage, investigative usefulness and the consequences of missing evidence before judging either number.

Separate a measured saving from illustrative arithmetic

A claim that tracing turns an investigation into a shorter one needs evidence about the investigations being compared. Incident complexity, staffing, code changes and other engineering practices can affect the result. A guessed hourly rate multiplied by guessed hours produces an illustration, not a measured return on investment.

Three dice stacked into a narrow tower that leans slightly on a plain plinth. A polished trophy cup balances on top, gleaming under a single spotlight, with wide dark space all around it.
Three hours against thirty minutes at a hundred and fifty dollars an hour is arithmetic on three guesses. The result looks like a finding and carries the weight of an anecdote.

A useful local cost model records the engineering effort actually spent, the service impact relevant to the incident and the telemetry cost attributable to the system. It can also record which missing observations prolonged diagnosis. That evidence supports a narrower conclusion than a universal percentage improvement, but it's a conclusion the team can inspect.

Avoid crediting instrumentation for every improvement accompanying it. Better tests, a simpler interface or reduced dependency coupling may have changed at the same time. Retain the uncertainty where causal attribution can't be isolated. The purpose of measuring the return is to make the next investment better, not to make a tool look indispensable.

Spend on distinctions that support decisions

A wide event can answer more questions than a narrow one, but useful width doesn't guarantee a proportional price increase or a proportional benefit. Event-priced and byte-priced systems can reward different designs. Query and retention charges can matter as much as ingest.

Measure representative record sizes and volumes. Separate normal traffic from exceptional records, and account for spikes. Review what's discarded before export, what remains queryable and what expires. A bill reduction achieved by removing the field needed to explain expensive failures needs to be visible as a coverage decision.

A winding mountain road at night, lined along its whole length with plain low guardrails. At one sharp bend, a large searchlight on a truck throws a bright beam over the edge, lighting the slope below while the rest of the mountainside stays dark.
Vendors bill by bytes, events and series, so every useful attribute costs money. Cheap metrics stay on everywhere as guardrails for alerts and autoscaling; wide events are the searchlight aimed at the bends where unpredictable questions will come up.

Population metrics remain useful for alerting, capacity and service objectives. Detailed events support investigation. Keep each for the job it performs. The distinction follows the information retained and the required query, not a ranking in which one signal proves engineering maturity.

Operator effort belongs in this budget too. A dataset with the right fields can still require a specialist to write every query. Stable names, saved investigative views and clear definitions can reduce that dependence. A shared query that remains correct after a release is an operational asset; one that silently mixes changed field meanings is a liability.

On the left, a dim warehouse under one weak bulb, where three people holding candles search along tall shelves, one of them up a ladder. On the right, the same warehouse is lit along every aisle, and one person lifts a box straight off the shelf.
An observability bill at a low single-digit share of infrastructure usually signals under-instrumentation, with the difference paid in engineering hours nobody attributes to it. A bill above thirty percent usually means watching indiscriminately.

A small pattern vocabulary makes the method portable

The implementation can change while nine recurring jobs stay recognizable. They're useful when their names point to specific responsibilities rather than becoming labels applied to every piece of code.

The patterns fit together as follows. The examples describe related uses, not evidence that a particular application already has them:

Pattern Responsibility Related use
Decorator or wrapper Apply a shared observation policy around work Request middleware
Checkpoint Record a stage's identity, parameters and outcome A versioned ingestion transition
Metrics aggregation Summarize a defined population Duration distributions
Correlation Connect records about related work Job and attempt identity
Health check Report a defined aspect of service operation Liveness or readiness
Trace propagation Carry execution context across a boundary Request headers or message metadata
Metrics taxonomy Keep names, units and meanings consistent A reusable service dashboard
Error genealogy Preserve connected evidence about a failure's ancestry Caller, callee and retry records
Performance budget State an agreed limit and response policy An operation's latency objective
NINE PATTERNS, THREE JOBS CAPTURE CONNECT AGREE decorator checkpoint correlation propagation genealogy aggregation taxonomy health check budget
The nine sort into three jobs, and the difficulty runs downward. Capture is a library. Connection is plumbing. Agreement is a standing commitment that decays without maintenance.

A wrapper and a checkpoint capture different information. Correlation and propagation connect that information. Taxonomy and budgets make it interpretable across teams. The terms are useful because they identify the missing responsibility in an existing system without requiring the same library everywhere.

Health checks need a question and a consumer

A process answering a lightweight endpoint has demonstrated that it can answer that endpoint. That can be an appropriate liveness signal. It doesn't establish that the process can complete every useful job, and it shouldn't be labeled as though it did.

Readiness asks whether an instance should receive a particular class of work. Dependency state can be relevant to that question. A richer diagnostic endpoint can expose which dependency is unavailable or whether configuration is current. Those are different consumers and can justify different probes.

A lighthouse at night. A keeper on the gallery halfway up waves a small bright lantern, while the great lamp at the top stands dark. Out on the water, a small boat steers toward the lighthouse, heading for rocks hidden in the dark.
A health endpoint that returns ok proves the process can answer, and little more. A service can pass that check in a millisecond while the database or API it needs is unreachable, and the load balancer keeps sending it traffic.

Combining every dependency into a liveness check can make an outage worse if a temporary shared dependency failure causes healthy processes to restart. A readiness policy can also remove capacity too aggressively when every instance shares the same dependency. Probe design has consequences beyond the correctness of the returned JSON.

Keep checks bounded and safe. A probe that performs expensive production work can consume the resource it's measuring. A diagnostic endpoint that exposes connection details or private state can create an access problem. Record the intended consumer, the condition being tested and the response expected when it fails.

Agreements need maintenance as well as code

Metrics taxonomy is an agreement about meaning. A duration field needs a unit, an operation identity and a known population. A dashboard that can compare services depends on those agreements continuing to hold as teams change their implementations.

A performance budget is an agreement about action. It needs a scope, a measurement window and a response when violated. An observed threshold with no owner may produce alerts that nobody can safely act on. A limit redefined after every regression ceases to express the original requirement.

Review these agreements when a new operation, dependency or deployment changes the workload. Reuse the existing vocabulary where it fits, and distinguish a genuinely new concept from a private synonym. The result should be easier for a new operator to interpret, not merely more uniform on a diagram.

These patterns work best when the application's boundaries have meaning. An architecture that separates business decisions from external effects gives observation a place to attach without forcing every helper to become its own service.

Logical boundaries give observations a clear meaning

A frontend interaction, a data contract and a computation can fail in different ways. The user can wait too long while every API call succeeds. A backend can return a well-formed object containing the wrong generation of data. A computation can run within its time budget and produce an incorrect result.

Separating those concerns makes the questions clearer. The frontend needs evidence about the experience people received. The backend needs evidence about accepted inputs, preserved state and contract outcomes. The computational core needs evidence about its inputs, configuration, performance and correctness. Those concerns can exist inside one deployable application.

Separate responsibilities before deciding to separate services

The useful boundary is a place with a defined input, output and ownership of effects. It might be a function, module, process or service. Moving code across a network adds deployment and communication costs; it isn't a prerequisite for observing meaningful work.

A module that owns database access can provide a clear observation point inside a monolith. A separate service with several undocumented responsibilities can remain difficult to interpret. The topology by itself doesn't establish the quality of the boundary.

Still from the animation. One system divided into frontend, backend and core, each holding its own scatter of spans and its own question: response time, data integrity, accuracy. The two boundaries between them are drawn as lines where every crossing becomes an observable event.
One undivided mass with work moving through it has no place a span could go. Two boundaries drop in and the mass resolves into three labelled zones. Every crossing now lights as an observable event, and each zone shows a different question written across it.

Different deployment cadences may become possible when interfaces are narrow and responsibilities are distinct. That's an architectural option with operational obligations. A shared schema, backward-compatibility requirement or coordinated migration can still couple deployments. Telemetry can reveal a regression; it doesn't remove those dependencies.

Choose separation according to the system's actual pressures. The observability benefit's that an investigator can associate an observation with a responsibility. It doesn't justify a rewrite whose cost and failure modes exceed the problem being solved.

Pure computation and external effects need different evidence

A pure function produces its result from its explicit inputs without changing external state. That can make reproduction easier when the code, inputs and relevant execution conditions are preserved. An external API call depends on another system's state and behavior at a moment in time.

The distinction helps locate deep observation. Capture safe evidence at reads, writes and external calls, then connect it to the transformations performed between them. A function that reads a clock, calls a model provider or accesses a mutable database is using an external effect even if its return type looks simple.

Embedding is a useful example of why the boundary needs precision. A local, pinned computation under controlled conditions may be reproducible. A call to a hosted embedding service can involve provider revisions, external availability and configuration outside the caller's control. Calling the entire operation pure would hide the boundary worth observing.

A pottery workshop seen in cross-section. In the middle, a potter turns identical lumps of clay into identical bowls beside a ticking metronome. At the delivery door on the left and the dispatch door on the right, bright lamps light people watching everything that passes.
Chunking, embedding and clustering return the same output for the same input, so a timing is nearly all they need. Database writes and API calls touch systems you can't re-run, and that is where the deep instrumentation goes.

Pure functions also need correctness evidence. A deterministic bug will reproduce the wrong answer consistently. Timing may be the only runtime detail needed for one narrow computation, but that's a conclusion about its contract and risk, not a general exemption from validation or testing.

Shapes can support both sides of the boundary. An external fetch records the source revision and response outcome. A parser records accepted and rejected counts under a schema version. A computation records the parameters that determine its result. A write records the generation that persisted. Together, those observations preserve an explanation without dumping every value into telemetry.

Composition can reuse policy without pretending systems are identical

Entity-component systems in game development offer a limited analogy for composition. An identity can have data attached to it, and shared operations can act on entities with the relevant data. That's a useful way to think about reuse, provided the analogy remains an analogy.

A span has identity and attributes, and processing tools operate on those records. Those similarities don't make OpenTelemetry an entity-component implementation or turn a collector into proof of the analogy. Reuse a shared policy across operations with explicit data rather than copying behavior into every operation.

A wrapper can apply common capture limits. A versioned schema can define recurring lineage fields. A query can compare operations that use the same units and outcome meanings. Each is reuse with a contract. None requires inventing a universal abstraction that makes every system fit one shape.

Observation and enforcement need separate responsibilities

A performance budget may trigger an alert. A validation boundary may reject malformed data. An authorization boundary may prohibit an action. These mechanisms can emit related observations, but their responsibilities remain distinct.

A trace saying authorization passed is evidence about what the code reported. The authorization mechanism must still enforce the rule independently of whether the trace exporter is available. A missing span mustn't become permission to proceed, and a forged trace identifier mustn't become an authenticated identity.

The same distinction applies to data integrity. An observed write attempt isn't a durable commit. A durable commit isn't proof that a downstream index reflects it. Record the boundary's actual outcome with the identifier someone can use to read it back, and preserve truthful partial states where the operation spans several systems.

A successful function can be investigated because the system distinguishes its decision, the effect it requested and the result that persisted. That distinction becomes more important when an agent selects the next action and its behavior can't simply be rerun to recover the original choices.

An agent needs evidence of its choices and their consequences

A model-driven agent can take different routes through the same tools. Its retrieved context can change, tool responses can change and a provider can update behavior behind a model identifier. Running the task again produces another execution. It doesn't necessarily recover the one being investigated.

Conventional software also needs historical evidence when external state or timing matters. Determinism helps reproduction only when the relevant inputs, code and state can be restored. Agents make that limitation more visible because variation can enter directly into the choice of what to do next.

Preserve the inputs to a choice without claiming access to private reasoning

The observable record should identify the task, the context sources, the configuration, the selected action and the result. A retrieval step can retain source references, revisions and the passages made available under an approved retention policy. A model step can identify the provider, requested model, parameters and reported usage. A tool step can record its name, version, safe arguments and outcome.

These are observable inputs and actions. They don't establish the model's complete internal reasoning, and an explanation generated after the fact is another output to assess. Keep that distinction so an operator can tell a recorded action from a retrospective account of why it supposedly happened.

A line of footprints crosses a wet beach at night. Incoming waves have already smoothed away the oldest prints. A kneeling person pours plaster into one footprint, beside a finished cast of an earlier one, as the next wave rolls toward the rest.
A deterministic function can be run again with the same inputs. A model-driven agent samples, retrieves from a moving corpus and calls tools in a changing world, so each run happens once, and what was recorded at the time is all that will ever exist.

Content retention needs the same authority as elsewhere in the system. A prompt can contain private documents or instructions. Tool arguments can contain credentials or personal information. Safe identifiers, protected references and bounded summaries may be appropriate in the general trace, with restricted access to exact content where retaining it's justified.

A hash identifies bytes when those bytes are available for comparison. It doesn't let an investigator recover content that was never retained. Document which evidence is reopenable and which is only identifiable. Otherwise the appearance of precise provenance can conceal an archive that can't answer the intended question.

Record the decision path as well as the call path

The call graph shows which operations ran. The decision path adds what options or tools were available, which action was selected and what happened after the action. Two runs can use the same tool and differ in the arguments that determine the result.

A handoff also needs an outcome. An orchestrator may ask another component to do work, receive a response and report success even though the requested state never persisted. Record the consequential action and its read-back, linked to the task that authorized it. A tool's successful response is one observation, not a substitute for checking the resulting state where the contract requires it.

Still from the animation. One prompt run three times through the same tool graph, lighting three different routes to the same accepted answer, with three near-identical timing waterfalls beneath showing that only the recorded path tells the runs apart.
One identical prompt runs three times through the same agent and lights three different paths through the same tree of tools and handoffs. All three arrive at an acceptable answer by different routes, and the timing waterfall for all three is indistinguishable.

My Agent Redwood work treats tools, evaluation, constraints and other agent-design choices as things to decide explicitly. It grew from projects that were difficult to finish when those choices stayed vague. The same discipline applies to telemetry. Decide what an operator must be able to explain before treating the agent as a prompt connected to a toolbox.

Use the relevant semantic conventions where they fit, and record the versions in use. Model and agent conventions can evolve independently of a library example. Keep the mapping at a controlled boundary so a naming change doesn't require revising every business operation or silently break saved analysis.

Runtime success and task success need separate checks

An agent can return valid JSON, complete within its latency budget and still answer the wrong question. It can cite a real document whose text contradicts its claim. It can call the intended tool with an inappropriate argument. Technical success measures won't identify all of those failures.

A finished stone bridge, lit along its length by lamps, runs from the near shore out to a small bare rock in a dark bay. Just past the rock, the lit houses of a town sit across open water with nothing reaching them.
An agent can answer quickly, in valid format, with a success status, and still cite a source that says the opposite or answer the question next door. Every mechanical signal reports a finished bridge, so quality has to be scored too.

Evaluation therefore needs dimensions tied to the useful job. Format compliance can often be checked mechanically. Whether a cited passage supports a conclusion may require semantic assessment. Whether the task was completed can require inspecting an external state or asking an authorized human to judge the result.

Use deterministic checks for conditions they actually prove. Parsing JSON proves parseability. Confirming a citation reference exists proves that the reference resolves. Neither establishes factual support for every claim. A checker should name the condition it tests instead of assigning a broader success label.

Model-based judgments need calibration against human judgments appropriate to the task. Agreement on convenient examples doesn't establish performance on the failures that matter. Retain the evaluator version, criteria and evidence so a changed score can be investigated rather than treated as a fact independent of the instrument producing it.

Combine pre-release cases with observed production failures

A held-out evaluation set can detect regressions on known tasks before release. It needs a population relevant to expected use and separation from examples used to tune the system. Repeatedly optimizing for the same visible cases can make the score improve while general usefulness stays unchanged.

Online evaluation can examine a permitted sample of live work. The sample needs a defined selection policy and appropriate treatment of sensitive inputs. It can reveal cases missing from the held-out set, but its score describes that evaluated population and the evaluator's own limits.

A production failure can become a regression case after the failure is understood and the necessary material can be retained lawfully. Preserve the expected outcome and the reason the case matters. Copying an incorrect output into a test fixture without a reviewed target would make the failure repeatable without making the check useful.

Behind a lamplit counter, a dark board holds rows of counterfeit coins nailed up one by one, with empty space still below. A gloved hand adds a newly caught coin to the row, while genuine coins pass through a brass slot on the counter.
Deterministic checks, online scoring of live traffic and drift watch on the scores each catch a different kind of bad answer. Every interesting failure in production then becomes a permanent test, so the checks get sharper with each one caught.

Evaluation history can drift too. A lower score may reflect a changed input mix, a changed evaluator or a changed agent. Record those versions and compare like conditions before attributing the movement to one component. Inspect meaningful examples behind the aggregate rather than allowing one score to conceal which quality dimension changed.

The record should support an accountable correction

An investigation should connect the observed defect to a bounded change. A wrong source selection may need a retrieval correction. A tool permission failure may need a clearer constraint or integration change. An action completed without the required approval is an authority problem even if its output is accurate.

Observability makes those distinctions inspectable. It can't choose the acceptable risk or authorize a repair outside the operator's remit. The final record should make clear what happened, what remains uncertain and which decision follows. That's why observation belongs in the feature's delivery contract, alongside the code that performs the task.

Observable delivery includes an answer someone can reopen

A feature design states what the system should do. It should also state how an operator will know whether it happened. That explanation belongs beside the intended behavior while the designer still knows which decision, dependency or transformation could invalidate the result.

Retrofitting that answer requires someone to reconstruct intent from code and current behavior. Instrumentation can still be added later, but the old executions won't gain observations that were never captured. Including the evidence requirement during design avoids treating that missing history as an inevitable property of production work.

Define the questions before choosing the dashboard

Start with the useful result. A pricing operation owes a correct application of the effective policy. A pipeline owes a retrievable output derived from the intended source. An agent owes an authorized action or supported answer within its task contract. Their evidence requirements differ.

For each, name a few consequential questions. Which input revision did this run use? Which policy or configuration applied? Where did it wait? Which attempt consumed the allowance? What persisted? Who can inspect that record, and for how long? The answers identify fields and relationships before a vendor interface determines what appears easy to chart.

On the left, a builder sets a pipe sleeve by hand into a foundation form full of wet concrete. On the right, the same foundation has long hardened under a finished floor, and two workers drill and hammer through the slab to fit the same pipe.
Ask how a feature will show it is working right after deciding what it should do, while the designer still knows which branch worries them. Ask six months later and someone has to read the code, rebuild the intent and instrument from outside.

A design can then name the observations that support those questions. It should also identify gaps, such as a provider that doesn't expose a revision or a payload the application isn't permitted to retain. An explicit unknown is more useful than a field filled with a plausible substitute. The operator needs to know the limits of an explanation as well as its content.

Review instrumentation as part of the behavior being changed

A new decision branch changes what can happen. Review whether its outcome and reason are observable under the same access and retention policy as related decisions. A new external call changes dependencies and resource consumption. Review how its context, attempts and result will be connected to the originating operation.

A test can verify that a field is emitted under a known condition. A representative read-back can show that the field survives the real collection route and can be queried by its intended consumer. Those checks establish different facts. Neither should be represented as proving every future diagnosis will succeed.

DONE MEANS ALL FOUR does the thing handles failure is tested is observable
Nobody argues about the first three. The fourth carries the same weight and gets treated as optional, which is the whole of why systems ship blind.

The evidence requirement should be proportionate. A small internal operation may need a narrow event and a clear failure state. A consequential financial or access decision may need a durable audit record with stronger controls. Apply the obligation to the risk and the useful job rather than requiring the same telemetry volume everywhere.

An incident review can improve that design. Identify which missing observation prevented a useful distinction, then add the smallest record or relationship that would have preserved it. A resolution to be more careful doesn't change what the next operator can learn. A stable reason code or a preserved handoff can.

Use evidence to choose a safe response

Consider the ingestion path again as a hypothetical operator exercise. An alert says completion has fallen. The operator finds affected jobs, follows their recorded attempts and sees a provider limit response. Caller attribution identifies which workload consumed the allowance. The backlog and freshness requirements show what work is waiting and what delay would cost.

Those observations support a response. They don't automatically make waiting for a reset safe. Some jobs may expire. Some actions may have completed remotely despite a timeout. A retry can duplicate effects unless the application protects them. The operator should act under a runbook or explicit authority suited to those consequences.

A dark bedroom. Someone lies propped in bed holding a phone whose screen is the only light in the frame. Three trace bars sit beside it: two intact, the third broken through, timestamped 02:47. A thread runs from a server rack down to the broken bar. The labels read three minutes, rate limited, and never got up.
The failure is identical in both versions of this night. What instrumentation buys is that the diagnosis finished before anyone had to sit up.

Preserve the resulting state. A job may be deferred, partially completed, rejected or awaiting an external condition. Calling every non-error response success would hide those distinctions again. An authorized reader should be able to reopen the record and see the actual outcome, including what remains to happen.

The response also needs a verification step appropriate to the correction. A reduced queue depth doesn't prove the missing outputs appeared. A resumed worker doesn't prove it used the intended policy. Read back the result the useful job required, then connect that evidence to the incident and correction.

Shared evidence reduces dependence on one investigator

A record with stable fields and accessible definitions lets another engineer continue an investigation without inheriting the first person's terminal session. A useful query can be shared. A source revision can be reopened. A policy version can be compared with the outcome it produced.

That shared evidence doesn't eliminate interpretation. Two plausible causes can remain, or a missing provider detail can prevent certainty. Record the distinction between what the system observed and what the team inferred. The next investigation then starts from an inspectable result instead of an explanation whose evidence disappeared with the chat history.

This is also what makes measurement useful for improvement. The team can compare similar operations before and after a change, retain the limitations of the comparison and decide whether the result justifies keeping the change. An aggregate score or a favorable anecdote becomes one part of that decision rather than the entire case.

A decision record makes the pricing result checkable

The useful record now identifies the request, the effective policy version, the relevant permitted inputs, the applied branch and its reason. It connects dependency calls to the calculation and the calculation to the persisted order. An investigator can compare that record with the intended rule and find out whether the decision, its observation or the later write was wrong.

That ability costs resources and design work. It can be limited by privacy, retention, sampling and the behavior of systems outside the team's control. Make those limits visible. Then ask the question that justifies the instrumentation: can someone explain this completed operation using evidence the system retained at the time?

A timing bar alone couldn't answer the customer's pricing question. The connected decision record gives the operator a place to begin, and the persisted result gives them something to check the explanation against.

Visuals 41

Keep reading