---
name: linbox-tags
description: >-
  Manage Linbox LinkedIn tags — list the account tag dictionary, upsert tag
  metadata, attach/detach tags on personas, threads and posts, and filter searches
  by tag. Use when a request involves LinkedIn tags/labels through Linbox, when
  filtering linbox_search_thread, linbox_search_persona or linbox_search_post by
  tags, or when calling linbox_list_tags, linbox_upsert_tag, linbox_add_persona_tags,
  linbox_remove_persona_tags, linbox_add_thread_tags, linbox_remove_thread_tags,
  linbox_add_post_tags, or linbox_remove_post_tags.
contract_version: "3.0"
---

# Linbox Tags

Account-scoped labels attached to personas, threads and posts, plus the
tag-filtering semantics of Linbox search. Tags are the canonical way to segment
people, conversations and content (`lead`, `hot`, `customer`, ...) without schema
changes.

Read the root [Linbox](../SKILL.md) skill first for the shared auth, identifier,
and response contract; this skill only covers the tags group.

## When to use

- Listing the account's tag dictionary (names, descriptions, colors, counts).
- Creating a tag or editing its `description` / `color` (no attachment).
- Attaching or detaching tags on a person (persona), a conversation (thread), or a
  cached post (activity).
- Filtering `linbox_search_thread`, `linbox_search_persona`, or `linbox_search_post`
  by `tags=[...]`.

## Prerequisites

- A Linbox MCP token scoped to one LinkedIn `account_id`. Tags are per account;
  never pass an account id (see root skill for auth).
- To tag a persona: a resolvable `persona_id` or `profile_url`.
- To tag a thread: a resolvable `thread_id` or `linkedin_thread_ref`.
- To tag a post: a `post_url` or `activity_id` for a post already cached for this
  account (see [Posts](../posts/SKILL.md)).

## Tools

| Tool | Purpose |
|---|---|
| `linbox_list_tags(limit_tags=100)` | List account tags with `name`, `description`, `color`, `count` (sorted by count desc, then name). |
| `linbox_upsert_tag(name, description=None, color=None)` | Create a tag or update its metadata only; returns `created`. No entity attachment. |
| `linbox_add_persona_tags(tags, profile_url=None, persona_id=None)` | Attach tags to a persona (`entity_type='persona'`). Auto-creates missing tags. |
| `linbox_remove_persona_tags(tags, profile_url=None, persona_id=None)` | Detach tags from a persona. Idempotent. |
| `linbox_add_thread_tags(tags, thread_id=None, linkedin_thread_ref=None)` | Attach tags to a thread (`entity_type='thread'`). Auto-creates missing tags. |
| `linbox_remove_thread_tags(tags, thread_id=None, linkedin_thread_ref=None)` | Detach tags from a thread. Idempotent. |
| `linbox_add_post_tags(tags, post_url=None, activity_id=None)` | Attach tags to a cached post (`entity_type='activity'`). Auto-creates missing tags. |
| `linbox_remove_post_tags(tags, post_url=None, activity_id=None)` | Detach tags from a post. Idempotent. |
| `linbox_search_thread(..., tags=[...])` | Filter threads by tag (see semantics below). |
| `linbox_search_persona(..., tags=[...])` | Filter personas by tag (see semantics below). |
| `linbox_search_post(..., tags=[...])` | Filter cached posts by tag (see semantics below). |

### Normalization

Every tag name is normalized to `trim + lowercase`; empties are dropped and
duplicates deduped. `"  Lead "` and `"lead"` are the same tag. Uniqueness is
`(account_id, name)`.

### Add / remove responses

Add and remove tools return the same shape:

- `entity_type` (`persona` | `thread`), `entity_id`, plus `persona_id` or
  `thread_id`. The post tools instead identify the target with `post_url` +
  `activity_id`.
- `applied` — tags now attached (add mode).
- `removed` — tags actually detached (remove mode).
- `not_found` — requested names that were missing or not attached (remove mode).
- `remaining` — tags still attached after the mutation.

## Workflow

1. **Discover names first.** Call `linbox_list_tags` before filtering or tagging
   so you use real, normalized names instead of guessing. Unknown names silently
   match nothing in search.
2. **Choose the entity.** Tag a **persona** to label the person across all their
   conversations; tag a **thread** to label one specific conversation; tag a
   **post** to label a piece of content. Persona tags inherit into that person's
   thread search (see below); thread and post tags do not inherit anywhere.
3. **Attach / detach.** Use the add/remove tool for the entity, passing the
   resolved identifier. Missing tags are auto-created on add; on remove, unknown
   names land in `not_found` (no error).
4. **Filter.** Pass real names as `tags=[...]` to `linbox_search_thread`,
   `linbox_search_persona`, or `linbox_search_post`. Paginate via the cursor
   (root skill).

