Ecosystem Integration: how sibling packages emit spans
Source:vignettes/ecosystem-integration.Rmd
ecosystem-integration.RmdOverview
securetrace is the observability backbone for the secure-r-dev
ecosystem. Each sibling package (securer,
securetools, secureguard,
securecontext, orchestr,
securebench) can emit structured spans into any trace
context you open with [with_trace()] — no plumbing required on your
side. This vignette documents the contract the siblings
use so you know what to expect and how to instrument your own code the
same way.
The contract
securetrace ships a single private helper that every sibling calls:
.trace_active() # TRUE iff there is a current_trace() on the stackEach sibling guards every instrumented operation with this helper. If
a trace is active, the operation is wrapped in
securetrace::with_span(name, type, { ... }) and one or more
.span_event() attributes are attached. If no trace is
active, the operation runs unchanged — zero overhead.
That means:
- The sibling packages only Suggest securetrace (soft dependency).
- Nothing emits spans unless you opened a trace with [with_trace()] (or one of the convenience helpers like [trace_graph()] or [trace_agent()]).
- Cross-package traces work naturally: if you open a trace and call code that crosses package boundaries, each package’s spans nest under yours.
Span taxonomy by package
The table below is the canonical list of spans emitted by each
sibling as of the current release. Span names follow
package.operation and type follows the [Span]
taxonomy (llm, tool, guardrail,
custom).
| Package | Span name | type |
Triggered by |
|---|---|---|---|
securer |
securer.execute |
custom |
SecureSession$execute(), execute_r()
|
securer |
securer.tool_call |
tool |
Tool invocation inside a secure session |
securetools |
tool.<tool_name> |
tool |
tool_calculator(), tool_query_sql(),
… |
secureguard |
guardrail.<name> |
guardrail |
run_guardrail(), check_all()
|
secureguard |
pipeline.check_{input,code,output} |
guardrail |
secure_pipeline() stages |
securecontext |
context.embed_tfidf |
custom |
embed_tfidf() |
securecontext |
context.embed_texts |
custom |
embed_texts() |
securecontext |
context.vector_add |
custom |
vector_store$add() |
securecontext |
context.vector_search |
custom |
vector_store$search() |
securecontext |
context.context_for_chat |
custom |
context_for_chat() |
orchestr |
agent.invoke |
custom |
Agent$invoke() under trace_agent()
|
orchestr |
graph.node.<name> |
custom |
AgentGraph$invoke() under
trace_graph()
|
securebench |
bench.guardrail_eval |
custom |
guardrail_eval(),
benchmark_guardrail()
|
securebench |
bench.guardrail_metrics |
custom |
guardrail_metrics() |
If a span you expect is missing, check that (a) the sibling package is installed, and (b) you actually opened a trace around the call. The simplest smoke-test is:
result <- securetrace::with_trace("smoke", {
securetools::tool_calculator()@fn("2 + 2")
})
length(result$spans) # should be > 0 if securetools is installedWriting siblings of your own
If you build a package on top of the ecosystem and want to emit spans into the same traces, follow the same pattern the siblings use:
#' @keywords internal
.trace_active <- function() {
requireNamespace("securetrace", quietly = TRUE) &&
!is.null(securetrace::current_trace())
}
my_operation <- function(args) {
.do <- function() {
# ... real work ...
}
if (.trace_active()) {
securetrace::with_span("mypkg.operation", type = "custom", {
result <- .do()
securetrace:::.span_event("mypkg.operation.complete", list(
size = length(result)
))
result
})
} else {
.do()
}
}This keeps securetrace a Suggests-only dependency and avoids any overhead when consumers are not tracing.
See also
-
vignette("orchestr-integration")— deeper dive on the orchestr-only helperstrace_graph()andtrace_agent(). -
vignette("exporters")— how to ship spans to JSONL, OTLP, or Prometheus once they’ve been collected. -
vignette("cloud-native")— distributed tracing with W3C traceparent headers.