> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Inspect a campaign journey simulation step by step

> Run a project-only contact trace, control branch inputs with attribute overrides, and read the Journey Inspector before you launch.

# Inspect a campaign journey simulation

The Journey canvas can project a draft graph before you activate a campaign. Use the simulation to answer a narrow question: **how does an estimated cohort move through this graph, and where does it stop?** The result is project-only. It does not enroll a contact, queue a message, or call a provider.

The current canvas simulator is a cohort projection, not a live contact replay: it does not read a contact record or create a per-contact execution trace. The **Inspector** terminology below refers to the projection results shown beside the canvas: inspect each `nodeResults` row and its exact `nodeId`. This guide shows how to use the branch assumptions to test a contact-shaped path without changing tenant data.

This guide assumes you have a journey draft open in **Outbound → Campaigns → Journey**. For the graph itself, see [Build, simulate, and launch a campaign journey](/guides/campaign-journey-builder). For a journey that is already being refused at launch, see [Troubleshooting: campaign and journey enrollment errors](/troubleshooting/campaign-journey-errors).

## 1. What the simulator executes

Select **Simulate** in the journey toolbar. The simulator runs the same in-memory `nodes` and `edges` that the canvas is editing. It does not reload a different saved version, enroll a real contact, or execute live actions.

The projection evaluates graph structure and contact inputs only:

* A trigger provides the entry point for the estimated cohort.
* A wait passes the projected volume to the next node; the current result does not advance or return a per-contact clock.
* A condition projects `yes` and `no` volumes from the configured pass rate.
* An A/B split projects each branch from its configured weights.
* A send node contributes a projected send count; it does not send.
* An end or exit node contributes to the projected terminal outcome.

Save the graph before you use the result as a review artifact. You can simulate an unsaved draft, but the trace describes the current canvas state, not the last saved definition used by a running campaign.

## 2. Pick a sample contact

Start with an entry cohort size that represents the population you want to inspect. In the simulation panel, set **Entry cohort size** and review the graph fields your conditions read. For a contact-shaped test, write down the sample contact's relevant attributes and translate them into a branch pass-rate assumption; the current cohort simulator does not fetch contacts, events, or tenant attributes.

Use assumptions when you need to force a branch without changing tenant data. A global **Branch pass rate (%)** controls the default share taking the `yes` path, while a per-node override controls a named condition. These values apply only to the projection. For example, for a condition that reads `plan`, prepare two contact-shaped cases: an eligible contact (`plan = pro`) and an ineligible contact (`plan = free`).

```text theme={null}
Sample contact shape: ct_2048
Attributes used for review:
  plan = pro
  country = GB
  journey_engaged = false
Entry event:
  contact.created
Projection assumptions:
  conditionPassRate = 1.0 for the eligible case
```

To exercise both sides of a condition, run the same graph twice:

1. Run the eligible case with a `100%` pass rate and confirm the `yes` handle.
2. Run the ineligible case with a `0%` pass rate and confirm the `no` handle.
3. Remove the temporary assumption before you save or activate. Assumptions never become contact updates.

Supply every value a branch needs. A missing attribute is not the same as an empty string, `false`, or zero. If a condition has no usable value, the Inspector marks the decision unresolved rather than silently guessing a branch. For date-based waits, use a contact with the date field populated and confirm the timezone shown by the project. If the journey enters from a segment, choose a contact that is actually in that segment or use the segment-entry context offered by the simulator.

## 3. Read each projected step

After the run, read the projection results in node order. The current result includes one row per graph node with its exact `nodeId`, label, projected arrival volume, and whether that node is reachable. Select the matching node on the canvas to inspect its configuration. Read branch volumes and warnings alongside the rows; the result does not include an execution-time contact timestamp or a per-contact resolved attribute value.

| Step | Read it as |
| - | - |
| Trigger | The row is the entry node. Its `arrivedVolume` is the projected cohort entering the graph; it is not a contact-admission verdict. |
| Wait | The row passes its `arrivedVolume` onward. A 1-day wait does not sleep for a day, create a scheduler job, or add a timestamp to the current result. |
| Condition | Compare the projected arrivals on the `yes` and `no` edges using the configured pass rate. The result does not show a contact's resolved field value. |
| A/B split | Compare each downstream arrival volume with the configured weight. The split is a projection of assignment, not an experiment assignment stored for a contact. |
| Send | The row's arrival volume contributes to the channel's projected send count. No message is created and no delivery result exists. |
| Goal or exit | The node's arrivals contribute to `completedVolume`, `exitedVolume`, or `unresolvedVolume`, depending on the graph path. |