### Thread tags vs persona tags

An inbox / search thread item exposes two separate arrays:

- `tags` — tags attached directly to the **thread** (`entity_type='thread'`).
- `persona_tags` — tags attached to the thread's **persona**
  (`entity_type='persona'`), shown for context.

They are distinct sets. `linbox_add_thread_tags` only writes `tags`;
`linbox_add_persona_tags` only writes `persona_tags`.

Post tags (`entity_type='activity'`) are a third, separate set: they surface in
`linbox_search_post` results and filters only, never in thread or persona search.

### Search filtering semantics

- **OR inside a tag array.** `tags=["lead","hot"]` matches an entity tagged with
  `lead` OR `hot` (at least one). It is not an intersection.
- **AND across different filters.** Different filters on the same call combine
  with AND. `linbox_search_thread({"tags":["lead"], "reply_status": "unread"})` = tagged
  `lead` AND has an unread incoming message. Same for `name`, `message_contains`
  (threads), `name`, `linkedin_url`, `is_friend`, `campaign_id` (personas), and
  `content_contains`, `posted_since_days` (posts). So
  `linbox_search_persona({"tags":["lead"], "campaign_id":"<uuid>"})` = tagged `lead`
  AND enrolled in that campaign.
- **Persona → thread inheritance (thread search only).** A thread matches a
  requested tag if the tag is attached to the thread directly OR to the thread's
  persona. So `linbox_search_thread({"tags":["lead"]})` returns threads whose
  `tags` OR `persona_tags` include `lead`. `linbox_search_persona` matches on
  persona tags only (no inheritance).

### Unknown tag behavior

- **Search:** an unknown/misspelled name matches no rows — the tag filter returns
  an empty set rather than an error. Always verify names via `linbox_list_tags`.
- **Add:** a name that isn't in the dictionary is auto-created, then attached.
- **Remove:** a name that is unknown or not attached goes to `not_found`; the
  call still succeeds (idempotent, no error).

## Rules

- Always call `linbox_list_tags` before filtering by tags — unknown names return
  an empty result set, which is easy to mistake for "no matches".
- Names are `trim + lowercase`; treat tags case-insensitively.
- OR within `tags[]`; AND across distinct filters.
- Thread search includes inherited persona tags; persona and post search do not.
- Post tagging needs the post cached for the account — otherwise
  `Error: Post is not in cache for this account`; scrape it first (see
  [Posts](../posts/SKILL.md)).
- `linbox_upsert_tag` edits metadata only and never attaches to an entity; use
  `linbox_add_*_tags` to attach.
- `linbox_upsert_tag` leaves omitted `description` / `color` unchanged (no wipe).
- Remove is idempotent — check `not_found` / `removed`, not for an error.
- A validation error is the plain string `"Error: ..."`, not JSON.

## Examples

**Discover, then filter threads by tag**

```
linbox_list_tags()                       # confirm the real name is "lead"
linbox_search_thread({"tags": ["lead"], "reply_status": "unread"})
  # lead (thread OR persona) AND has an unread incoming message
  # take threads[].id; if page.has_more, repeat with cursor=page.next_cursor
```

**Tag a person, then find their threads**

```
linbox_add_persona_tags({"tags": ["lead", "hot"], "profile_url": "https://www.linkedin.com/in/johndoe/"})
  # -> applied: ["lead","hot"], remaining: ["lead","hot"]
linbox_search_thread({"tags": ["lead"]})  # matches via persona inheritance (persona_tags)
```

**Tag one conversation (thread-only, no inheritance)**

```
linbox_add_thread_tags({"tags": ["follow-up"], "thread_id": "<uuid>"})
  # sets thread `tags`; persona_tags unchanged
```

**Idempotent remove**

```
linbox_remove_persona_tags({"tags": ["hot", "typo-tag"], "persona_id": "<uuid>"})
  # -> removed: ["hot"], not_found: ["typo-tag"], remaining: ["lead"]  (no error)
```

**Upsert metadata only**

```
linbox_upsert_tag({"name": "lead", "description": "Qualified inbound lead", "color": "#22c55e"})
  # -> created=false; description/color updated; no entity attachment
```

## Related skills

- [Linbox](../SKILL.md) — root contract: auth, identifiers, cursor pagination,
  task polling.
- [Messaging](../messaging/SKILL.md) — inbox, threads, and the search tools that
  accept `tags` filters.
- [Connections](../connections/SKILL.md) — send/accept/withdraw invites.
- [Enrichment](../enrichment/SKILL.md) — profile/company scraping.
- [Posts](../posts/SKILL.md) — cached post search and the scrapes that make a post
  taggable.
