Join a Desk
Open a Desk so teammates can serve a lane from their own accounts, join one as a worker, leave, and change the join settings. Since 0.8.0. Every command output shown is what the CLI printed.
Since 0.8.0.
This guide covers both sides. The owner opens a Desk. A joiner serves one lane of it from their own accounts and connectors, so the work runs as them and their approvals are theirs.
Before you start
- The owner has a Desk with a
definition.json(see Add a Desk to a routine). - The owner has shared the Desk artifact with the joiner as a Contributor. Without that the CLI says “Ask the owner to share this Desk with you as a Contributor.”
- Both have the
routineos.pyCLI.$RObelow stands forpython3 skills/routineos/scripts/routineos.py.
Owner: open the Desk
Add one join block to the Desk-level definition.json. A Desk has one join block; a second fails compile.
"join": {
"version": 1,
"open": true,
"summary": "Draft replies to inbound sales mail from your own mailbox.",
"lanes": [{
"id": "sender",
"label": "Sender",
"kind": "mail-draft",
"match": {"type": "role", "field": "owner", "equals": "{mailbox}"},
"params": {"mailbox": "email"},
"connectors": ["gmail"],
"schedule": {"default": "0 22 * * 1,4", "min_interval_hours": 24},
"gate": "none"
}],
"cap": {"scope": "account", "maxRunsPerDay": 1}
}
Every key is described in the definition schema. Then seed as usual. Pass --url with the Desk’s address so joiners get a link; the seed stores it in meta/join.
$RO seed <dir> --out seed --url <desk_url>
To open or close an existing Desk later, the owner runs:
$RO join-settings <dir> --open --out out --docs <snapshot.json> --by <ownerId>
$RO join-settings <dir> --closed --out out --docs <snapshot.json> --by <ownerId>
It rewrites join.open in definition.json and plans the meta/join write. Only the owner can run it, and --by must equal meta/owner.by. A Desk with no join block refuses: “this Desk has no join block; answer the open-to-join question in build first”. Applying the batch is the owner’s decision.
Joiner: see the lanes
$RO join <desk_url> --docs <snapshot.json>
With no --lane, join lists the lanes and which are free. A closed Desk refuses with “This Desk is not open to join. Ask its owner to open it.” A lane served by someone else refuses with “This lane is served by someone else since
Joiner: take a lane
$RO join <desk_url> --docs <snapshot.json> --lane sender \
--param mailbox=you@example.com --connected gmail \
--schedule "0 22 * * 1,4" --tz Australia/Sydney \
--gate-email you@example.com --by <yourDeskId> --name "Your Name" --out join-out
join checks that the lane is free, that the connectors the lane needs are present, that the schedule keeps the lane’s minimum spacing, the timezone (default UTC), the gate email, --by and --out. It then prints the permission screen and writes worker-routine.json. Your Desk id is on the Workers page with a Copy control.
Create the scheduled routine from worker-routine.json, then run join again with --routine-id trig_... to also plan the workers/<laneKey> batch that records you as the holder. Apply that batch yourself.
The first run is report-only: it plans work but acts on nothing until a people/<deskId>/decisions/verified--<laneKey> document exists, written on your word after you read a run.
What a worker run does
A run reads its items with worker-items and writes only its own lane:
$RO worker-items --docs <snapshot.json> --worker <laneKey> --by <id> --out items.json
$RO budget-inputs --docs <snapshot.json> --worker <laneKey> --out budget
$RO write --docs <snapshot.json> --run run.json --out out --worker <laneKey> --artifactdata
items.jsonhasstopped,verified,todo,approvedandwaiting.write --workermay touch only the lane’s ownworkers/,outcomes/andpending/paths. When the lane is stopped it prints “stopped:; nothing written” and exits 0. Reasons are “no worker document”, “left”, “served by someone else” and “released”. - The run input needs
laneKey,byandrunId, and optionallyoutcomes,gates,budgetandhealth. - A
mail-draftlane drafts and you send. Amail-sendlane has asendgate; you approve on the Desk, and only you can. - Outcomes are
drafted,sent,skippedorfailed.
The daily cap applies per cap.scope. A run counts itself before it does work.
Joiner: leave
$RO leave <desk_url> --docs <snapshot.json> --by <yourDeskId> --out leave-out
It plans status: left and leftAt for your lanes and prints the recorded routine ids. Disable each scheduled routine after you confirm (RemoteTrigger with enabled: false); leave does not do it for you. If you serve no lane it says “You do not serve any lane on this Desk.”
Owner: release a lane
On the Workers page, Release (with a confirm) writes people/<ownerId>/decisions/release--<laneKey> naming the holder. The release is final for that holder. To let them back, remove that document.
Check it worked
- The Workers page shows your name on the lane after the first run.
worker-itemsshowsstoppedempty andverifiedset once you have accepted a run.- Re-run
joinwithout--lane: your lane no longer shows as free.
Common pitfalls
- Forgetting to share the Desk. The joiner must be a Contributor first.
- Applying the owner’s batches from a joiner’s session.
meta/is owner-written andpeople/is self-written. - Treating the approval email as the approval. It is a notice only. The click on the Desk is the approval, and editing the gate after the click voids it.
- Expecting a released joiner’s counters to count. The
deskcap counts only workers still serving.