For a 1,000-contact entry cohort, a 1-day wait still contributes 1,000 projected arrivals to the next node. The wait changes live execution timing, but the current graph projection does not calculate timestamps, contact attrition, or delivery time.

### A/B split math

For a split with variants weighted 70% and 30%, the simulator normalizes the weights to 0.70 and 0.30. For an entry cohort of 1,000 contacts, the projection is:

```text theme={null}
Variant A: 1,000 × 0.70 = 700 projected contacts
Variant B: 1,000 × 0.30 = 300 projected contacts
```

A single-contact trace shows one selected branch; it does not claim that the contact will be permanently assigned in a live run. If the configured weights do not sum to 100%, fix the split before relying on the trace.

## 4. Map branch handles to outcomes

The label on an edge is the contract between a decision node and its next step. Read the `sourceHandle` on the edge and compare it with the result shown in the Inspector:

| Node | Handle | Outcome |
| - | - | - |
| Condition or Score Check | `yes` | The comparison evaluated true. |
| Condition or Score Check | `no` | The comparison evaluated false. |
| Wait for Event | `event` | The named event arrived in the projected scenario. |
| Wait for Event | `timeout` | The event did not arrive before the configured timeout. |
| A/B Split | Configured variant handle | The weighted assignment selected that variant. |

Do not infer a branch from its position on the canvas. A lower edge is not automatically `no`, and a right-hand edge is not automatically a timeout. Select the edge or node and verify its handle label. If a handle has no outgoing edge, the Inspector can report a branch as unresolved even when the other branch reaches an exit.

## 5. Fix common simulator findings

The Inspector distinguishes a projected graph problem from a launch refusal. When it names a node, copy the exact node id into your review notes; ids are more reliable than node labels, which can be duplicated.

| Finding | What it means | Smallest graph change |
| - | - | - |
| **Unreachable node** (`node_id: n_goal`) | No admitted path from the trigger reaches `n_goal`. | Connect the missing incoming edge to the intended predecessor, or delete `n_goal` if it is not part of the journey. Do not add another node first. |
| **Cycle** (`n_condition → n_send → n_condition`) | A non-wait path returns to an earlier node and cannot produce a finite projection. | Remove the one edge that closes the loop, then connect that handle to the intended exit. A wait-and-re-evaluate pattern is distinct from an ordinary graph cycle; review the validator message before changing it. |
| **Missing exit criteria** (`node_id: n_goal`) | A path reaches the goal or a branch end without a configured goal or terminal outcome that finishes the contact. | Configure the intended goal or add the smallest terminal **End Journey** edge from the open handle. |
| **High send density** (`node_id: n_send_2`) | The projection places too many sends close together for the configured path. | Add an appropriate wait between adjacent sends, or remove the redundant send. Keep the change on the path named by the Inspector. |

Run the simulation again after each graph change. A clean trace should show an admitted trigger, a finite sequence of node ids, explicit branch results, and a goal or terminal outcome.

### Projection finding or launch validation failure?

A **projection finding** is attached to the sample run: it names a node or handle in the trace, such as `n_goal` being unreachable or `no` having no edge. Fix the graph, then run the projection again. A **launch validation failure** is a refusal for the saved campaign or resolved audience, such as `JOURNEY_DEFINITION_INVALID`, `JOURNEY_TRIGGER_MISSING`, `JOURNEY_TRIGGER_DISCONNECTED`, `AUDIENCE_TOO_LARGE`, or a channel/send-gate refusal. It can happen even when one sample trace looks healthy. Copy the error code and request id and follow [Troubleshooting: campaign and journey enrollment errors](/troubleshooting/campaign-journey-errors); do not try to fix a launch refusal by changing only the sample contact.

## 6. Walkthrough: segment entry, one-day wait, send, and goal

