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/Enforce.

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/Enforce.

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
export STASO_SESSION_ID=launch-quickstart-first-trace

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

REQUIRED_ENV: Final = (
    "STASO_API_KEY",
    "OPENAI_API_KEY",
    "STASO_AGENT_NAME",
    "STASO_ENVIRONMENT",
    "STASO_WORKSPACE_SLUG",
    "STASO_SESSION_ID",
)

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()
    session_id = settings["STASO_SESSION_ID"]
    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:
        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.

Configure a Guard audit

The sample guarantees one supported lookup_order proposal and its trace. It does not guarantee a rule match.

  1. Open Rules and choose a deterministic template shown for your organization.
  2. Open Policies and attach that rule to an audit policy scoped to the sample agent and environment.
  3. Run a proposal whose displayed fields intentionally meet that rule.

Built-in templates are inactive until attached to a policy. When an active audit policy matches, the trace records guard:would-block:lookup_order and lets the proposal continue. With no matching active rule, the trace has no would-block finding. When an active Enforce policy matches, the trace records guard:blocked:lookup_order and prevents the tool execution span.

Use the rules catalog and rules and policies pages to check the options currently shown for your organization.

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
  • a matching configured Guard audit adds guard:would-block:lookup_order
  • a matching configured Guard Enforce policy 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.

  • A matching configured Audit policy records guard:would-block:<tool> and allows the proposal.
  • A matching configured Enforce policy 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 current usage and rate 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