From an idea to a working description. The order below is deliberate: each step is cheap and catches mistakes the next one would make expensive.
Write the smallest description that names what you need, and resolve it. No steps, no reservation, no hardware touched.
# ~/cosmos-experiments/my-first.yml
apiVersion: cosmos/v1
name: my-first
description: Can I get two nodes with a USRP?
resources:
tx:
requires: {sdr: {usrp: {}}}
rx:
requires: {sdr: {usrp: {}}}
cosmos-run my-first.yml --resolve-only
Either it names the nodes it would use, or it tells you exactly what is missing and whether waiting will help. Do this before reserving time, and repeat it on each sandbox you are considering.
--checksteps:
- name: What radio is this?
run: uhd_find_devices
target: tx
cosmos-run my-first.yml --check
A dry run reports what each step would do. Destructive steps — imaging above all — never execute under --check, which is why it is the right rehearsal before an experiment that requests an image.
run stepsIf a role already does what you need, one step replaces twenty:
steps:
- role: cosmos_lab.orchestration.gnuradio_ber
vars:
cosmos_ber_mods: [bpsk, qpsk]
See the role library. Reach for external when nothing fits — it takes raw Ansible tasks or any Galaxy role, and is deliberately unrestricted.
parameters:
sub: {type: string, default: bpsk, choices: [bpsk, qpsk, 8psk]}
amplitude: {type: float, default: 0.06, min: 0.001, max: 1.0}
Ship defaults that run. A description whose default transmit gain could not close the link decoded nothing across a 73-point sweep, and every published command for it silently overrode the default — which was the signal that the default was wrong, not that the commands were careful. If you find yourself always passing
-e, fix the default.
collect: [/run/cosmos/my-results]
teardown:
- name: Return the attenuator to maximum
service: {name: rfmatrix, atten_db: 95}
teardown runs whether or not the experiment succeeded. Anything that puts shared equipment into a non-default state belongs there — the next person's experiment depends on it.
Use pin |
Use requires |
|---|---|
| The experiment depends on those nodes — a fixed attenuator, a particular radio, a cable that runs nowhere else | Any node that can do the job will do |
| Refuses to run elsewhere, which is what you want | Runs wherever the requirement is met |
Ask for the least specific thing that genuinely matters. A description requiring a B210 will not run on a sandbox with X310s even when the experiment would have worked perfectly.
Read the cosmos-sdr guidance before trusting any number. Three things account for nearly every bad result:
dB above floor the run prints. Points beyond it are measuring the ceiling, not your setting.| Location | When |
|---|---|
~/cosmos-experiments/ |
Yours. Searched first, editable without root. |
/usr/share/cosmos-experiments/ |
The packaged set — root-owned, same on every console. |
A copy in your home directory keeps winning after the package is updated.
cosmos-runnow warns when your copy differs from a packaged one of the same name; if you did not mean to keep a fork, delete it. A forgotten copy is how a fixed default silently fails to take effect.
apiVersion: cosmos/v1
name: my-throughput
description: TCP throughput between any two nodes
parameters:
seconds: {type: int, default: 10, description: Measurement duration}
resources:
server:
requires: {}
software: {packages: [iperf3]}
client:
requires: {}
software: {packages: [iperf3]}
steps:
- name: Start the server
run: {cmd: iperf3 --server, background: true, unit: cosmos-iperf3}
target: server
- name: Wait for it to listen
wait: 2
- name: Measure
run:
cmd: >-
iperf3 --client {{ hostvars[groups['server'][0]].ansible_host }}
--time {{ seconds }} --json --logfile /run/cosmos/iperf3.json
target: client
collect: [/run/cosmos/iperf3.json]
teardown:
- name: Stop the server
run: systemctl stop cosmos-iperf3
target: server
ignore_errors: true
Last verified: 2026-08-04 on console.sb5.cosmos-lab.org.