OXYGENOxygen/ Docs
Workspace

Collaboration

Comment threads, approval requests assigned to a named person, the opt-in gate that blocks a live launch, and the restricted client role.

Get a second person's documented go/no-go on the sequence about to launch or the list it sends to — and, when you want it, make that decision a precondition for the live launch. Comment threads hang off any workspace object, from a wiki page to a CRM record; approvals exist only where a gate does.

This is not the paid-action approval gate. That one asks you to authorize your own spend (--approved, --max-credits). This one asks somebody else to decide, stores their answer, and can hold the action until it arrives. Both can apply to the same launch.

Where it lives

Sidebar Data → Approvals opens /collab, titled "Waiting on you": approvals assigned to your login, plus comment threads that named you. Decide a card in place — Approve, Request changes, Reject, with an optional decision note. Withdrawing is the requester's move, not the assignee's: use approvals cancel, or the card's Withdraw button on the object's own panel. Members and roles in the header goes to /collab/members.

The sidebar entry is beta-gated — a workspace admin turns it on at /settings#/beta-features. The routes are not. /collab, /collab/approvals/<requestId>, and /collab/threads/<threadId> open by direct URL at either setting, deliberately: an emailed "you have been asked to approve" link must work for a client whose agency never flipped the toggle. Every approvals and comments command returns the matching URL as web_url.

Ask someone to decide

A sequence page carries an Approvals section with a Request approval button: add approver emails, a title, what to check, an expiry, then Send request. The form names no gate — each kind has exactly one today, so it is filled in for you. Tables mount the same panel behind the table toolbar's Comments button (not beta-gated). Knowledge pages and CRM records mount the thread half only: no gate covers those kinds, so there is nothing to request — approvals request on one is refused with gate_not_supported_for_subject. In a table, ask from the whole-table view — narrowed to a row, cell, or column the approvals half is hidden, because a gate is armed on the object, not on a cell.

oxygen approvals request --on sequence:9f1c2b7e --gate launch \
  --assignee client@acme.com --title "Q3 outbound" --expires-hours 48
oxygen approvals inbox                 # what is waiting on you
oxygen approvals list --on sequence:9f1c2b7e   # also --status, --mine, --limit
oxygen approvals show <requestId>      # the id from an email or a /collab link
oxygen approvals decide <requestId> --approve --note "Copy is fine"
oxygen approvals cancel <requestId>

You cannot assign yourself, and you cannot decide your own request — withdraw it with approvals cancel instead. Any one assignee can answer, and so can a workspace admin. Only --approve opens a gate: --changes-requested is a real answer that leaves it shut, and an expired request never opens one.

The gate

Asking blocks nothing on its own. A gate is workspace policy, and the owning primitive enforces it.

GateMeansSet onRefuses
launchapprove before a sequence sends livesequencesoxygen sequences start, oxygen sequences resume
signoffapprove the table's rows before they are workedtablesany enrollment from the bound source table — oxygen sequences enroll, a workflow's enroll step, the capture-feed sweep
oxygen approvals gate set --default sequence --gate launch --required
oxygen approvals gate set --on sequence:9f1c2b7e --gate launch --not-required
oxygen approvals gate list --on sequence:9f1c2b7e

Every gate is off until an admin turns it on, and gate set is admin-only and CLI/MCP-only — the web app has no control for it. A gated sequence still previews, dry-runs, and shows its steps, copy, and cost to everyone; that is what the approver reads. Only the live start refuses, with approval_required naming the pending request and its assignees. The yes binds to what was approved: rewrite the sequence afterwards and the next live start refuses with approval_stale. Clearing the gate is not authorization to spend — the live start still needs its own --approved and caps, and a start that raises --max-credits or --max-live-sends above the approved ceiling is refused with approval_cap_exceeded.

Comments

Threads hang off the object, never inside it: resolving one changes no campaign.

oxygen comments add "Second step reads as a pitch" --on sequence:9f1c2b7e
oxygen comments add "Wrong domain" --on table:2b7e --path row:8f2c1a4d#email
oxygen comments list --on table:2b7e --status all
oxygen comments resolve <threadId>       # --reopen flips it back

An @email in the body notifies that member. comments edit and comments delete are author-only and leave a trace.

The client role

The agency case: a login that can read the work, comment, and decide the approvals assigned to it — nothing else. Anything that runs, launches, spends, or reconfigures refuses with insufficient_role.

oxygen approvals client grant client@acme.com    # admin only
oxygen approvals client list                     # everyone, and what they may do
oxygen approvals client revoke client@acme.com

Invite the person in Settings → Members first; a grant on an invited-but-not-joined address applies the moment they accept. On /collab/members, the Access column shows who holds what, and an admin's Change access column switches a member between Full access and Client (restricted). An admin's role overrides the restriction, so the grant is refused until you demote them with oxygen orgs member-role <email> --role member.

On this page