Common BanterLab plans
Drive BanterLab from OPAL — three end-to-end recipes
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.
1. Build and persist a test set
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" }
2. Run an RL learning sweep over baud rate
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.
3. Compare two codecs side-by-side
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".
Canceling and skipping
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.
Troubleshooting
| 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 |
The test set has no path yet | Call banter.testset.export path=... once; subsequent save calls reuse that path. The lab keeps it in mCurrentTestSetPath. |
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.
What's next
- Drive a TaskPlan — chain these recipes together via OPAL's TaskPlan, with dependencies between steps.
- Read the reference — every OT, every parameter, every return value: BanterLab OPAL reference.