Skip to content

Data in experiments ​

Values move through a run: a parameter chooses the target, a field of one response becomes a header of the next request, a generated id goes out in a command and comes back in a check. This page covers where those values come from and how a field uses them.

SourceWritten asSet where
Parameter{{api}} or {{params.api}}Parameters panel, a profile, Run with…
Variable{{token}} or {{vars.token}}a node during the run: Extract value, a wait, a send that waits for a reply
Secret{{secret.API_TOKEN}}the computer's credential store, or the server's environment and files
Built-in value{{run.seed}}, {{now.iso}}, {{counter}}the run itself
Generator{{uuid}}, {{random_int(1, 100)}}drawn from the run's seed

Parameters ​

A parameter is a named text value that any templated field can use. Keep targets in parameters, so a change of address is one edit instead of one per node.

Adding a parameter ​

  1. Press Parameters ({ }) in the editor's toolbar.
  2. On the Defaults tab, press Add parameter.
  3. Type the Name and the Value, for example api and http://127.0.0.1:8080.
  4. In a node's field, write {{api}}/login.

Every change in the panel is an edit of the experiment: it is saved with it and undone with Ctrl+Z like any other.

Rules ​

RuleLimit
Namestarts with a letter or _, then letters, digits and _
Reserved namesvars, params, secret, run, node, now, uuid, counter, random_int, random_float, pick
Parameters per experiment64
Size of one value64 KiB
Namesunique; a variable may not have a parameter's name

A value is plain text and is inserted as written: {{…}} inside a value is not resolved. When a field asks for a part of a parameter ({{config.ports[0]}}), the value is read as JSON; a value that is not JSON has no parts.

A parameter whose name is invalid, reserved or taken twice does not keep the experiment from saving, so you can keep typing; the experiment does not run until the name is fixed.

Profiles ​

A profile is a named set of parameter values — Laptop, Stage, Venue — so switching the target is a choice, not an edit of every node. A profile changes some parameters; the others keep their default.

Making a profile ​

  1. Open Parameters and press Profile. A new tab opens.
  2. Rename it in Profile name.
  3. For each parameter the profile changes, type its value. An empty field keeps the default, shown greyed in the field; Use the default value (↺) clears a value.
  4. Press Use for runs to run with it. The active profile's tab carries ● In use. Use for runs on the Defaults tab goes back to the defaults.

Once an experiment has profiles, a Profile list in the toolbar switches between them. The active profile is used by runs, the preview and Send now, and it is saved in the experiment, so an exported file opens with the same targets. Delete profile deletes the profile on screen.

RuleLimit
Profiles per experiment32
Name1–64 characters, unique (spaces at the ends do not count)
Valuesonly parameters that exist; at most 64

Renaming or removing a parameter changes it in every profile at once.

Which value a run uses ​

Later wins:

  1. the parameter's default, on the Defaults tab;
  2. the active profile's value, if it sets one;
  3. a value typed in Run with… for this run only — see running with other values.

Run with… may only set parameters the experiment has. The run report records the profile, the values typed for the run and every value it used.

Profiles that would not run ​

Each time the experiment is checked, the other profiles and the defaults are checked too. One that would fail — say, a URL that is not http:// or https:// — carries ⚠ in the tabs and in the toolbar's list, and its tooltip says why. It does not stop runs with the profile in use.

Templates ​

Text inside {{ }} is an expression; everything else in a field is kept exactly as written.