Use this small graph to reproduce the same example described in [Campaign journeys](/campaigns/journey): a segment admits the contact, the journey waits one day, sends a message, and checks a goal condition.

### Before the fix

The draft contains these nodes and edges:

```text theme={null}
n_segment  (segment entry: VIP trialists)
  └─→ n_wait_1d  (wait: 1 day)
       └─→ n_send_welcome  (send: SMS)
            └─→ n_goal  (condition: trial_started)
                 └─yes→ n_end_success
```

The `no` handle from `n_goal` is not connected. Run the simulator with this sample context:

```text theme={null}
Contact: ct_2048
segment: VIP trialists
trial_started: false
Projected start: 2026-10-11T09:00:00Z
```

The projection results show the reachable node rows before they report the open path:

```text theme={null}
nodeId           arrivedVolume   reachable   projected result
n_segment        1,000           true        entry
n_wait_1d        1,000           true        wait passes volume onward
n_send_welcome   1,000           true        channel=sms, projected send
n_goal           500             true        yes=500, no=500
warning          —               —           missing no path from n_goal
```

This is not a failed SMS or a live enrollment. It is a graph projection that found a branch with nowhere to go. The exact node id is `n_goal`, and the missing handle is `no`. The 500/500 split uses the default 50% pass rate; set a per-node override when your sample-shaped case needs a different split.

### After the fix

Make the smallest change: connect the `no` handle to an explicit end node.

```text theme={null}
n_segment  ─→ n_wait_1d ─→ n_send_welcome ─→ n_goal
                                             ├─yes→ n_end_success
                                             └─no → n_end_not_ready
```

Run the same projection again. The node-id result is now finite:

```text theme={null}
nodeId           arrivedVolume   reachable   projected result
n_segment        1,000           true        entry
n_wait_1d        1,000           true        wait passes volume onward
n_send_welcome   1,000           true        channel=sms, projected send
n_goal           500             true        yes=500, no=500
n_end_success    500             true        completed
n_end_not_ready  500             true        completed
```

Run a second projection with a `100%` pass-rate assumption for `n_goal`. The first three rows remain, the `yes` volume becomes 1,000, and `n_end_success` receives the full projected volume. Comparing the two projections verifies that both branch handles are wired without changing tenant data or sending either message.

## 7. Know when simulation is not enough

A successful projection means the graph can produce a path for the supplied sample. It does **not** authorize a live campaign. The launch validator still checks the saved journey and the tenant's launch conditions, including:

* **Channel eligibility:** the configured channel, sender, template, recipient address, and tenant channel setup must be eligible for the audience.
* **Audience size:** the resolved audience must fit the tenant's allowed recipient count. A one-contact simulation does not prove that the full audience will pass this check.
* **Per-tenant send gates:** tenant-owned send controls such as quiet hours, frequency caps, suppression, consent, and configured country or channel controls still apply at launch and at send time.

If activation or enrollment returns a graph or launch error, copy the code and request id from the response and follow [Troubleshooting: campaign and journey enrollment errors](/troubleshooting/campaign-journey-errors). Do not treat a clean contact trace as a bypass for those checks.

## 8. Simulation limits

The canvas simulation is intentionally a projection. It does not model:

* **Contact attrition during waits.** A contact remains in the trace across a one-day wait; the projection does not estimate who will leave, unsubscribe, or become unreachable during that period.
* **Event timing noise.** A projected event or timeout follows the scenario you supplied. It does not reproduce ingestion delay, clock skew, retries, or other timing variation.
* **Per-channel delivery outcomes.** A projected send is not a delivered, opened, clicked, failed, or replied message. Read those outcomes from the live campaign analytics after activation.

Use the simulator to inspect graph intent and branch coverage. Use launch validation for readiness, and use live journey analytics for what happened to enrolled contacts.

## See also

* [Campaign journeys: build, validate, simulate, and launch](/campaigns/journey) — the canvas loop and journey model.
* [Build, simulate, and launch a campaign journey](/guides/campaign-journey-builder) — graph structure, node types, and API examples.
* [Troubleshooting: campaign and journey enrollment errors](/troubleshooting/campaign-journey-errors) — launch and enrollment refusal codes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.