Decorators
Three decorators — pick by intent. All work with sync and async.
import staso as st
@st.agent
def handle_ticket(ticket_id: str) -> str:
return draft_reply(fetch(ticket_id))
@st.tool
def fetch(ticket_id: str) -> dict:
return db.lookup(ticket_id)@st.agent
Marks an agent entry point. Produces a span of kind agent and overrides the agent name for everything nested inside — the primary way to label per-agent traces when one process runs more than one agent.
@st.agent
def research_agent(query: str) -> str: ...
@st.agent(name="billing-agent", tags=["v2"])
async def billing_agent(user_id: str) -> str: ...@st.tool
Wraps a tool function. Produces a span of kind tool.
@st.tool
def search_docs(query: str) -> list[str]: ...
@st.tool(name="stripe.refund")
async def refund(charge_id: str) -> bool: ...@st.trace
Generic. Produces a chain span by default; override kind for retrievers, custom steps, etc.
@st.trace(kind="retriever")
def embed_and_search(query: str) -> list[str]: ...Valid kind: "llm", "tool", "chain", "retriever", "agent", "custom".
Parameters
All three accept the same keyword args:
| Parameter | Default | Description |
|---|---|---|
name | function name | Span name. |
kind | varies | agent / tool / chain. @st.trace lets you override. |
tags | None | Free-form tags. |
metadata | None | JSON-serializable metadata. |
capture_input | True | Record arguments. |
capture_output | True | Record return value. |
Capture flags
Disable when the payload contains PII, secrets, or megabyte blobs. For a global kill switch on LLM message content, use capture_messages=False in st.init.
@st.tool(capture_input=False)
def charge_card(card_number: str, amount_cents: int) -> str: ...Exceptions
If the function raises, the span is marked error, the exception message is recorded, and the exception propagates.
Next
- Manual spans — finer-grained control.
- Annotations — attach scores to traces.