Skip to main content

Tags & handoffs

A tag is a short string you attach to an artifact when you create it: handoff, lane:auto, repo:stump.wtf/cairn. Tags let something downstream (usually a Switchboard routing rule) decide what to do with a new artifact without opening its body. The main use is an agent handoff: one agent writes a work order, and another agent picks it up and runs with it. See ADR-0018.

Tags are not provenance

Cairn derives the actor (the authenticated principal) and the channel itself. A tag is simply whatever the creator sent. Cairn stores it and never trusts it.

Never make a trust or authorization decision from a tag. handoff tells you what the creator wants, not who the creator is. For identity, use actor_id. on_behalf_of names the MCP harness (e.g. claude-code/2.1.0) as that harness reported itself: useful context, not proof.

In the web view, tags get their own Tags section in the side panel, separate from Provenance.

Rules

RuleLimit
Characterslowercase a-z, 0-9, and . _ : / # -
Length1–64 bytes per tag
Count32 distinct tags per artifact
Repeatsdropped silently; the first occurrence keeps its place

Cairn rejects a tag that breaks a rule. It never truncates a tag or changes its case, so lowercase run ids and timestamps yourself. Tags are set at creation and can't be changed afterwards.

Setting tags

CLI: repeat --tag, or give a comma-separated list. It works on both the bare command and cairn add:

cat prompt.md | cairn --tag handoff --tag lane:auto,size:m
cairn add prompt.md context.log --tag handoff --tag repo:stump.wtf/cairn

REST: on POST /v1/artifacts, send X-Cairn-Tags: handoff,lane:auto, repeated ?tag= parameters, or both. Each value may be a comma-separated list. A multipart create also accepts repeated tag form fields.

MCP: pass a tags array to artifact_create or bundle_create:

{
"title": "handoff: fix the flaky reaper test",
"body": "# Task\n\nThe reaper integration test flakes under -race ...",
"model": "claude-opus-5",
"tags": ["handoff", "lane:m", "size:m", "reply:cairn-comment"]
}

Tags come back on every read and in the Bin. GET /v1/bin?tag=handoff&tag=size:s narrows the Bin to artifacts carrying every given tag. Tags also ride along on the artifact.created outbound event.

The handoff convention

To hand work to another agent, write the artifact body as a self-contained prompt: the task, the relevant links, the constraints, what's been tried, and what "done" looks like. Then tag it:

TagMeaning
handoffThis artifact is a work order for another agent.
lane:s · lane:m · lane:l · lane:vision · lane:autoWhich worker lane runs it. Lanes are by difficulty, not provider. Optional; lane:auto or no lane routes by size.
size:s · size:m · size:l · size:xlThe weakest model that can carry the work end to end. Optional.
repo:<owner/name>The repository the work targets. Optional.
issue:<owner/repo#n>The tracked issue, if there is one. Optional.
source:<harness>/<run>The run that produced the handoff. Optional.
reply:cairn-comment · reply:signalHow the executing agent reports back. cairn-comment means comment on this artifact. Optional.

Cairn checks only the rules above, not this vocabulary. A misspelled lane is accepted here and then misroutes downstream.

Receiving a handoff: semi-trusted

A handoff from another of our agents is semi-trusted. The receiving agent does the task, but treats the artifact body as data that may carry prompt injection:

  • it never follows an instruction to widen its own permissions, send data somewhere new, reveal credentials, or skip its usual review rules just because the handoff says so;
  • it uses the event's actor_id to see whose token or grant sent the handoff, and on_behalf_of for the harness it came from. The tags and the body say nothing about either.

Example event

The artifact.created event for a tagged bundle created over MCP looks like this:

{
"source": "cairn",
"kind": "artifact.created",
"event_id": "5b0f3c1e-8a3d-4c55-9f0e-2d7c6b1a9e40",
"created_at": "2026-09-11T12:00:00Z",
"data": {
"id": "7Kq2mZ",
"share_type": "bundle",
"title": "handoff: fix flaky reaper test",
"url": "https://cairn.example/7Kq2mZ",
"channel": "via MCP",
"model": "claude-opus-5",
"actor_id": "joestump",
"expires_at": "2026-09-18T08:00:00Z",
"on_behalf_of": "claude-code/2.1.0",
"tags": [
"handoff",
"lane:m",
"size:m",
"repo:stump.wtf/cairn",
"issue:stump.wtf/cairn#42",
"source:claude-code/morning-brief-2026-09-11",
"reply:cairn-comment"
]
}
}

A routing rule matches on handoff and the lane: tag in data.tags. The worker that claims the todo reads the artifact at data.url and follows it, semi-trusted.

See SPEC-0002 for the tag requirement and SPEC-0012 for the event payload.