Three end-to-end recipes the LLM (or you, via the OPAL chat overlay) can run against an open BanterLab session. Every step here is one OPAL Task — copy the JSON blocks straight into the chat. The full OT reference lives at BanterLab OPAL reference.
Prerequisites: open the BanterLab activity in hub at least once so the locator can find it. All OTs fail with a
BanterLab is not openmessage until that happens.
Goal: compose a 3-test set in memory, save it to disk, and walk away. Useful when you want to script up a campaign and run it later (or hand the JSON file to a colleague).
{ "task": "banter.testset.new", "args": {"name": "agc-sweep", "description": "AGC characterization across modes"} }
Add the two nodes the run will use. bus/bus keeps everything in-process; pass real device names (from banter.audio.list-devices) for over-the-air runs.
{ "task": "banter.node.add", "args": {"name": "Alice", "output-device": "bus", "input-device": "off"} }
{ "task": "banter.node.add", "args": {"name": "Bob", "output-device": "off", "input-device": "bus"} }
Set test-set-wide defaults so each test inherits payload, codec, and node routing without repeating itself.
{ "task": "banter.testset.set-defaults", "args": {
"codec": "FSK",
"payload": "HELLO",
"iterations": 30,
"nodes": ["Alice", "Bob"]
} }
Append three tests. Each only specifies what differs from the defaults — BanterTestSet::resolveTest merges them at run time.
{ "task": "banter.testset.add-test", "args": {"name": "agc-off", "parameters": [{"id": "agcMode", "current": 0}]} }
{ "task": "banter.testset.add-test", "args": {"name": "agc-normal", "parameters": [{"id": "agcMode", "current": 1}]} }
{ "task": "banter.testset.add-test", "args": {"name": "agc-freeze", "parameters": [{"id": "agcMode", "current": 2}]} }
Persist to disk; the path becomes the test set's tracked source so future banter.testset.save calls overwrite the same file.
{ "task": "banter.testset.export", "args": {"path": "/tmp/agc-sweep.json"} }
Later (after edits), re-save with one call:
{ "task": "banter.testset.save" }
Goal: let the lab tune fsk_baudRate in the [50, 600] range to maximize a reward driver that combines decode success and SNR. The bandit explores values and records improvements in a .rlpol sidecar.
Mark the parameter as learning (the sweep mode that hands control to the RL controller).
{ "task": "banter.param.set-sweep", "args": {
"id": "fsk_baudRate", "mode": "learning", "min": 50, "max": 600
} }
Configure the RL controller. simple-bandit works fine with 5–50 episodes; switch to ppo for heavier setups.
{ "task": "banter.policy.set-algorithm", "args": {"algorithm": "simple-bandit"} }
{ "task": "banter.policy.set-reward-driver", "args": {"driver": "decode-and-snr"} }
Set up a one-test set so the run is bounded.
{ "task": "banter.testset.new", "args": {"name": "learn-baud"} }
{ "task": "banter.testset.set-defaults", "args": {"codec": "FSK", "payload": "HELLO", "iterations": 30, "nodes": ["Alice", "Bob"]} }
{ "task": "banter.testset.add-test", "args": {"name": "sweep"} }
{ "task": "banter.testset.export", "args": {"path": "/tmp/learn-baud.json"} }
Kick the run. start-testset is long-running — the OT reports per-iteration progress and respects cancellation.
{ "task": "banter.run.start-testset" }
Once it finishes, snapshot the trained policy alongside the test set:
{ "task": "banter.policy.save" }
Reload it on the next session before re-running:
{ "task": "banter.policy.load" }
The sidecar's stored fingerprint is checked on load — if you've changed which params are in learning mode (or their bounds) since the save, policy.load fails with the stored vs expected fingerprint and the suggestion to retrain.
Goal: run the same payload across FSK and OFDM in one test set, then query the results database to compare scores.
{ "task": "banter.testset.new", "args": {"name": "fsk-vs-ofdm"} }
{ "task": "banter.testset.set-defaults", "args": {"payload": "HELLO", "iterations": 50, "nodes": ["Alice", "Bob"]} }
{ "task": "banter.testset.add-test", "args": {"name": "fsk", "codec": "FSK"} }
{ "task": "banter.testset.add-test", "args": {"name": "ofdm", "codec": "OFDM"} }
{ "task": "banter.testset.export", "args": {"path": "/tmp/fsk-vs-ofdm.json"} }
{ "task": "banter.run.start-testset" }
After completion, pull the most recent rows from the SQLite results database. The default limit is 100 rows, sorted by id descending — most-recent runs first.
{ "task": "banter.results.query", "args": {"limit": 200} }
The returned rows JSON array carries one entry per iteration with score, codec, params, and timestamps. The LLM can summarize that arbitrarily — typical shape is "FSK averaged 0.87 decode rate, OFDM averaged 0.74 over the same payload".
Both banter.run.start-single and banter.run.start-testset honour the OPAL runtime's cancellation. From a separate task:
{ "task": "banter.run.abort" }
Mid-test-set, you can also drop the current test and continue with the next one:
{ "task": "banter.run.skip-test" }
abort is idempotent — calling it with no run active is a benign success.
| Symptom | Likely cause | Fix |
|---|---|---|
Failed to ...: BanterLab is not open |
Activity not yet mounted | Open the BanterLab activity in hub once; the locator's children persist after that. |
Failed to set fsk_baudRate: parameter scoped to codec FSK but current codec is OFDM |
Codec scope guard | Call banter.codec.set with codec=FSK first. |
Failed to save: test set has not been imported or exported yet |
mCurrentTestSetPath empty |
Call banter.testset.export path=... once; subsequent save calls reuse that path. |
Failed to load policy: fingerprint mismatch (...) |
Learning param action space changed since save | Either revert the learning params, or accept retraining with a fresh policy. |
Failed to calibrate: requires at least 2 nodes |
Fewer than two nodes | banter.node.add until the bus has Alice + Bob. |
For the full failure-message catalogue see the reference's Failure messages section.