Skip to Content
Platform

Tags

A tag is an opaque label on a task, a file, or a member. Tags group work so you can filter it, and a tag share opens one resource to every member who holds a tag.

Ownership in Introspection is per-member: a file owned by one end user is invisible to every other. That is right for personal data and wrong for shared working material, such as a team’s documents, a customer-wide knowledge base, or a memory file several agents maintain together.

A share grants one file, issue, or conversation to a grantee. When the grantee is a tag, the share admits every member who holds that tag, so the cohort can change without touching the share. Adding someone to the cohort is one tag on their member, and removing them is taking it away.

No tag key is reserved, none is interpreted, and nothing is derived from a tag’s text. A tag means exactly what your organization decides it means, and two tags are related only by being equal.

Sharing with a cohort

Sharing a resource with a cohort takes two steps:

  1. An organization owner gives each member of the cohort the same tag, such as team:acme. See Writing tags.
  2. The resource’s owner creates a share that names that tag as its grantee.
introspection shares create --resource-type file --resource-id "$FILE_ID" \ --tag team:acme

Every member whose tags include team:acme now sees the file in their ordinary file list. A share admits; the caller’s scopes decide: a member whose token holds files:write can update the file, one holding files:delete can delete it, and one with only files:read can only read it. No share lets a grantee change the file’s tags or manage its shares.

Three rules keep a tag share from reaching further than you intend:

  • You must hold the tag. Creating a tag share requires owning the resource and holding the tag yourself, unless you’re an admin. You can’t see which members hold a tag, so this rule stops a typo or a guessed tag from opening a resource to a cohort you have never seen.
  • A member and a tag narrow each other. A share that names both a member and a tag admits only that member, and only while they hold the tag.
  • One live share per tag. Creating a second live share for the same resource and tag returns 409. What a share admits never changes in place; revoke it and create another.

A tag share names one resource. Tagging another file with team:acme doesn’t share it, so each resource the cohort should reach needs its own share.

Implicit tag access is deprecated

Today, a member whose tags intersect a task’s or a file’s tags can still read and write that row without any share. This implicit tag access is deprecated and will be removed. Use a tag share for anything a cohort needs to reach, and treat tags on tasks and files as labels for filtering and grouping.

Until implicit access is removed, a tag used for filtering also grants access when the same string appears on a member. If you tag files env:prod to filter them and also tag a member env:prod, that member can read and write those files. Keep the two vocabularies apart, and audit with the reads in Who can reach what.

A member admitted by implicit tag access can update a row’s content, title, metadata, and lifecycle state, and can perform the ordinary recoverable delete. They can’t change the row’s tags, because a patch that supplies tags matches no row unless they own it.

Who writes which tags

Which side you’re writing decides how privileged the write is:

You are writingWho may write itWhat it does
A task or file’s tagsThe row’s owner, or a privileged callerLabels the row for filtering and grouping
A member’s tagsAn organization owner (members:manage)Makes the member eligible for every tag share naming that tag

Only an organization owner can put a tag on a member, and a tag share admits only members who hold its tag. Together with the rule that a share’s creator must hold the tag, a tag share can reach only a cohort that an organization owner already defined.

Writing tags

WhereHowAvailable in
Task, at creationtags on task createSDKs, CLI (--tag), REST
Task, latertags on task updateSDKs, REST
File, at creationtags on file upload or createREST
File, latertags on file updateSDKs, REST
Membertags on member create or update, organization owner onlyDashboard, REST
A member an identity assertion is about to mintidentity.tags when opening a runnerSDKs, CLI (--identity-tag), REST

Tagging a member is the one write with no SDK surface: it is an organization-owner operation, done from the dashboard or with a REST call. That asymmetry is deliberate, because a member’s tags decide which tag shares admit them. See Who writes which tags.

The CLI sets tags when it creates a task, but does not filter a list by one; reach for an SDK or REST for that.

A tag list replaces wholesale. Omit tags to leave the existing list untouched; pass [] to clear it. This is unlike a metadata patch, which merges — so an explicit empty list has to reach the wire, and a client that drops falsy values from a request body will silently fail to clear a tag.

