ServicesTour

Defining Tours

A tour definition is one JSON document: the ordered steps, the branching rules, the A/B variant allocation, and the reset policy. This page covers the file snug tour create --file takes;

{
  "name": "docs4_new_user_setup",
  "title": "New User Setup",
  "steps": [
    {
      "id": "step_welcome",
      "index": 0,
      "title": "Welcome tour",
      "semantic_target": { "kind": "route", "name": "/home", "description": "Landing page walkthrough" },
      "completion_flag": "saw_welcome",
      "mandatory": true
    },
    {
      "id": "step_profile",
      "index": 1,
      "title": "Complete your profile",
      "semantic_target": { "kind": "ui_element", "name": "profile-form", "test_hint": "data-testid=profile-form" },
      "completion_flag": "profile_completed",
      "prerequisites": ["saw_welcome"],
      "mandatory": true
    },
    {
      "id": "step_advanced",
      "index": 2,
      "title": "Advanced settings",
      "semantic_target": { "kind": "route", "name": "/settings/advanced" },
      "completion_flag": "saw_advanced",
      "prerequisites": ["saw_welcome"]
    }
  ],
  "branching": [
    { "when": "$variant == 'power' && $flags.profile_completed", "next_step": "step_advanced" }
  ],
  "variant": { "experiment_id": "docs4_exp", "allocation": { "control": 50, "power": 50 } },
  "reset_policy": { "on_version_change": "reset_incomplete_steps", "preserve_flags": ["saw_welcome"] }
}

Steps

Each step requires id, index, title, semantic_target, and completion_flag; mandatory, prerequisites, and reward_hook are optional. The step's completion_flag is the bit that records its completion - the set of all completion flags is the tour's flag vocabulary, and each version freezes a flag-to-bit-offset map. A prerequisites list names flags, not step ids, and every entry must be some step's completion flag - creating a definition with a dangling prerequisite fails with 400 invalid_definition; reproduced:

Invalid tour definition: prerequisite flag 'nonexistent_flag' is not produced by any step

Duplicate step ids and unknown branch targets are rejected the same way.

The semantic_target says where in the product the step happens: kind is ui_element, route, or action, plus a name and optional description and test_hint. Nothing server-side interprets it during progress tracking - it exists so frontends, test harnesses, and AI agents can locate and verify each step from the definition alone. reward_hook (kind, target, enabled) can point a step's completion at another service; see the API reference for the schema.

Branching rules

Branching decides the current step. On every status read and event, rules run in order against the user's state; the first rule whose predicate is true and whose next_step is currently available wins. If none match, the current step is the first available step in index order. With the definition above, a user bucketed into power was steered to step_advanced the moment profile_completed was set, skipping ahead of lower-index incomplete steps.

when is a predicate over:

  • $variant - the user's assigned variant name (also $user.variant)
  • $flags.<flag> - whether a completion flag is set
  • $completed.<step_id> - whether a step is completed
  • $completed_count - number of set flags

with comparisons == != < <= > >= against 'strings', numbers, true/false/null; combined with &&/||/! (or the words and/or/not) and parentheses. A bare path is a truthiness test: $flags.profile_completed alone means "that flag is set". Predicates are parsed at definition time, so a syntax error fails the create or update with 400 invalid_definition rather than surfacing later.

Variants

variant holds an experiment_id and an allocation map of bucket names to relative weights. Assignment hashes experiment, user, and version (SHA-256), so it needs no storage: the same user always lands in the same bucket for a given version, with no assignment call. Two consequences:

  • Assignment exists before the user ever touches the tour - a status read of a not_started user already reports the variant.
  • A version bump re-rolls assignment: the same user was power on version 1 and control on version 2.

Per-variant funnel counts come back in the analytics endpoint.

Updating and versioning

PUT /tours/{tour_id} replaces whole sections (title, status, steps, branching, audience, reset_policy) - arrays are replaced, not merged. Changing status (draft, active, paused, deprecated) does not bump the version; replacing steps bumps it by one and immediately applies the reset policy to every participant of the previous version.

Reset policies

on_version_change takes four modes. What they actually do:

  • no_op - the version bump clears nothing.
  • reset_all - every participant's progress and flags are cleared.
  • reset_incomplete_steps and preserve_selected_flags - progress is cleared, then any flag listed in preserve_flags that the user held is set again on the new version.

Note that reset_incomplete_steps does not carry over completed steps' flags automatically - it behaves identically to preserve_selected_flags, keeping only the preserve_flags list. A user with saw_welcome and profile_completed set came out of a version bump with only saw_welcome (the preserved flag), completed_steps empty, and state back to not_started. List every flag you want to survive explicitly.

An explicit POST /tours/{tour_id}/reset with scope of user, version (one version's participants), or tour (all versions) does the same clearing on demand; the wide scopes are admin-only.

On this page