Tracing with Decorators
Use @st.agent, @st.tool, and @st.trace to auto-trace functions with zero boilerplate. Decorators are the fastest path to agent instrumentation in Python.
import staso as st
st.init(api_key="ak_...", agent_name="support-bot")
@st.agent
def handle_ticket(ticket_id: str) -> str:
context = fetch_context(ticket_id)
return draft_reply(context)
@st.tool
def fetch_context(ticket_id: str) -> dict:
...Every call to a decorated function becomes a span in your trace data model.
@st.agent
Mark agent entry points. Produces a span of kind agent and — importantly — overrides the agent name for every nested span inside the decorated function. This is the primary way to label agents when you run more than one in the same process. The name you pass (or the decorated function's own name, if you omit name=) flows down through every span the agent produces, so traces route to the right agent automatically. Use st.init(agent_name=...) as the baseline for code paths that aren't wrapped in @st.agent.
def agent(
_fn=None,
name: str | None = None,
*,
tags: list[str] | None = None,
metadata: dict | None = None,
capture_input: bool = True,
capture_output: bool = True,
)@st.agent
def research_agent(query: str) -> str:
return plan_and_execute(query)
@st.agent(name="billing-agent", tags=["v2"])
async def billing_agent(user_id: str) -> str:
return await resolve(user_id)Works with async def transparently.
@st.tool
Wrap the functions an agent calls as tools. Produces a span of kind tool.
def tool(
_fn=None,
name: str | None = None,
*,
tags: list[str] | None = None,
metadata: dict | None = None,
capture_input: bool = True,
capture_output: bool = True,
)@st.tool
def search_docs(query: str) -> list[str]:
return vector_db.query(query)
@st.tool(name="stripe.refund")
async def refund(charge_id: str) -> bool:
return await stripe.refund(charge_id)@st.trace
Generic decorator for anything that is not an agent or a tool — helpers, pipelines, retrievers. Produces a span of kind chain by default.
def trace(
_fn=None,
name: str | None = None,
*,
kind: str | SpanKind = SpanKind.CHAIN,
tags: list[str] | None = None,
metadata: dict | None = None,
capture_input: bool = True,
capture_output: bool = True,
)@st.trace(kind="retriever")
def embed_and_search(query: str) -> list[str]:
return vector_db.query(query)
@st.trace
async def rerank(candidates: list[str]) -> list[str]:
return await model.rerank(candidates)Valid kind values: "llm", "tool", "chain", "retriever", "agent", "custom".
Parameter reference
All three decorators share the same keyword arguments.
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | None | function name | Span name. Defaults to fn.__name__. |
kind | str | SpanKind | varies | agent / tool / chain per decorator. @st.trace lets you override. |
tags | list[str] | None | None | Free-form tags stored on the span. |
metadata | dict | None | None | Arbitrary JSON-serializable metadata. |
capture_input | bool | True | Record function arguments on the span. |
capture_output | bool | True | Record return value on the span. |
Capture flags
capture_input and capture_output control whether the SDK records function arguments and return values on the span. They default to True.
Disable them when:
- The payload contains PII or secrets.
- The payload is very large (megabyte blobs, raw files).
- You want to mask an output and re-attach a sanitized version manually via manual spans.
@st.tool(capture_input=False)
def charge_card(card_number: str, amount_cents: int) -> str:
...For a global kill switch on LLM message content, use capture_messages=False in st.init.
Exceptions
If the decorated function raises, the span is marked with status error, the exception message is recorded, and the exception propagates unchanged.
See also
- Manual spans — finer-grained instrumentation.
- Annotations — attach scores and labels to traces.
- Trace data model — the shape of a span.