text
{{api}}/users/{{user.id}}?trace={{uuid}}
Bearer {{secret.API_TOKEN}}
  • Spaces inside the braces do not matter: {{ token }} is {{token}}.
  • \{{ writes a literal {{.
  • A }} on its own is plain text.
  • A value is inserted as it is, without quotes. In a JSON body, write the quotes yourself: "id": "{{uuid}}".

Names ​

ExpressionValue
{{name}}the variable name if one is set on this path, otherwise the parameter name
{{vars.name}}the variable only
{{params.name}}the parameter only
{{secret.NAME}}the stored secret NAME — see secrets
{{name.field}}a field of a JSON value
{{name[0]}}an element of a JSON array
{{name["a b"]}}, {{name['a b']}}a field whose name has other characters

A field name after . may hold letters, digits, _ and -. Steps chain: {{reply.args[0]}}, {{order.items[2].sku}}.

How values are written ​

ValueWritten as
textthe text
numberits shortest form: 42, 0.5
true, falsetrue, false
nullnull
object, arraycompact JSON: ["x","y"]

Built-in values ​

ExpressionValue
{{run.id}}the run's job number; 0 in the preview and in Send now
{{run.seed}}the seed of this run
{{node.id}}the id of the node being executed
{{now}}the current time, Unix milliseconds
{{now.iso}}the current time in UTC, ISO 8601 with milliseconds: 2026-09-30T12:34:56.789Z
{{counter}}how many times this node has run in this run, this time included, from 1

{{counter}} counts per node: in a Loop body it is the iteration's number, in a node that repeats the send's number. run, node and now have only the fields listed; anything else is an error.

Generators ​

ExpressionValue
{{uuid}} or {{uuid()}}a version 4 UUID
{{random_int(min, max)}}a whole number from min to max, both included; whole-number arguments, min ≤ max
{{random_float(min, max)}}a number from min up to, but not including, max, with 3 decimals; min < max
{{random_float(min, max, digits)}}the same with digits decimals, 0–9
{{pick(a, b, c)}}one of the arguments, at least one

Arguments are separated by commas. One in quotes ("dark blue" or 'a, b') may hold anything but its own quote; one without quotes may hold letters, digits and _ - . : / +. An empty argument is an error.

Every generator draws from the run's seed. The values of one execution of a node depend only on the seed, the node's id and how many times the node has run, so parallel branches never change each other's values, and a run with the same seed generates the same values again. Draws within one node follow the order of its fields. {{now}} and {{run.id}} are not reproducible. See seeds.

Suggestions ​

Typing {{ in a templated field, or pressing Ctrl+Space, opens a list in four groups: Parameters with their values, Variables set upstream of this node with the node that sets them (a reply's fields too, such as reply.args[0]), Secrets and Generators. ↑ and ↓ choose, Enter or Tab inserts, Esc closes the list and keeps the field.

Unknown names are errors ​

A name without a value never becomes an empty string. Before a run, every name a field uses must be a parameter, a valid secret name, or a variable set on every path that leads to the node. The editor points at the node and the field:

ProblemBefore the runDuring the run
A name nobody setsname.unknown—
A variable set on some paths onlyname.not_on_every_path—
{{params.x}} without a parameter xparam.unknown—
A field that a value does not have—template.no_field
Unclosed {{, an empty {{}}, a malformed argumenttemplate.*, with the position—

The texts of these codes are in Errors.

Which fields take templates ​

NodeTemplated fields
HTTP requestURL, header names and values, body, the user name and password of Basic and Digest, the Bearer token
OSC messagetarget, address, text arguments; with a reply: its address pattern and rule values
UDP datagramtarget, payload; with a reply: its pattern
TCP messagehost, payload
MQTT publishbroker host, topic, payload
Log markermessage
Response textexpected text
Response headerheader name, expected text
Check value, Branch on value, a Loop's exit conditionvalue, expected value
Wait for OSCaddress pattern, rule values
Wait for UDP, Wait for WebSocketpattern
Wait for MQTTbroker and topic (parameters only), pattern
Wait for HTTP requestpath pattern, conditions
Impairmentlisten and target (parameters only)
WebSocket connectURL, header names and values
WebSocket sendpayload
WebSocket closereason

Numbers — ports, timeouts, delays, statuses, typed OSC numbers — and the listening addresses of waits are literal. An Emulator renders its own replies with what arrived ({{request.…}}) and the parameters; see faults.

Parameters only. Some fields are opened before the first step, when no variable exists yet: a Wait for MQTT's broker and topic, an Impairment's listen and target. They take text and parameters, nothing else (node.params_only).

Checked like literals. A field that uses only parameters is resolved before the run and checked as the text the run will send: a URL must be http:// or https://, an OSC target IP:port or host:port, a header name valid. A field with variables or generators is checked when it runs.

Preview ​

When the selected node has a template, its properties show what it will do with the values known now: Will be sent for a send, Will wait for for a wait, Will compare for a comparison. The engine resolves it, with the same code a run uses, so the preview never disagrees with the run.

  • Parameters come from the active profile.
  • Variables come from what the editor has seen in this session: the last run's steps, and Send now.
  • A stored secret shows as ••••.
  • A name without a value yet stays as written, and the preview lists it. A secret that is not stored is listed apart.
  • Generators use the experiment's pinned seed, or 0 when none is pinned, as the node's first execution. With a pinned seed, the preview shows the generated values the node's first execution in a run will send.

Extracting values ​

Extract value reads one value of the latest HTTP response on its path and writes it into a variable.

FieldWhat
Variablethe variable to write; the naming rules of parameters apply
Take fromwhere the value comes from (below)
JSON path, Header name or Pattern (group 1 if present)what to read, depending on the source
Take fromReadsValue
JSON fieldthe body as JSON, at a paththe JSON value: text, number, object, array
Headerthe first header with that name, in any casetext
Status codethe status codea number
Whole bodythe whole bodytext
Regular expressionthe first match in the bodycapture group 1 if the pattern has one, else the whole match

JSON paths. $.token, $.items[0].id, $["a b"], $['a b']['c-d']; the leading $. may be left out (token, items[0].id), and $ alone is the whole body.

Regular expressions use the syntax of the Rust regex engine, which has no look-around and no back-references. The match is searched anywhere in the body; anchor it with ^ and $ when that matters.

The step fails, naming what is missing, when:

  • no HTTP request ran before it on this path (check.no_response; the editor already refuses a graph where none can, graph.needs_http);
  • the body is not JSON, or the path is not in it;
  • the header is not there, or the pattern does not match;
  • the body is over the 256 KiB a response keeps, for a JSON path or the whole body, and for a pattern that matched nothing in the part kept (extract.truncated).

The timeline shows the value written: token = abc123.

Extract by clicking

Send now on an HTTP request shows its JSON response. Click a value in it: an Extract value node is added after the request, with the path filled in and a name taken from the key, and the value is known to the preview at once.

Variables ​

A variable holds a JSON value. These nodes write one:

NodeWritesOn the output
Extract valuethe extracted valueits output
Wait for OSC, Wait for UDP, Wait for MQTT, Wait for HTTP request, Wait for WebSocketwhat arrived, default name reply (request for HTTP)Matched only
OSC message, UDP datagram with wait for a replythe reply, default name replyits output

What a wait writes is an object; later fields read its parts:

WaitFields
OSCaddress, args, from, ms
UDPtext, hex, bytes, from, ms, and match with a pattern
MQTTtopic, and the fields of UDP
WebSocketthe fields of UDP, and json when the message is JSON
HTTP requestmethod, path, query, headers, body, json, params, from, ms

ms is the time from the branch's latest action to the arrival. The exact contents are in the nodes' reference.

Where a variable is known ​

A variable exists from the output that writes it onward, on the paths that pass through that output:

  • After a merge of alternative paths — the Yes and No of a branch meeting again — only what every path set is known.
  • After Join branches, what any branch into it set is known: all of them ran.
  • After a Loop's Done or Limit, and in its exit condition, what every iteration of the body sets is known.
  • A wait's variable is not known after its Timeout output.

Each parallel branch works on its own copy of the variables. A Join merges the copies in the order of its incoming wires, the later wire winning a name both set, so the result never depends on which branch finished first. See how a run moves.

Comparing values ​

Check value fails the run when a comparison does not hold; Branch on value leaves through Yes or No; a Loop uses the same comparison as its exit condition. Each has a Value, a Condition and an Expected value, and both texts are templates:

ValueConditionExpected
{{status}}less than300
{{reply.args[0]}}equals{{nonce}}
ConditionHolds when
equals, does not equalthe two are equal (not equal) — as numbers when both are numbers (200 equals 200.0), else as exact text, case included
less than, at most, greater than, at leastas numbers; a side that is not a number fails the step (compare.not_numbers) instead of a quiet no
containsthe value contains the expected text, case included
matches regexthe regular expression in the expected value matches anywhere in the value
is empty, is not emptythe value is empty, or not, after trimming spaces; the expected value is not used

A number is text that reads as one after trimming spaces: 42, -1.5, 1e3. The timeline shows the comparison as made, 401 = 200, each side cut to 120 characters.

Secrets ​

A token or password goes into a field as {{secret.NAME}}. The experiment file keeps only the name; the value stays where it is stored and never reaches the interface.

Where secrets live ​

Where Signal Lab runsStoreFrom the interface
Desktop app on Windowsthe Windows Credential Manager, under the service SignalLab, one entry per nameset, replace, remove
Desktop app on Linuxnone: a run that needs a secret fails with secret.unsupported—
Serverthe environment variable SIGNALLAB_SECRET_<NAME>, else the file <NAME> in its secrets folder, /run/secrets/signallab unless set otherwiseread-only
signallab on the command lineas a server, or the Windows Credential Manager with --secrets system—

The heading of the Secrets section has a tooltip that says which of these applies where you are: the Windows store, the server's environment and files, or — in the desktop app on Linux — that there is no store. There, secret.unsupported says the secrets are kept in the Windows Credential Manager, which the system does not have.

A secret belongs to the computer or the server, not to one experiment: two experiments that use {{secret.API_TOKEN}} use the same value.

On a server, the environment variable wins over the file. A file's trailing line break is not part of the value, and an empty file counts as no secret. The server's folder is set with --secrets-dir or SIGNALLAB_SECRETS_DIR; see the server. For the command line, see signallab run.

RuleLimit
Namestarts with a letter or _, then letters, digits and _; at most 128 characters
Valuenot empty, at most 16 KiB

Setting a secret ​

On Windows:

  1. Open Parameters. The Secrets section lists every secret the experiment's fields use, each stored or not set on this computer.
  2. Press Set… next to the name, or Secret for a name no field uses yet.
  3. Type the value — the field shows dots — and press Save or Enter. The field is cleared; nothing can read the value back.

Replace… stores a new value and Remove deletes it from the credential store. A name stored in this session is offered in the suggestions too.

In a browser connected to a server the section only says set on the server or not set on the server: set the value where the server runs, in one of two ways:

bash
# in the server's environment
SIGNALLAB_SECRET_API_TOKEN='…'
# or as a file in its secrets folder
printf '%s' '…' > /run/secrets/signallab/API_TOKEN

A file is read each time a run starts, so a changed file counts from the next run; a changed environment variable needs the server restarted.

Before a run ​

Every secret the run's fields use must be stored. A missing one stops the run before any traffic, at the first node and field that use it (secret.missing). Send now checks the same for its node.

Masking ​

While a run or a Send now uses secrets, every occurrence of their values is replaced by •••• in everything that leaves the engine:

  • step texts, errors and the variables a step wrote;
  • the run report;
  • Send now's result, the HTTP response it shows included;
  • the Inspector's frames, captured while the run lasts — in a hex dump each byte of a value becomes *, so offsets stay true.

Basic authentication sends name:password in base64; when either part holds a secret, that base64 text is masked too. The traffic itself carries the real value. The preview shows a stored secret as ••••. An Emulator's replies cannot use secrets.

Commands ​

The preview is experiment_resolve; secrets are listed, set and removed with secret_status, secret_set and secret_delete. No command returns a secret's value.