resources: says what hardware an experiment needs. The framework then decides whether a given sandbox can supply it — and if not, says exactly why.
This is the part of the system worth understanding properly, because it is what makes one description run on several sandboxes, and what stops it running somewhere that merely looks similar.
resources:
tx:
pin: [node1-1.sb5.cosmos-lab.org]
rx:
pin: [node1-2.sb5.cosmos-lab.org]
Use it when the experiment depends on something only those nodes have: a particular radio, a fixed attenuator between a specific pair, a cable that runs where no other cable runs. A pinned description refuses to run elsewhere rather than substituting something that looks equivalent.
resources:
server:
requires: {} # any node at all
software: {packages: [iperf3]}
client:
requires: {}
software: {packages: [iperf3]}
resources:
rx:
requires: {sdr: {usrp: {}}} # any node with a USRP
count: 2
image: baseline-sdr.ndz
This is the portable form. The same file runs wherever the requirement can be met, which is the whole point of describing needs rather than naming nodes.
Pin and
requiresare mutually exclusive. Pinning already decides resolution, so a requirement cannot also be honoured. The validator says so rather than quietly preferring one: "pinning fixes resolution, so 'requires' cannot be honoured. Keep one."
| Key | Meaning |
|---|---|
pin |
List of node FQDNs. Mutually exclusive with requires. |
requires |
Capabilities the node must have. {} means "any node". |
count |
How many nodes this resource needs. Default 1. |
image |
Disk image the node must be running. Triggers imaging if it is not. |
image_policy |
never, if-needed (default), always |
software |
Packages or sources to stage on the node |
image is destructive — know what image_policy does| Policy | Behaviour |
|---|---|
if-needed |
Image only if the node is running something else. The default. |
never |
Never image. Fail if the node is not already correct. |
always |
Image every run, guaranteeing a clean start. Slowest, and destroys whatever was there. |
A node's contents are not yours to assume. Someone else's work may be on it, and imaging destroys it silently. Rehearse with --check, which never images.
The refusal is the most important output this system produces, so it is written to be actionable. A real one, from asking for a GPU on the grid:
'needs-impossible' cannot run on
grid right now.
63 powered-on node(s) were considered.
- worker: no candidate provides gpu (model=)
needed 3, found 0
each requirement, counted independently:
gpu: satisfied by 0 of 63 candidate(s) <-- nothing provides this
Availability is point-in-time - a subdomain that qualified earlier
may not now. Try another subdomain, or wait for nodes to return.
Four things are on offer there, and each answers a question you would otherwise have to investigate by hand:
<-- nothing provides this on the one at fault. When you ask for four things and three are common, this points straight at the fourth instead of leaving you to bisect.For a pinned description the last part is different, because waiting will not help:
- tx: pinned nodes belong to another subdomain
needed 1, found 0
node18-2.grid.cosmos-lab.org
-> in grid, not sb4
This experiment is pinned to specific hardware and does not run
anywhere else. Run it from console.grid.cosmos-lab.org.
It names the console to use. That is the difference between an error and an instruction.
Several experiments can run on one subdomain at the same time, and they must not land on the same node. Each run therefore leases what it resolved onto — nodes, and shared instruments such as the XY table or the attenuator matrix — for as long as it runs.
Two consequences you will see:
A node another run holds is simply not a candidate. Resolution leaves it out before matching, so if a free node elsewhere in the sandbox can satisfy your resource, you get it and never notice.
If nothing else can satisfy it, the refusal says so. A busy sandbox reads differently from one that lacks the hardware:
- rx: no candidate provides sdr.usrp (model=b210),
1 node(s) excluded: held by another run on this subdomain
That distinction matters. Without it a sandbox that is merely occupied looks like one missing a radio it demonstrably has, and you go hunting a fault that is not there.
If your run is refused because everything it needs is taken, it has claimed nothing — leases are all-or-nothing, so a blocked run never sits on hardware somebody else could be using. Wait for the other run, pick another subdomain, or check cosmos-run --status if you believe it has already finished.
Leases are released when the experiment tears down, and a lease whose run no longer exists is reclaimed automatically — including when a run ends because a reservation expired. Nothing has to be cleaned up by hand.
Shared instruments are leased whole, not per port. Two runs using different ports of one attenuator matrix still share its switch state, so the matrix goes to one experiment at a time.
Nodes go offline out of band. Someone reimages one, a radio stops answering, a rack loses power. The framework never trusts a cached host list — it asks the inventory service on every run, and a description that resolved this morning may not this afternoon.
The same caution applies to written documentation about what exists. Measured 2026-08-03: a live sweep of the grid's MIMO racks found 19 X310s answering, while the inventory service's own cached scan — last run in March — listed 22, and the two disagreed in both directions. Twenty-four are installed. If your experiment depends on a particular radio, probe for it; do not plan from a table, including the tables in this wiki.
requires is matched against what the inventory service reports about each node. The vocabulary follows the inventory's own structure — device families, daughterboards, chipsets, CPU and architecture, and so on — rather than a hard-coded list in the framework, so it grows as the inventory does.
requires: {} # any node
requires: {sdr: {usrp: {}}} # any USRP
requires: {sdr: {usrp: {b210: {}}}} # specifically a B210
The nesting narrows the match. Ask for the least specific thing that your experiment genuinely needs: a description requiring a B210 will not run on a sandbox that has X310s, even when the experiment would have worked perfectly.
Some radios are not nodes at all. The grid's X310s are
rfdeviceresources in MIMO racks — no host operating system, no disk,INF_pxe_imageofNA— soomf loaddoes not apply to them and they are driven from a compute node over the control network. An experiment that wants one pins the compute node and addresses the radio explicitly.
cosmos-run my-experiment.yml --resolve-only
Reads the inventory, decides, prints the answer, stops. No reservation needed, no hardware touched. Use it to find out whether an idea is possible before booking time, and to check a description while writing it.
"msg": "ofdm-ber-grid on grid: tx=node18-2.grid.cosmos-lab.org; rx=node18-19.grid.cosmos-lab.org "
Last verified: 2026-08-03 on console.grid.cosmos-lab.org.