BanterLab OPAL reference
Drive BanterLab from OPAL Tasks
Every operator-reachable feature in BanterLab is exposed as an OPAL Task — 58 OTs in total, grouped here by resource family. For walkthroughs and copy-pasteable plans see Common BanterLab plans.
All lab-bound OTs require BanterLab to be open in hub. The locator returns
nullptruntil the activity has been mounted at least once; OTs fail with the canonical not-open message (see Failure messages).
Resource families and permission scopes
| Family | Read OTs | Write OTs | Permission scope (writes) |
|---|---|---|---|
| Parameters / presets | 3 | 6 | banter.param.write |
| Codec | 2 | 1 | banter.param.write |
| Test sets | 3 | 9 | banter.testset.write |
| Nodes | 1 | 13 | banter.node.write |
| Audio | 2 | 2 | banter.audio.write |
| Run | 1 | 4 | banter.runtime (start-* only) |
| Policy / RL | 1 | 6 | banter.policy.write |
| Activity | 1 | 2 | banter.activity.write |
| Results | 1 | — | — |
Read-only OTs require no permission. Permission scopes match the resource-scope strings used by OpalResourceSemaphore, so granting access also unlocks concurrent serialization for that resource.
Parameters
banter.param.list
List BanterLab parameters with their current value, sweep mode, and range.
| Arg | Required | Default | Description |
|---|---|---|---|
codec |
no | — | Filter to params with the given codec scope (fsk, ofdm, dpsk, css, mcfsk, common, meta, preprocessor, gibber, bus, adversary). |
Returns: count, params (JSON array of rows; each row has id, label, codec, description, current, default, hardMin, hardMax, decimals, sweepMode, sweepMin, sweepMax, sweepSteps).
{ "task": "banter.param.list" }
banter.param.get
Read one parameter row by id.
| Arg | Required | Description |
|---|---|---|
id |
yes | Parameter id (e.g. fsk_baudRate). |
Returns: id, current, sweepMode, row (full JSON object).
{ "task": "banter.param.get", "args": {"id": "fsk_baudRate"} }
banter.param.set
Set one parameter's current value. Bounds-checked against hardMin/hardMax. Codec-scoped params (fsk_*, ofdm_*, dpsk_*, css_*, mcfsk_*) fail with a structured suggestion when the active codec doesn't match. Setting id="codec" with an integer 0-4 is equivalent to banter.codec.set.
| Arg | Required | Description |
|---|---|---|
id |
yes | Parameter id. |
value |
yes | New current value (numeric). |
{ "task": "banter.param.set", "args": {"id": "fsk_baudRate", "value": 300} }
banter.param.set-sweep
Configure how a parameter is swept during a test run.
| Arg | Required | Default | Description |
|---|---|---|---|
id |
yes | — | Parameter id. |
mode |
yes | — | One of fixed, stepped, random, learning. |
min |
no | leave unchanged | Lower bound. |
max |
no | leave unchanged | Upper bound. |
steps |
no | 10 | Step count for stepped mode (>=1). |
{ "task": "banter.param.set-sweep", "args": {"id": "fsk_baudRate", "mode": "learning", "min": 50, "max": 600} }
banter.param.reset
Reset a parameter (or * for all) to its registry default. Sweep state resets to fixed.
| Arg | Required | Description |
|---|---|---|
id |
yes | Parameter id (or *). |
{"task": "banter.param.reset", "args": {"id": "*"}}
banter.param.import
Load a parameter snapshot from a JSON file written by banter.param.export.
| Arg | Required | Description |
|---|---|---|
path |
yes | File path. |
{"task": "banter.param.import", "args": {"path": "/tmp/params.json"}}
banter.param.export
Save the current parameter set to a JSON file.
| Arg | Required | Description |
|---|---|---|
path |
yes | Destination file path. |
{"task": "banter.param.export", "args": {"path": "/tmp/params.json"}}
Presets
banter.preset.list
List the named parameter presets registered in BanterParamSet (audible-fsk, audible-dpsk, audible-dqpsk, ultrasonic-fsk, ultrasonic-dpsk, fast-ofdm, robust-css, max-throughput).
Returns: count, presets (CSV).
{ "task": "banter.preset.list" }
banter.preset.apply
Overwrite the parameter set with a named preset.
| Arg | Required | Description |
|---|---|---|
name |
yes | Preset name (see banter.preset.list). |
{"task": "banter.preset.apply", "args": {"name": "audible-fsk"}}
Codecs
banter.codec.list
List the codecs available in BanterCodecRegistry (CSS, DPSK, FSK, MCFSK, OFDM).
Returns: count, codecs (CSV).
{"task": "banter.codec.list"}
banter.codec.get
Read the currently active codec name. Reflects the bus-level codec selection (the codec used by the first node).
Returns: codec.
{"task": "banter.codec.get"}
banter.codec.set
Set the active codec by name. Equivalent to banter.param.set id=codec value=N with the integer encoding (0=FSK, 1=OFDM, 2=DPSK, 3=CSS, 4=MCFSK).
| Arg | Required | Description |
|---|---|---|
codec |
yes | One of FSK, OFDM, DPSK, CSS, MCFSK. |
{ "task": "banter.codec.set", "args": {"codec": "FSK"} }
If you intend to change codec and codec-specific parameters in one plan, call banter.codec.set first — the fsk_* / ofdm_* / dpsk_* / css_* / mcfsk_* parameters apply only when the matching codec is active.
Test sets
banter.testset.describe
Describe the currently loaded BanterLab test set: name, description, test count, defaults, execution config, and the path it was loaded from (empty for unsaved in-memory sets).
Returns: name, description, test-count, defaults (JSON), execution (JSON), current-path.
{"task": "banter.testset.describe"}
banter.testset.list-tests
List the per-test summaries inside the current test set.
Returns: count, tests (JSON array of {name, codec, iterations, payload, paramOverlay, nodes}).
{"task": "banter.testset.list-tests"}
banter.testset.describe-test
Return the fully resolved spec for one test (defaults merged with per-test diff).
| Arg | Required | Description |
|---|---|---|
index |
one of | 0-based test index. |
name |
one of | Test name. |
{"task": "banter.testset.describe-test", "args": {"index": 0}}
banter.testset.new
Create a fresh in-memory test set. Fails with the spec's "unsaved changes" message when one is already loaded — call banter.testset.export first or reimport on a blank file.
| Arg | Required | Description |
|---|---|---|
name |
yes | Test set name. |
description |
no | Human description. |
{"task": "banter.testset.new", "args": {"name": "demo"}}
banter.testset.set-defaults
Merge a partial overlay onto the current test set's defaults.
| Arg | Required | Description |
|---|---|---|
codec |
no | Codec name. |
payload |
no | Default payload. |
iterations |
no | Default iterations per test. |
parameters |
no | JSON array of param overlay objects. |
nodes |
no | JSON array of node names or full BanterNodeSpec objects. |
{"task": "banter.testset.set-defaults", "args": {"codec": "FSK", "iterations": 50, "payload": "HELLO"}}
banter.testset.set-execution
Merge an execution-config overlay onto the current test set.
| Arg | Required | Description |
|---|---|---|
connection-string |
no | DB connection string. |
ncores |
no | Parallel cores for batch (0 = auto). |
settle-time-ms |
no | Per-iteration settle time. |
timeout-per-test-ms |
no | Per-test timeout. |
quit-on-complete |
no | Quit hub when test set completes. |
{"task": "banter.testset.set-execution", "args": {"ncores": 1, "timeout-per-test-ms": 60000}}
banter.testset.add-test
Append a test spec to the current test set. The name is required; everything else is an optional overlay (only the fields you supply override the test set's defaults at run time).
| Arg | Required | Description |
|---|---|---|
name |
yes | Test name. |
codec, payload, iterations, parameters, nodes |
no | Overlay fields. |
{"task": "banter.testset.add-test", "args": {"name": "t1", "codec": "FSK"}}
banter.testset.remove-test
Remove a test by index or name.
{"task": "banter.testset.remove-test", "args": {"index": 0}}
banter.testset.update-test
Apply an overlay onto an existing test, identified by index or name.
{"task": "banter.testset.update-test", "args": {"index": 0, "iterations": 100}}
banter.testset.import
Replace the current test set with one loaded from a JSON file. Updates the tracked source path so subsequent banter.testset.save calls overwrite this file.
| Arg | Required | Description |
|---|---|---|
path |
yes | Test-set JSON file path. |
{"task": "banter.testset.import", "args": {"path": "/tmp/set.json"}}
banter.testset.export
Save the current test set to a JSON file and track its path.
| Arg | Required | Description |
|---|---|---|
path |
yes | Destination file path. |
{"task": "banter.testset.export", "args": {"path": "/tmp/set.json"}}
banter.testset.save
Save to the tracked source path. Fails with "test set has not been imported or exported yet" when the path is empty — call banter.testset.export first.
{"task": "banter.testset.save"}
Nodes
banter.node.list
List BanterLab nodes with their I/O routing and adversary flags.
Returns: count, nodes (JSON array of {name, outputDevice, inputDevice, advOut, advIn, codec}).
{"task": "banter.node.list"}
banter.node.add
Add a BanterLab node. When name is omitted, a unique name is auto-generated. Default routing is bus/bus.
| Arg | Required | Default | Description |
|---|---|---|---|
name |
no | auto | Node name. |
output-device |
no | bus |
bus, off, or a host audio device name. |
input-device |
no | bus |
Same. |
{"task": "banter.node.add", "args": {"name": "Carol"}}
banter.node.remove
Remove a node by name.
{"task": "banter.node.remove", "args": {"name": "Carol"}}
banter.node.rename
Rename a node; the new-name must be non-empty and not already taken.
| Arg | Required | Description |
|---|---|---|
name |
yes | Current name. |
new-name |
yes | New name. |
{"task": "banter.node.rename", "args": {"name": "Alice", "new-name": "Anna"}}
banter.node.set-routing
Update output/input device routing.
| Arg | Required | Description |
|---|---|---|
name |
yes | Node name. |
output-device |
one of | bus, off, or device name. |
input-device |
one of | Same. |
{"task": "banter.node.set-routing", "args": {"name": "Alice", "output-device": "off"}}
banter.node.set-adversary
Toggle adversary-out / adversary-in flags.
| Arg | Required | Description |
|---|---|---|
name |
yes | Node name. |
adv-out |
one of | Adversary mix on TX (bool). |
adv-in |
one of | Adversary mix on RX (bool). |
{"task": "banter.node.set-adversary", "args": {"name": "Alice", "adv-out": true}}
banter.node.set-adversary-params
Configure the node's adversary effect parameters: noise gain, dribble level, interference tone frequency/amplitude.
| Arg | Required | Default | Description |
|---|---|---|---|
name |
yes | — | Node name. |
noise |
no | 0 | White-noise SNR target. |
dribble |
no | 0 | Dribble amplitude. |
tone-freq |
no | 0 | Interference tone frequency (Hz). |
tone-amplitude |
no | 0 | Interference tone amplitude (0..1). |
{"task": "banter.node.set-adversary-params", "args": {"name": "Alice", "noise": 0.1, "tone-freq": 2200, "tone-amplitude": 0.3}}
banter.node.set-monitor-tone
Inject a continuous test tone on the node's output. freq-hz=0 or amplitude-pct=0 disables.
| Arg | Required | Description |
|---|---|---|
name |
yes | Node name. |
freq-hz |
yes | Tone frequency. |
amplitude-pct |
yes | 0..100. |
{"task": "banter.node.set-monitor-tone", "args": {"name": "Alice", "freq-hz": 440, "amplitude-pct": 25}}
banter.node.set-noise-level
Mix white noise into the node's output.
| Arg | Required | Description |
|---|---|---|
name |
yes | Node name. |
amplitude-pct |
yes | 0..100. |
{"task": "banter.node.set-noise-level", "args": {"name": "Alice", "amplitude-pct": 5}}
banter.node.set-bandpass-margin
Adjust the RX bandpass margin (frequency tolerance for the chirp/tone band).
| Arg | Required | Description |
|---|---|---|
name |
yes | Node name. |
margin |
yes | Typically 0.1..2.0. |
{"task": "banter.node.set-bandpass-margin", "args": {"name": "Alice", "margin": 0.5}}
banter.node.send
Transmit a single packet from the named node, bypassing the auto-test loop. Payload is UTF-8 text; bytes-on-the-wire equal the UTF-8 encoding.
| Arg | Required | Description |
|---|---|---|
name |
yes | Source node. |
payload |
yes | UTF-8 payload. |
{"task": "banter.node.send", "args": {"name": "Alice", "payload": "HELLO"}}
banter.node.clear-log
Clear the node's log pane.
{"task": "banter.node.clear-log", "args": {"name": "Alice"}}
banter.node.reset-codec-state
Reset the codec internal state (RX/TX buffers, interleaver, sync trackers) without recreating the codec.
{"task": "banter.node.reset-codec-state", "args": {"name": "Alice"}}
banter.node.reset-drop-count
Reset the audio drop counter on a node. Pass name='*' to reset every node plus the bus drop counter.
{"task": "banter.node.reset-drop-count", "args": {"name": "*"}}
Audio
banter.audio.list-devices
List the host's audio I/O devices in the requested direction. The synthetic bus device is always returned first.
| Arg | Required | Default | Description |
|---|---|---|---|
direction |
no | output |
input or output. |
Returns: count, devices (JSON array of {name, description, is-default, synthetic}).
{"task": "banter.audio.list-devices", "args": {"direction": "output"}}
banter.audio.get-volume
Read the BanterLab bus volume (0..1).
Returns: volume.
{"task": "banter.audio.get-volume"}
banter.audio.set-volume
Set the bus volume; range-checked.
| Arg | Required | Description |
|---|---|---|
volume |
yes | 0.0 (mute) … 1.0 (unity). |
{"task": "banter.audio.set-volume", "args": {"volume": 0.5}}
banter.audio.calibrate
Run the OKChirp round-trip calibration; emits a chirp pattern and writes a <nodename>.profile per node. Requires at least 2 nodes.
{ "task": "banter.audio.calibrate" }
Run lifecycle
banter.run.status
Read the live BanterLab run status.
Returns: running, current-test-index, current-iteration, total-iterations, last-score, test-name, codec.
{"task": "banter.run.status"}
banter.run.start-single
Long-running. Run a single interactive test using the named TX/RX nodes. Reports per-iteration progress; cancellable via the runtime.
| Arg | Required | Description |
|---|---|---|
tx-node |
yes | Transmit node name. |
rx-node |
yes | Receive node name. |
payload |
no | UTF-8 payload override. |
iterations |
no | Iteration override. |
{"task": "banter.run.start-single", "args": {"tx-node": "Alice", "rx-node": "Bob", "payload": "HELLO", "iterations": 5}}
banter.run.start-testset
Long-running. Iterate the loaded test set. Per-test + per-iteration progress; cancel triggers bus->abortRun().
| Arg | Required | Default | Description |
|---|---|---|---|
close-on-completion |
no | false | Close BanterLab when finished. |
{"task": "banter.run.start-testset"}
banter.run.abort
Idempotent abort of any active run. Returns was-running so the LLM can distinguish "stopped a run" from "no-op".
{"task": "banter.run.abort"}
banter.run.skip-test
Skip the current test inside an active test set; the run continues with the next test.
{"task": "banter.run.skip-test"}
Results
banter.results.query
Query the BanterLab results SQLite database.
| Arg | Required | Default | Description |
|---|---|---|---|
session-id |
no | — | Filter by session id. |
limit |
no | 100 | Max rows (1..9999). |
Returns: count, rows (JSON array — one entry per iteration with score, codec, params, timestamps), db-path.
{"task": "banter.results.query", "args": {"limit": 50}}
Policy / RL
banter.policy.info
Read RL controller diagnostics.
Returns: active, algorithm, reward-driver, episodes, best-reward, fingerprint, sidecar-path.
{"task": "banter.policy.info"}
banter.policy.set-algorithm
Select the RL algorithm. Auto-creates the controller if no learning run has been started yet.
| Arg | Required | Description |
|---|---|---|
algorithm |
yes | simple-bandit or ppo. |
{"task": "banter.policy.set-algorithm", "args": {"algorithm": "simple-bandit"}}
banter.policy.set-reward-driver
Choose how each iteration's outcome becomes a scalar reward.
| Arg | Required | Description |
|---|---|---|
driver |
yes | One of decode-only, decode-and-snr, throughput, resilience, composite. |
{"task": "banter.policy.set-reward-driver", "args": {"driver": "decode-and-snr"}}
banter.policy.set-composite-weights
Set the four scalars ([decode, snr, throughput, resilience]) used by the composite reward driver. Defaults: [1.0, 0.3, 0.2, 0.2].
| Arg | Required | Description |
|---|---|---|
weights |
yes | JSON array of 4 numbers. |
{"task": "banter.policy.set-composite-weights", "args": {"weights": [1.0, 0.3, 0.2, 0.2]}}
banter.policy.reset
Clear reward history and the episode counter; configuration (algorithm, reward driver, weights) is preserved.
{"task": "banter.policy.reset"}
banter.policy.save
Save the policy blob to <testsetbase>.rlpol. Fails when no controller exists or when the test set has no tracked path.
{"task": "banter.policy.save"}
banter.policy.load
Load the policy blob from the test-set sidecar. The blob's stored fingerprint must match the controller's current fingerprint (derived from the learning-param action space). Mismatches return both fingerprints with a Suggestion to retrain.
{"task": "banter.policy.load"}
Activity
banter.activity.list-panes
List the toggleable panes on the BanterLab activity.
Returns: count, panes (JSON array of {name, visible}).
{"task": "banter.activity.list-panes"}
banter.activity.show-pane
Show or hide a pane by name.
| Arg | Required | Default | Description |
|---|---|---|---|
pane |
yes | — | One of bus, testsets, parameters, nodes, learning. |
visible |
no | true | Visibility flag. |
{"task": "banter.activity.show-pane", "args": {"pane": "learning", "visible": true}}
banter.activity.show-results
Open the BanterResults activity (historical run viewer). Emits viewResultsRequested on the bus widget; BanterLabActivity wires that to a navigation push.
{"task": "banter.activity.show-results"}
Failure messages
Every OT follows the OPAL Task System format: "Failed to <action>: <what>. State: <left behind>. Suggestion: <next>." so an LLM can recover programmatically.
Canonical messages used across multiple OTs:
- Lab not open —
"Failed to <action>: BanterLab is not open. State: no change. Suggestion: navigate to BanterLab activity first."Returned by every lab-bound OT when the locator can't find aBanterLabActivitychild of any top-level window. - Codec scope mismatch —
"Failed to set <id>: parameter scoped to codec <X> but current codec is <Y>. State: no change. Suggestion: call banter.codec.set with codec=<X> first." - Test set has no path —
"Failed to save: test set has not been imported or exported yet. State: no change. Suggestion: call banter.testset.export first."(Or analogous wording forpolicy.save/policy.load.) - Policy fingerprint mismatch —
"Failed to load policy: fingerprint mismatch (stored '<a>', expected '<b>'). State: no change. Suggestion: learning param action space changed since save — train a fresh policy for the current params, or revert the test set." - Calibration node count —
"Failed to calibrate: requires at least 2 nodes (have <n>). State: no change. Suggestion: add nodes via banter.node.add first."
Worked example — full plan from the spec
The 12-step prompt the spec was designed around — reachable end-to-end through the OTs above:
{ "task": "banter.audio.list-devices", "args": {"direction": "output"} }
{ "task": "banter.audio.list-devices", "args": {"direction": "input"} }
{ "task": "banter.testset.new", "args": {"name": "learn-baud-codec-agc"} }
{ "task": "banter.node.add", "args": {"name": "Alice", "output-device": "default", "input-device": "default"} }
{ "task": "banter.node.add", "args": {"name": "Bob", "output-device": "default", "input-device": "default"} }
{ "task": "banter.testset.set-defaults", "args": {"payload": "HELLO", "iterations": 50} }
{ "task": "banter.testset.add-test", "args": {"name": "learn-baud", "codec": "FSK",
"parameters": [{"id": "fsk_baudRate", "sweepMode": "learning", "min": 50, "max": 600}]} }
{ "task": "banter.testset.add-test", "args": {"name": "learn-codec",
"parameters": [{"id": "codec", "sweepMode": "learning", "min": 0, "max": 4}]} }
{ "task": "banter.testset.add-test", "args": {"name": "learn-agc",
"parameters": [{"id": "agcMode", "sweepMode": "learning", "min": 0, "max": 4}]} }
{ "task": "banter.policy.set-reward-driver", "args": {"driver": "decode-and-snr"} }
{ "task": "banter.testset.export", "args": {"path": "/tmp/learn.json"} }
{ "task": "banter.run.start-testset" }
For three guided recipes built on this surface — including a learning-sweep walkthrough and a codec comparison — see Common BanterLab plans.
Maintainer note
When adding a new BanterLab feature, ship the OT and its entry in this reference in the same commit. Doc drift is the fastest way for the OPAL surface to lose the LLM's trust — the failure messages and parameter shapes here are what the model uses to plan and recover.