Staso Docs
Quickstart

Launch quickstart

Run one OpenAI Chat Completions trace that matches the supported launch path: Python 3.11+, sync, non-streaming, explicit @st.agent and @st.tool, Observe, and configured Guard audit/block.

Anthropic Messages is supported with staso[anthropic]==0.5.15, anthropic==1.3.0, messages.create, and claude-haiku-4-5. Use the Anthropic quickstart if Anthropic is your default provider.

Create a requirements.txt

staso[openai]==0.5.15
openai==3.6.0

Set the environment

Create a Staso API key in the dashboard for the workspace you selected. Use traces:write for Observe and add guard:evaluate when you test Guard audit/block.

export STASO_API_KEY=ak_...
export OPENAI_API_KEY=sk-...
export STASO_AGENT_NAME=launch-quickstart-agent
export STASO_ENVIRONMENT=production
export STASO_WORKSPACE_SLUG=default

Use default only if that is the workspace selected in the dashboard.

Run the sample

from __future__ import annotations

import json
import os
from typing import Final

from openai import OpenAI

import staso as st

SESSION_ID: Final = "launch-quickstart-first-trace"
REQUIRED_ENV: Final = (
    "STASO_API_KEY",
    "OPENAI_API_KEY",
    "STASO_AGENT_NAME",
    "STASO_ENVIRONMENT",
    "STASO_WORKSPACE_SLUG",
)

TOOLS: Final = [
    {
        "type": "function",
        "function": {
            "name": "lookup_order",
            "description": "Look up the current status for a sample order.",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "The order ID to look up.",
                    }
                },
                "required": ["order_id"],
                "additionalProperties": False,
            },
        },
    }
]


def _load_settings() -> dict[str, str]:
    missing = [name for name in REQUIRED_ENV if not os.environ.get(name)]
    if missing:
        raise RuntimeError("Missing required environment variables: " + ", ".join(sorted(missing)))
    return {name: os.environ[name] for name in REQUIRED_ENV}


@st.tool(name="lookup_order")
def lookup_order(order_id: str) -> dict[str, str]:
    return {
        "order_id": order_id,
        "status": "paid",
        "shipping_state": "ready_to_ship",
    }


@st.agent(name="launch-quickstart-agent")
def answer_order_question(order_id: str = "order_123") -> dict[str, object]:
    client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {
                    "role": "user",
                    "content": f"Look up the shipping status for order {order_id}.",
                }
            ],
            tools=TOOLS,
            tool_choice={"type": "function", "function": {"name": "lookup_order"}},
        )
    except st.GuardBlocked as blocked:
        return {
            "status": "blocked",
            "tool": blocked.tool_name,
            "reason": blocked.reason,
        }

    tool_calls = response.choices[0].message.tool_calls or []
    if len(tool_calls) != 1:
        raise RuntimeError("Expected one lookup_order tool proposal from OpenAI.")

    tool_call = tool_calls[0]
    arguments = json.loads(tool_call.function.arguments)
    result = lookup_order(**arguments)
    return {"status": "observed", "tool_result": result}


def main() -> None:
    settings = _load_settings()
    st.init(
        api_key=settings["STASO_API_KEY"],
        agent_name=settings["STASO_AGENT_NAME"],
        environment=settings["STASO_ENVIRONMENT"],
        workspace_slug=settings["STASO_WORKSPACE_SLUG"],
    )

    try:
        with st.conversation(SESSION_ID):
            result = answer_order_question()
        print(json.dumps(result, sort_keys=True))
    finally:
        delivery = st.shutdown(timeout=10)
        if not delivery.ok:
            raise RuntimeError(
                "Trace delivery incomplete: "
                f"last_failure={delivery.last_failure!r}, "
                f"failed={delivery.failed}, "
                f"pending={delivery.pending}, "
                f"rejected={delivery.rejected}, "
                f"last_rejection={delivery.last_rejection!r}"
            )

    print(
        "Staso accepted trace delivery for "
        f"session_id={SESSION_ID}. The dashboard/backend trace query may take a few "
        "seconds to return the durable trace."
    )


if __name__ == "__main__":
    main()

The same code lives in the repository at examples/launch_quickstart/openai_guard_quickstart.py.

The OpenAI client is constructed inside answer_order_question() after st.init() has run, so the traced agent input is only the safe sample order ID.

What the dashboard should show

After the dashboard/backend trace query can see the trace, expect:

  • session_id = launch-quickstart-first-trace
  • environment = production
  • workspace_slug equals STASO_WORKSPACE_SLUG
  • root span kind=agent, name=launch-quickstart-agent, status=ok
  • LLM child span kind=llm, provider=openai, requested_model=gpt-4o-mini, name=chat:tool_calls(lookup_order)
  • tool child span kind=tool, name=lookup_order, status=ok when Guard allows or audits
  • Guard audit adds guard:would-block:lookup_order
  • Guard enforce/block adds guard:blocked:lookup_order and prevents the lookup_order execution span

DeliveryReport.ok means the SDK reached ingest acceptance. The onboarding milestone is satisfied only when the dashboard/backend returns the trace in the selected workspace.

How setup health is interpreted

  • connection_status=connected means the selected workspace has a query-visible trace.
  • last_successful_ingestion_at is the query-visible trace ingestion time, not client time.
  • provider_patch_status=observed means a durably stored kind=llm span has provider=openai or provider=anthropic.
  • Before that provider span appears, patch status is pending or unobserved. It is not guessed from install clicks.
  • Setup failures use safe categories such as invalid_key, forbidden, plan_expired, rate_limited, backend_unavailable, network_unavailable, timeout, malformed_response, queue_full, shutdown, and disabled.

Guard boundary

Staso checks provider-proposed tool calls before your host code dispatches them.

  • Audit records guard:would-block:<tool> and allows the proposal.
  • Block records guard:blocked:<tool> and raises st.GuardBlocked.
  • @st.tool observes actual Python execution. It does not enforce Guard by itself.
  • Direct Python calls need st.guard(...) when they require enforcement.

API key recovery

Key secrets are shown once. If you lose one, rotate the key or create a replacement in the dashboard, update STASO_API_KEY, restart long-lived processes, and revoke the old key. The SDK cannot show, rotate, revoke, or recover a raw secret.

If the trace does not appear

CategoryWhat to do
invalid_keyCreate a replacement key and update STASO_API_KEY.
forbiddenCheck workspace scope and traces:write. Add guard:evaluate for Guard.
plan_expiredFix the account plan or contact founders.
rate_limitedCheck usage and fair-use limits in the dashboard.
backend_unavailable, network_unavailable, timeoutRetry after the service or network recovers.
malformed_responseContact [email protected] with the safe failure category.
queue_fullFlush more often, reduce burst size, or contact founders.
shutdownRe-run the process after initializing Staso.
disabledCheck that tracing is enabled in local config.

Do not send API keys, prompts, tool arguments, provider responses, or customer data when asking for help.

Next