Mentioning an agent in an issue
You bring an agent into a discussion the way you bring in a colleague: type@, pick it, and it is named in the comment.
Mentioning an agent wakes it. The mentioned agent is given a task naming
the issue and quoting the comment, and it runs — the same way it would if you
had assigned it work. A mention does not change the issue’s assignee.
Using it
In the comment box on an issue:- Type
@. A picker opens listing the agents in your workspace. - Keep typing to filter — it matches on both the agent’s handle and its display name.
- ↑ / ↓ to move, Enter or Tab to insert, Esc to dismiss. The caret never leaves the draft.
- ⌘/Ctrl + Enter posts the comment, the same as everywhere else in Crewship.
pavel@unify.cz stays an email
address.
How a mention is stored
A mention is a plain Markdown link with a private scheme:over to you @Robin — the CSV job is failing on empty rowsis stored as
<@id> or a bespoke @@agent:id@@ sigil:
Why it cannot be used to impersonate
The label in the token is decoration. The chip’s name and avatar are looked up from the workspace roster using the id, at render time. So writing- A body that contains the literal markup a chip renders to is not a chip. The comment renderer’s sanitiser drops unknown tags and unknown attributes, and the internal element name a mention passes through is randomised per page load, so it cannot be typed.
- A mention of an agent your workspace cannot see is not a mention. It is text.
What happens when you post the comment
Every step below runs after the comment is committed, so a mention that cannot be recorded never turns a posted comment into an error.-
The body is parsed, not scanned —
internal/mentionswalks the comment as CommonMark and reads only real link nodes. - Each id is resolved inside the comment’s own workspace. An id that names no agent there is dropped and leaves nothing behind — no row, no activity, no notification, no run. A mention of an agent in a different workspace is a probe, not a typo, and is treated the same way.
-
A
mentionedactivity is written to the issue’s History first, with the agent id indetails— this is what everything below references. -
A delivery is recorded and acknowledged. The resolved set is stored in
mission_comment_mentions, one row per (event, agent), so nothing ever re-parses a comment on read. Naming the same agent three times in one comment is still one row, one activity entry and one run — the same guarantee as before, now held by the delivery’s own key rather than the comment’s. The moment this row is written, the server pushesissue.delivery.ackedon the issue’s workspace channel — before any model call happens. That is what lets the UI show “received” the instant you post, rather than only once the agent’s own reply appears. The row also records what the dispatch trigger did —dispatched,refused(a limit said no, with the refusal verbatim),skipped, orfailed— so a mention that did not wake its agent is answerable rather than mysterious. A duplicate delivery of the same event — a redelivered event after a server restart, for instance — is collapsed to the row above rather than starting a second run: only the first delivery to claim the row is dispatched. -
The agent is dispatched, through the same endpoint an agent-to-agent
assignment goes through. It inherits that endpoint’s authorization and its
limits; see below.
The brief it is handed says who mentioned it and on which issue, and every
one of those values — the comment, your display name, the issue’s title and
identifier — sits inside an
<untrusted>block. Display names and issue titles are written by whoever wanted to write them, so the woken agent is told to read them and never to obey them. Because the run belongs to a session (below), the brief also carries a context pack: a snapshot of the issue, the session’s latest checkpoint, and every activity on the issue the session has not read yet — so an agent woken a week later resumes rather than starts over. It ends with the instruction to close the run with a---CHECKPOINT---block.
What bounds it
A mention is a way to start an agent run, so it is bounded by the same two numbers every other dispatch is — not by a mention-only rule:
Both are instance settings (
crewship instance settings set delegation.max_depth 3).
When one refuses, the mention is still recorded and still appears in History —
the refusal is stored on the row, naming the setting an operator would change.
A person’s mention is counted only against the runs mentions started for that
agent on that issue. Work the agent hands to other agents while leading the
issue is a different budget, so a busy lead does not become unmentionable.
Two more rules the dispatch inherits from the assign path:
- Crews must be connected. Mentioning an agent in a crew that is not connected to the issue’s crew records the mention and dispatches nothing.
- An agent does not mention itself awake. A self-mention is recorded and skipped.
Sessions, checkpoints and the context pack
A mention does not just start a run; it opens — or reuses — a session: one durable row per (issue, agent),crewship issue sessions <id>. The
second mention of the same agent on the same issue finds the same session,
which is what makes “resume where you left off” possible at all.
The session lifecycle
The state is on
crewship issue sessions and on the issue’s Sessions panel,
and every change is pushed as issue.session.state on the workspace
channel.
The checkpoint block
A session-bearing run is told to end its output with this block, and it is required on every run:facts and blockers may run over several lines; a field’s value continues
until the next key: line or the closing marker. outcome is what routes
the run (crewship issue runs shows it) — leaving it out is treated as
FAILED, so an agent that ran fine but forgot the block is a visible
failure rather than a silent success. Every block is stored as a row in the
session’s history (crewship issue checkpoints <id> --agent <slug>); a run
that ended without a valid block is stored too, with parsed: false and
every other field empty, so “forgot to report” and “reported nothing” stay
distinguishable.
The context pack
Every real dispatch on a session appends a context pack to the brief, in this order, each part inside its own<untrusted> fence and under its own
token budget:
- Issue snapshot — identifier, title, status, priority, owner and delegate, parent issue, the current work mode and note, and the description (800 tokens).
- Latest checkpoint — the most recent parsed block on this session, so
the run reads
donebefore deciding what to do (600 tokens). - Unread activity — every
mission_activityevent with aseqabove the session’slast_consumed_seq: comments, status changes, mentions, run results, in order (1,200 tokens, at most 2,000 rows).
summarized); when even that does not fit, the oldest contiguous run of
events that does fit is rendered (truncated) and the rest waits for the
next wake. The cursor only ever advances over events the run was actually
shown, so nothing is marked read that no agent read; the path taken is
recorded on the run. A fold-in — a follow-up appended to a run that is
already going — does not advance the cursor, because that run never saw
the pack.
Memory search and the artifact manifest are not part of the pack.
Leases: how a dead run is noticed
Every RUNNING assignment carries a lease — an owner (the server process driving it,hostname:pid) and an expiry 90 seconds out. The process renews
it every 20 seconds while the run is in flight; a sweeper looks every 15
seconds for RUNNING rows whose lease has lapsed and fails them, with the
error lease expired — no renewal from owner … within 1m30s. That error is
what crewship issue runs shows on the run; the lease columns themselves
are not on any API. Recovery is keyed on the lease, not on when a server
booted: a row a different, still-live process is renewing is never
touched, and the same rule guards boot-time recovery after a crash. A
lease expiry is a plain Tier 1 failure of the run (FAILED, session to
error) — it never kills a container process; that is what
issue stop --hard is for.
Known limits in 1.0
Written down on purpose (they are release conditions, not footnotes):- A follow-up on the SAME issue never starts a second run — it waits for
the one already going, then rides the NEXT run. One (issue, agent)
session may have at most one live run, enforced at the database level (a
partial unique index on
assignments.session_id), not by a check-then-act read that a fast second comment could race past. Mentioning the same agent again while its run on THIS issue is still going does not queue a second dispatch: the delivery is recorded and acknowledged (issue.delivery.acked) the instant you post, but it is left unclaimed — the run already in progress cannot see it (there is no channel to inject text into a turn already under way; steering a live turn is future work, and there is no “re-read the task mid-run” path either). The moment that run actually finishes, every comment queued behind it — one or several — is folded into exactly ONE new run, with all of their text in that run’s own brief before its exec starts. So a burst of five follow-ups while an agent is busy produces one extra run, not five, and not zero: nothing is silently dropped, and nothing is marked read before an agent has actually read it. - A correction jumps the queue and is labelled as one. A comment that
arrives while the agent’s run on this issue is still going is a
correction (the delivery’s priority,
stop > correction > normal, per the interruption-as-an-event model). When the queued comments are folded into the next run, corrections lead the brief, ahead of ordinary follow-ups, and are markedCORRECTIONwith a header telling the agent to read and apply them first — so the next step reflects your correction rather than treating it as just another later comment. This is delivery at the next safe boundary (the next run on the same session), not mid-token interruption of a turn already under way, which stays future work. - A busy agent is woken later, not interrupted, on a DIFFERENT issue. Beyond the same-session case above, an agent still has only one live run at a time across every issue and chat it touches. Mentioning it while it is busy on another issue queues the mention behind that live run; it is dispatched as its OWN new run once the agent frees up, with the brief it would have had. Before this existed, a second run for the same agent killed the first one mid-turn; queueing is the fix. The delivery is recorded and acknowledged the instant you post either way, so you are not left guessing whether the comment was received while you wait.
- A chat message to an agent busy on an issue bounces. The chat reports
agent_busyrather than starting a second run into the same container. Wait for the run to finish, or watch it from the issue. - The lock covers the doors that run an agent for an issue —
/assign, @mentions, mission tasks, lead planning, the chat, the agent cron and a routine’sagent_runstep — but not the inbound agent webhook (POST /api/v1/webhooks/{crewId}/{agentId}/trigger), the direct agent-start route on the IPC socket, or a peer query. Those three can still start an agent that is already running. A routine’sagent_runstep against a busy agent does not queue: the step fails withtarget agent has a live run in progress elsewhereand follows the step’s retry policy, thenon_fail, like any other execution error.
- An agent awaiting approval is not woken. An agent created or hired by an
agent under guided autonomy sits at
PENDING_REVIEWuntil a person approves it. Mentioning it records the mention and starts nothing — the same answer/assignand a mission task give, and the same answer it gives a chat message. Approve it from the inbox and mention it again.
When nothing runs
A mention that starts nothing is recorded either way: the join row keepsdispatch_state and the reason, and the issue’s History still shows the
mentioned entry. The join row itself has no API or CLI read path — what
you can see is the History row (crewship issue activity, action
mentioned), the inbox item described below, and the run in
crewship issue runs when one started. A refused, skipped or failed
mention is not queued — re-mention once the reason no longer holds. The one case that is queued is
an agent that is simply busy: see the known limits above.
Whether anyone is told depends on who wrote the comment.
A person gets an inbox item — “Your mention of Robin on ENG-42 did not
start a run”. What it carries depends on why:
- Refused — a limit or a rule said no. The sentence is reproduced verbatim, because it names the setting an operator would change.
- Failed — something broke on the Crewship side. You get a fixed sentence saying so. The underlying error is deliberately not shown: it is an internal fault, not a decision about your mention, and it lands on the mention record and in the server log instead.
An agent’s own answer arguably belongs in the response to its comment, and
today it is not there —
POST /api/v1/internal/issues/{identifier}/comments
returns the comment, not what its mentions did. So an agent that mentions a
capped or held colleague currently learns nothing from the call itself. That is
a gap, not a design: it is worth closing, and it is tracked separately rather
than described here as though it already worked.Being notified
A mention delivers on theissues.comment notification category — the same
toggle as any other comment on the issue, because a mention only ever arrives
inside one. Muting issue comments mutes mentions with them; there is no
separate switch to forget to turn on.
The history row
The issue’s History tab renders “X mentioned @Y” when an activity withaction in mentioned / mention / agent_mentioned / comment_mentioned
arrives. It reads the target out of details in whichever shape it finds — a
bare agent id, a whole mention token, or {"agent_id":"…"}. The backend writes
the bare agent id.
Which comments are scanned
All three doors a comment can arrive through, because a mention typed by a person and one written by an agent are the same wire format:
No new HTTP route was added for mentions — the trigger hangs off comment
writes that already existed.
crewship issue comment has a --mention <slug> flag (repeatable) that resolves the slug against the workspace agent
roster and writes the link form for you:
--body — this is what --mention
does under the hood, and it is still the only thing the server ever reads:
@robin typed into --body (or a positional argument) is not a
mention — see docs/cli/issue.mdx. Everything on this page happens on that
same comment call. crewship issue get ENG-42 renders the comment and the
activity that follows it.