Advanced features
Volume and privacy controls for production deployments of the Rius
SDK: head sampling to reduce trace volume, and two independent controls over
what content leaves your process. All of them are init() arguments; none
require code changes in your agent.
Sampling
By default the SDK exports every trace. To reduce volume, set a head-sampling rate:
glassflow.init(sample_rate=0.25) # keep ~25% of tracesor via the environment:
export GLASSFLOW_SAMPLE_RATE=0.25How it samples
The SDK installs a ParentBased(TraceIdRatioBased(rate)) sampler:
- Whole traces, not individual spans. The decision is made once per trace, from the trace ID, at the root span. Every child span follows the root’s decision, so you never get fragments of a trace.
- Parent decisions are respected. If a span arrives with tracing context from an upstream service or another instrumentation layer, its sampled flag is inherited rather than re-rolled. The rate only governs traces that start inside your process.
- Head sampling only. The decision happens at trace start; there is no tail sampling based on errors or latency in the SDK.
Values outside [0.0, 1.0] are clamped, with a warning log. 0.0 drops
everything; 1.0 (the default) keeps everything.
When to sample
Sampling trades visibility for volume. Two rules of thumb:
- Keep
1.0in development and staging; you want every trace while iterating. - In high-traffic production, prefer lowering the rate over disabling tracing: a 10% sample still shows latency and cost distributions, and error spans in sampled traces remain fully detailed.
If you need to drop content but keep every trace, use the privacy controls below instead of sampling.
Privacy controls
The SDK has two independent controls over what content leaves your process: a switch that strips content entirely, and a masking hook that transforms it. Both run at export time, inside your process, before anything is sent.
capture_content: the content switch
glassflow.init(capture_content=False)
# or: export GLASSFLOW_CAPTURE_CONTENT=falseThe default is True (content is captured). With False, prompt and
response content is stripped from spans at export while metadata still
flows: model names, token counts, span taxonomy, finish reasons, latencies.
You keep cost and performance analytics and give up content-level debugging.
Exactly which attributes are covered
The switch (and masking, below) applies to the content attribute families of the SDK itself and of the third-party instrumentation it ships:
| Family | Attributes |
|---|---|
| Rius spans | input.value, output.value |
| Rius generations | gen_ai.input.messages, gen_ai.output.messages |
| GenAI (third-party) | gen_ai.prompt, gen_ai.completion, and any gen_ai.prompt.* / gen_ai.completion.* |
| OpenInference | llm.input_messages, llm.output_messages, and any llm.input_messages.* / llm.output_messages.* / llm.prompts.* / llm.prompt_template.* |
| Documents and embeddings | any attribute ending .document.content or .embedding.text |
| MLflow / Traceloop | mlflow.spanInputs / mlflow.spanOutputs, traceloop.entity.input / traceloop.entity.output |
Indexed keys are matched by prefix, so
llm.input_messages.0.message.content is covered. Metadata siblings such as
.document.id and .document.score are deliberately not covered, so
retrieval analytics survive content stripping.
Attributes outside these families (your own set_attribute() keys, for
example) are never touched; treat custom attributes as content you control.
Masking: transform instead of strip
When you need content but not the sensitive parts of it, pass a mask callable. It runs on every covered content attribute:
def mask(value):
return my_pii_scrubber(value)
glassflow.init(mask=mask)If the callable accepts a key keyword argument (or **kwargs), it also
receives the attribute name, so you can mask selectively:
def mask(value, *, key: str):
if key.startswith("gen_ai.input"):
return scrub(value)
return value
glassflow.init(mask=mask)Semantics you can rely on
- Runs at export, off the hot path. Masking executes in the background batch thread, on copies of span data, so it never mutates what other processors see and never slows your agent’s code.
- Fail closed. If the mask raises, or returns
None, or returns something that cannot be serialized, the affected attribute is dropped entirely and a warning is logged. The original value never leaks on any failure path, and the rest of the batch still exports. - Return types. Primitives pass through as-is; other objects are serialized to JSON (truncated at 8192 characters).
- Composition.
capture_content=Falseis applied first; the mask then runs on whatever content attributes remain.
Choosing between them
- Regulated environment, content must never leave:
capture_content=False. - Content is useful but contains PII: a mask with your scrubber.
- Different rules per attribute family: a key-aware mask.
Next steps
- Reliability: the export pipeline both controls run inside.
- Troubleshooting: when content or traces go missing unexpectedly.