introspection tasks create --runtime support-agent --environment production \ --prompt "Draft the Q3 summary." --tag customer:acme --tag team:research

Tags on a file follow the file, not the version

A file’s versions are separate rows and a list returns the head, so a new version inherits both the tags and the owner of the version before it. Without that, a tag filter would silently stop matching at v2 because the new head was never tagged. A share follows the file the same way: it covers every version, so a grantee keeps access after someone adds a new one. Writing a new version records who wrote it separately from who owns it, so a writer admitted by a share never quietly takes ownership of a file.

Tags sent with POST /v1/files apply only when that request creates the file. Adding a version keeps the file’s tags; sending a different set returns 409, so change them with PATCH /v1/files/{id}.

What a tag may contain

A tag is an opaque string. key:value is the convention every example here uses, and the one to follow — but it is only a convention: a bare acme is an equally valid tag, because set membership does not care.

RuleValue
Maximum length128 characters
Maximum tags per resource64
Must beNon-empty, free of whitespace and control characters
MatchingExact and case-sensitive — Acme and acme are two different tags
DuplicatesCollapsed; the remaining order is preserved

Invalid tags are rejected, not trimmed. Silently turning "acme " into "acme" would store a tag you never sent, which could make you eligible for a tag share you never asked for. Nothing is normalized or lower-cased for the same reason: normalizing would be one more thing reading a tag’s text.

Filtering

tag is an optional filter on the task, file, and member list endpoints:

for await (const task of runner.tasks.list({ tag: "customer:acme" })) { console.log(task.id, task.tags); } for await (const file of runner.files.list({ tag: "customer:acme" })) { console.log(file.name, file.tags); }

The filter only narrows. It is combined with the access rule rather than replacing it, so naming a tag you do not carry returns nothing rather than granting you anything.

Who can reach what

These reads answer the audit question for a tag:

  • GET /v1/members?tag=team:acme lists the members who hold the tag.
  • GET /v1/shares?granted_tag=team:acme lists the shares that admit them.
  • GET /v1/tasks?tag=team:acme and GET /v1/files?tag=team:acme list the rows carrying the tag, which implicit tag access still opens to those members.

Nothing joins them, and there is no group registry to enumerate. Members live in the Control Plane while shares, tasks, and files live in the Data Plane, which is also why the caller’s tags travel in the session token rather than being looked up.

Removing a member’s tag is eventual. Because the caller’s tags ride the session token, removing a member’s tag takes effect at the next token mint, the same as changing a member’s role. Revoking a share with DELETE /v1/shares/{id} takes effect on the next request.

Tagging a customer as they arrive

When your backend opens a runner and asserts an end-user identity, identity.tags stamps the customer member that assertion mints. That puts your end user into a cohort at the moment they first appear, instead of asking an organization owner to tag them afterwards, so tag shares for that cohort admit them from their first session.

introspection tasks create --runtime support-agent --environment production \ --subject user_123 --identity-tag customer:acme --prompt "Draft the Q3 summary."

--identity-tag requires --subject: tags stamp the member an identity mints, so with no identity asserted there is no member to stamp.

Two bounds make this safe, and both are enforced by the server:

  • Attenuated. The asserted list is narrowed to the tags the asserting agent member already holds; anything else is dropped silently. An assertion can only hand out what an organization owner already granted, so a tag still originates in exactly one place. Excess tags are dropped rather than raising, since the list is a hint about a customer and failing a whole session over one ungrantable tag would turn a typo into an outage.
  • Applied on create only. Tags are stamped when the member row is first inserted and never on a later assertion. A returning customer cannot be re-tagged, stripped, or have an owner’s grant clobbered by a repeat call. Changing an existing member’s tags stays an owner operation.

This grants nothing until an organization owner deliberately tags the agent member: an agent member with no tags of its own can assert none.

Choosing between tags and shares

NeedUse
Let one named person read one file, issue, or conversationA share naming their member id
Let one named person edit a file or issueA write share naming their member id
Let a changing group reach a resourceA tag on each member of the group, and a share naming that tag on the resource
Group work for filtering and reportingA tag on the tasks and files
Let someone reproduce work from a conversationA share, then a fork
Last updated on