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).
| 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.
banter.param.listList 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.getRead 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.setSet 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-sweepConfigure 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.resetReset 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.importLoad 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.exportSave 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"}}
banter.preset.listList 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.applyOverwrite 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"}}
banter.codec.listList the codecs available in BanterCodecRegistry (CSS, DPSK, FSK, MCFSK, OFDM).
Returns: count, codecs (CSV).
{"task": "banter.codec.list"}
banter.codec.getRead 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.setSet 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.
banter.testset.describeDescribe 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-testsList 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-testReturn 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.newCreate 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-defaultsMerge 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-executionMerge 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-testAppend 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-testRemove a test by index or name.
{"task": "banter.testset.remove-test", "args": {"index": 0}}
banter.testset.update-testApply an overlay onto an existing test, identified by index or name.
{"task": "banter.testset.update-test", "args": {"index": 0, "iterations": 100}}
banter.testset.importReplace 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.exportSave 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.saveSave 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"}
banter.node.listList 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.addAdd 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.removeRemove a node by name.
{"task": "banter.node.remove", "args": {"name": "Carol"}}
banter.node.renameRename 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-routingUpdate 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-adversaryToggle 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-paramsConfigure 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-toneInject 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-levelMix 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-marginAdjust 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.sendTransmit 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-logClear the node's log pane.
{"task": "banter.node.clear-log", "args": {"name": "Alice"}}
banter.node.reset-codec-stateReset 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-countReset 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": "*"}}
banter.audio.list-devicesList 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-volumeRead the BanterLab bus volume (0..1).
Returns: volume.
{"task": "banter.audio.get-volume"}
banter.audio.set-volumeSet 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.calibrateRun 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" }
banter.run.statusRead 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-singleLong-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-testsetLong-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.abortIdempotent 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-testSkip the current test inside an active test set; the run continues with the next test.
{"task": "banter.run.skip-test"}
banter.results.queryQuery 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}}
banter.policy.infoRead RL controller diagnostics.
Returns: active, algorithm, reward-driver, episodes, best-reward, fingerprint, sidecar-path.
{"task": "banter.policy.info"}
banter.policy.set-algorithmSelect 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-driverChoose 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-weightsSet 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.resetClear reward history and the episode counter; configuration (algorithm, reward driver, weights) is preserved.
{"task": "banter.policy.reset"}
banter.policy.saveSave 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.loadLoad 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"}
banter.activity.list-panesList the toggleable panes on the BanterLab activity.
Returns: count, panes (JSON array of {name, visible}).
{"task": "banter.activity.list-panes"}
banter.activity.show-paneShow 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-resultsOpen the BanterResults activity (historical run viewer). Emits viewResultsRequested on the bus widget; BanterLabActivity wires that to a navigation push.
{"task": "banter.activity.show-results"}
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:
"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 a BanterLabActivity child of any top-level window."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.""Failed to save: test set has not been imported or exported yet. State: no change. Suggestion: call banter.testset.export first." (Or analogous wording for policy.save / policy.load.)"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.""Failed to calibrate: requires at least 2 nodes (have <n>). State: no change. Suggestion: add nodes via banter.node.add first."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.
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.