Skip to main content
This guide gets you from zero to a working Voyage in under a minute of reading. For the conceptual overview, see Voyages.

Prerequisites

  • Python 3.11+
  • A Sail API key
  • The Sail SDK: pip install "sail-sdk>=0.1.39" (or uv add "sail-sdk>=0.1.39")

Step 1: install + auth

pip install "sail-sdk>=0.1.39"
export SAIL_API_KEY=sk_your_key_here

Step 2: write the minimal Voyage

# voyage_hello.py
import sail

with sail.voyage.run(
    name="hello-voyage",
    metadata={"example": "quickstart"},
) as voyage:
    with voyage.agent("Greeter", role="planner"):
        with voyage.span("say-hello"):
            voyage.event("hello.fired", payload={"message": "hi from sail"})

            # Optional: actual Sail inference call (auto-attributed to this agent/span)
            response = sail.inference.responses.create(
                model="zai-org/GLM-5",
                input="In one sentence, say hello.",
                background=False,
            )
            voyage.event("inference.done", payload={"response_id": response["id"]})

print(f"Open: {sail.voyage.dashboard_url()}")
run() emits voyage.completed when the block exits cleanly — and voyage.failed (then re-raises) if it doesn’t. No try/except needed.

Step 3: run it

python voyage_hello.py
You’ll see one line printed:
Open: https://app.sailresearch.com/prod/voyages/voy_...

Step 4: verify in the dashboard

Open the printed URL. You should see:
  • Status: voyage completed
  • Agents: 1 (Greeter)
  • Events: 2 (hello.fired, inference.done)
  • Model calls: 1 (zai-org/GLM-5, status completed)
  • Trace tab: one agent block with one span, two events nested underneath, one model row.
That’s it. You shipped a Voyage.

Decorators

For function-shaped work, decorate instead of indenting — same events, same attribution:
import sail

@sail.agent("Greeter", role="planner")
@sail.span()
def say_hello():
    sail.voyage.event("greeting.sent", payload={"channel": "stdout"})

voyage = sail.voyage.create(name="hello-voyage")
say_hello()
voyage.complete(message="done")
And calls you don’t wrap at all are still captured: Sail inference and Sailbox execs made inside a Voyage get automatic, timed spans named after your calling code (marked “auto” in the cockpit).

What to do next

  • Add a Sailbox: see the Sailboxes guide for creating a long-running sandboxed VM, then pass sailbox_id=sb.sailbox_id to sail.voyage.create() so the Voyage is bound to that Sailbox.
  • Add a second agent: see Voyages Patterns → Multi-agent.
  • Something looking wrong? Install the Sail skills and use the sail-voyage-debugging skill.

Common first-run gotchas

  • Voyage doesn’t appear in dashboard. Most likely the API key is for a different environment than SAIL_MODE. Default is prod. Set export SAIL_MODE=dev or export SAIL_MODE=staging if your key is for a non-prod environment.
  • Process exits without a terminal event. The Voyage stays “in progress” forever (a bounded best-effort flush at exit delivers trailing events, but can still drop them if the network is down — and it never marks the Voyage terminal). Use with sail.voyage.run(...) so the terminal state is emitted for you, or call voyage.complete() / voyage.fail() yourself.
  • Model call shows up as “Unscoped”. You called inference outside the with voyage.span(...) context. Move the call inside.