Skip to main content
The Voyage dashboard is being retired from general use. Existing Voyage API integrations continue to work, and this guide remains available by direct link.
Beyond the quickstart, the most important patterns are recording multiple agents in one Voyage and attaching subprocesses to a parent Voyage. Use decorators for work that fits in one function. Use a with span only when a temporary block is clearer than a named function.

Multi-agent

When one logical task involves multiple cooperating agents (a Reviewer, a TestRunner, a GitHub-poster), each agent gets its own context, spans, and attributed work. They all share one Voyage, so the dashboard shows one trace for the full background agent workload.
Naming convention: the first (and only required) argument is the display name (“Reviewer”). The stable attribution key is derived from it automatically. role= is the optional cohort taxonomy (“reviewer”, “test_runner”, “source_control”, “executor”). The dashboard groups runs by name and offers role as a categorical filter. Pass slug= (advanced) to pin the attribution key across display renames.

Sailbox exec attribution and .wait()

Sailbox commands run as first-class Voyage evidence when they happen inside a Voyage. Keep the agent context active around the call so the dashboard can show which participant ran the command:
sb.exec(...) returns immediately with a request handle. For foreground commands, call .wait() to observe completion, return code, and output tails. If there is no active span, Sail creates an auto-span for the exec. For foreground commands, that span closes when .wait() observes the result. Use explicit spans for steps you want named in the product. Let auto-spans cover low-level calls when you are migrating an existing harness and only need attribution with minimal code changes.

Child-process attach

When the controller spawns subprocesses (e.g., a parallel test runner), the subprocess should attach to the parent’s Voyage rather than create its own. The parent exports SAIL_VOYAGE_ID. The child calls sail.voyage.attach(), which reads it:
child_env() returns the handoff env (SAIL_VOYAGE_ID, plus the active agent context as the child’s SAIL_AGENT_* defaults). It returns {} when telemetry is disabled, so the same code runs keyless.

Common cross-pattern pitfalls

  • Do not complete the Voyage from inside an agent block. Call voyage.complete() at the top level, after all agent contexts have exited.
  • Do not reuse a Voyage across logical tasks. One Voyage per task. If the agent does N tasks, record N Voyages.
  • Do not put secrets in event payloads. Sail applies server-side redaction, but the safest pattern is to summarize or hash sensitive values before recording them.
  • Use explicit handles when one execution context switches among Voyages. Concurrent async tasks can each select their own current Voyage. A raw thread or other context that never selects one uses the process-wide fallback. When one context switches among multiple Voyages, call methods on the intended Voyage object and pass voyage= to inference wrappers instead of relying on implicit attribution.

Reference