Tags
One label vocabulary across every primitive: declare and annotate tags, attach them on each owning surface, filter by them, and rename, merge, or delete them everywhere at once.
Tags are the campaign handle — there is no separate campaign object. Put the same label on a sequence, its lead table, the workflow feeding it, the conversations it produces, and its learnings wiki page, and tags get returns the whole footprint in one read. Signal types, saved Dashboards, runtime Agent definitions, and saved Ads search collections also join this index. Signal events, Agent runs, and individual ads do not carry tags.
A tag exists once it is declared (a registry row carrying a description, colour, and pinned flag) or carried (some item's tags array holds it). Either alone is enough, and drift is permanent in both directions: a declared tag nobody uses stays declared with a count of 0, and a carried tag nobody declared is fully real — it indexes, renames, merges, and deletes like any other.
Labels are lowercased, trimmed, and deduplicated, up to 50 per item. They are not slugified: "Q3 Outbound" becomes q3 outbound, spaces intact. Chip colour derives from the label unless someone sets an override, so the same tag looks the same everywhere by default.
Browse
oxygen tags list # every tag with per-primitive counts, most-used first
oxygen tags get q3-outbound # everything carrying the tag, deep-linked/tags is the same read plus editing: search across labels and descriptions, All / Pinned / Unused facets, New tag, per-row Edit… / Rename… / Pin / Delete…, and a selection bar with Merge into… / Pin / Unpin / Delete. Each tag's own page groups its items by kind. Tag chips filter the sequencer, publishing queue, and unibox lists; ?tag= deep-links into a pre-filtered view.
Declare and annotate
oxygen tags create q3-outbound --description "Q3 founder-led launch" --color blue --pinned
oxygen tags update q3-outbound --clear-color --unpinnedcreate is idempotent and never touches carriers — running it on a tag forty items already carry just annotates it. Colours are the named set (gray, red, orange, amber, yellow, green, teal, blue, indigo, purple, pink); omit --color to keep deriving it from the label. update changes only the flags you pass; --clear-description and --clear-color remove a value. To change the label itself use tags rename.
Attach
Tags are attached on each primitive's own surface:
oxygen sequences create ... --tags q3-outbound,saas-founders
oxygen sequences update <sequence> --tags q3-outbound # replaces the set; "" clears
oxygen tables tag <table> --tags q3-outbound
oxygen workflows tag <workflow> --tags q3-outbound # org metadata; no new revision
oxygen knowledge page upsert --slug <slug> --tags q3-outbound ...
oxygen publishing tags set <post> --add q3-outbound # add/remove deltas
oxygen inbox tag <conversation> --add q3-outbound # add/remove deltas; --channel linkedin/whatsapp for DMs
oxygen crm tag companies <record> --add tier-1 # add/remove deltas (run `oxygen crm setup` once to add the attribute)
oxygen mailboxes tag <mailbox> --tags q3-pool # pool sending identities under a campaign
oxygen senders tag <sender> --tags q3-pool
oxygen blueprints tag <blueprint> --tags q3-outbound # a saved blueprint; no re-export needed
oxygen domains tag acme.com --tags q3-pool # sending domains (survives `domains sync`)
oxygen projects tag outbound --tags q3-outbound # table projects
oxygen senders profiles tag <id> --tags q3-pool # a whole sending identity in one edit
oxygen voice numbers tag +14155550142 --tags q3-pool # dialing numbers
oxygen tags attach signal_type website_visit --tags q3-outbound
oxygen tags attach dashboard <id-or-slug> --tags q3-outbound
oxygen tags attach agent <slug> --tags q3-outbound
oxygen tags attach ad_search_collection <saved-search-id> --tags q3-outboundtags attach replaces the complete tag set on those four definition kinds; --tags "" clears it. It changes metadata only: a signal type remains a code-defined type, a Dashboard keeps its widgets, an Agent keeps its immutable versions, and a saved Ads search does not run. Over MCP, use oxygen_tags_attach. Save and browse Ads queries with oxygen ads searches or oxygen_ads_searches; see Ads.
Feeds carry no tags of their own: a live table's capture is part of the table, so oxygen tables tag is where a feed joins a campaign.
Most list commands accept --tag to filter — sequences list, publishing posts list, tables list, workflows list, messages query, blueprints list, signals registry, dashboards list, agent custom, and ads searches list — each taking one tag.
Every sending-identity list — mailboxes, domains, senders, senders profiles, voice numbers — takes --tag as a comma-separated set instead, matching any of them:
oxygen mailboxes list --tag q3-pool,q4-pooloxygen inbox list --tag is the exception: it filters on the tags of the conversation's campaign (its sequence), not the conversation's own tags. DMs have no campaign link, so a tag filter there returns email only.
An archived sequence is otherwise immutable but still accepts a tags-only update, so finished campaigns can still be filed into a learnings tag.
In the web app
Anywhere tags appear there is an editing affordance: Add tags beside a tag glyph on an item with none yet, collapsing to a + once it has some (tight call sites show the + in both states). It opens a picker listing the tags your workspace already uses, pinned first, each with its carrier count; type to filter. Pick any number of them; if what you type doesn't exist yet, choose Create "…" and it's added on save. On list rows, clicking an existing tag chip filters the list instead; the add affordance is what edits.
Your changes save when you close the picker. If someone (or the AI Inbox Tagger) adds a tag while you have it open, that tag is kept — the picker only applies what you actually changed.
Filtering the sending pool
On Sequencer → Accounts, every tab (Senders, LinkedIn, Email, WhatsApp, Phone numbers) has a Tags dropdown next to its search box. Pick one or more tags to narrow the list to anything carrying any of them; clicking a tag chip on a row does the same thing, and clicking it again removes it.
The Email tab lists inboxes under their domains, and both can be tagged. Tagging the domain is the shortcut: filter to that tag and the domain shows up with all of its inboxes, so you don't have to tag twenty inboxes one at a time. Combine the tag filter with another filter (say Provider) and it narrows to inboxes proper — a tagged domain with no matching inbox drops out.
Auto-tagging inbound conversations
The AI Inbox Tagger (a workspace agent at /agents/inbox-auto-tagger; the /agents nav entry is beta-gated) classifies every new inbound conversation against a tag allowlist you configure and applies the matches automatically — riding the same analysis call that already categorizes replies, so it adds no model cost. It only ever adds tags, and it never re-adds a tag you removed from a conversation.
oxygen inbox auto-tagger get
oxygen inbox auto-tagger set --enabled --tags "pricing-question,partnership,support-request"
oxygen inbox auto-tagger set --channels email,linkedin # default is all three
oxygen agent get inbox-auto-tagger # state + recent tag applications--tags replaces the whole allowlist (max 50). --tags "" clears it, which pauses tagging while leaving the agent enabled.
Reshape the vocabulary
oxygen tags rename q3-outbound q3-founder-launch # preview: per-kind counts, no writes
oxygen tags rename q3-outbound q3-founder-launch --apply
oxygen tags merge q3-outbound q3-old --into q3-founder-launch --apply
oxygen tags delete q3-old q3-scratch --apply # IRREVERSIBLEAll three are preview-first: without --apply they print per-kind (and per-tag) would-change counts and mutate nothing. All three are unfiltered by liveness — archived and finished items are rewritten too, so an un-archive never resurfaces the old name. That is why a preview count can legitimately exceed what tags list shows, which hides archived carriers. All three are safe to re-run after a partial failure.
Rename onto an existing tag behaves exactly like merging into it. When a tag absorbs another, the surviving tag wins per field, but a field it left empty is filled from the first source that has one; pinned ORs. delete strips the tag from every carrier and removes its declaration.
Semantics worth knowing
- Whole-set vs delta is per-surface, and the flag names it:
--tagsreplaces every tag on the item (--tags ""clears them) with no preview step, so read the current tags first when you mean to edit rather than overwrite. Exactly three surfaces take--add/--removedeltas instead — publishing posts, inbox conversations, and CRM records — so the auto-tagger and your edits merge instead of clobbering each other. Every other surface is whole-set, projects, sending domains, sender profiles and phone numbers included. - The tag index hides finished work: archived tables, wiki pages, blueprints, domains, projects, sender profiles and conversations drop out, as do canceled posts, disabled mailboxes, disconnected senders, and released phone numbers. Archived sequences stay in.
- The unibox's warmup/DMARC/bounced markers are system badges — derived state, not tags.
- Tags carry no approvals and spend no credits — they are workspace metadata.