ContributingDraft
Teammates: default, and distinct in chat
Section 3 is settled (app-design 83434944); sections 1 and 2 are still building. See "What ships, and by whom" below for which team owns each piece.
Teammates: default, and distinct in chat
Builds on ADR 0031, which already has the tools (team.ask, team.list,
...), the inbox, notes, and worktree isolation built (through step 4, held on work/projects
landing). What is missing, and what this doc decides, is the user's 2026-09-28 ask: make
teammates the default, and make them look distinct in chat. Three pieces, in order of who builds
them.
1. Default: every project session gets team tools
Today (ADR 0031 section 4) a session only gets team.* tools when its project already has a
teammate. That is backwards for a default: a project with zero teammates is exactly the case
where @role (section 2) needs to work, so it can create one.
Decision: every Vyre SDK session started in a project (owned or shared, not a bare box
session with no project) gets the team.* tools and the policy append, whether or not the
project has any teammates yet. This is a sessions change, not a core/team one: sessions is
what decides a session's tool set and append per ADR 0030, and core/team must stay reachable
through ctx.call only (module boundary, test/boundaries.test.js). Concretely:
-
sessions's session-start path callsteam.list(or a cheaper does-this-project-have-any-teammates call, name TBD with sessions) for the project and includesteam.*in every project session's tool set regardless of the result. -
The append (ADR 0031 section 5's paragraph) is written by
core/teamtoday (core/team/index.js'spreamble/append path is for a teammate's own session; the append for an ordinary session asking about teammates does not exist yet and is new work here) and handed tosessionsto inject, same as the project brief and memory today. With no teammates yet, the line changes from "this project has teammates: ..." to:This project has no teammates yet. For an ongoing role (design, review, research, QA) prefer team_ask with a new role name, it creates one on first use, over a subagent. Use a subagent only for a one-off lookup or a burst that needs no memory.
-
The off switch. A per-project setting,
team.default(on by default, person-only,sessions.limits-shaped:{project, enabled}), turns this off. Off means: no append line, no auto-create on@role(section 2 still lists and asks existing teammates, it just cannot make new ones), and the Task tool is not de-emphasized. Existing teammates keep working either way, this switch is about steering new work, not removing what is already there. Surfaced in Project settings, alongside the project's other team settings. -
Ongoing vs one-off is a judgement call the append states but does not enforce (as today): design, review, research, QA and similarly-shaped recurring work read as "ongoing"; a single file read, a burst of screenshots, or a lookup that needs no memory of its own reads as "one-off". The Agents place's existing "subagents doing a teammate's job" signal (ADR 0031 section 5) is the backstop when the guidance is ignored.
Owner: sessions. This is the one piece that must land in core/sessions/switchboard
(session start, tool set, append plumbing) rather than core/team. core/team provides the two
append strings and the team.default setting; sessions wires them into every project session's
start.
2. @role in chat: route, and create on first use
Decision: typing @name at the start of a chat message (Chat, the Capsule, the phone) sends
that message as a team.ask to the teammate named name in the current project, instead of
spending the current session's own turn on it: this is already described in ADR 0031 section 5
("a person who types @design ... sends a request without spending the current session's turn").
What is new:
- If no teammate named
nameexists in the project (andteam.defaultis on), chat callsteam.addthere and then, with:role: name(lowercased,SLUG-checked, same charset asagentNametoday);- a generic template (no role-specific brief guessed from the name, guessing wrong is worse
than asking) with
brief: "Ask me about anything; I'll figure out the role from what you send me.",isolation: folder(neverworktreeby default: code isolation is a deliberate, person-made choice per ADR 0031 section 7, not a side effect of typing a word with an@in front of it),tools: files, web(no shell, no connectors, until a person's Setup tab grants more); - model: Sonnet, not the ADR's stated default of Opus. This is the one place this doc
changes ADR 0031 section 1's table: a teammate created by a person deliberately (
vyre team add, the Agents place) still defaults to theteammatepurpose (Opus); a teammate created by@roleon the fly defaults to Sonnet, because it exists on a guess and should not spend Opus turns proving out a role nobody has scoped yet. The person can change it in Setup at any time; once changed, later requests use the new model like any other teammate. - This is a real
team.add(PERSON_ONLYtoday): the chat message that typed@roleis the person's own input in their own surface, so this is the person creating it, exactly as typing it into the Agents place would be. No session or teammate can trigger this path; it only fires from a verified person surface (Chat, Capsule, phone), the same checkteam.addalready makes. - Chat shows a small inline confirmation before the request goes: "There's no
researchteammate yet, I'll create one and send it your message." with an inline "Don't create, just answer this here" fallback that routes the message as an ordinary chat turn instead. This is one click, not a form; the Setup tab is where the brief gets refined afterward.
- If
team.defaultis off and no teammate exists,@roleshows "There's noroleteammate in this project. Add one in Setup, or turn Teammates on for this project." and does not send. - Ambiguity:
@rolematches on the teammate'sroleslug only (not its brief, not fuzzy). A typo makes a new teammate rather than silently going nowhere: cheap to remove, and the chat-side confirmation is exactly where a typo gets caught ("There's nodesingteammate yet"). - Where this is built: the routing and the confirmation card are
chat's (this doc's UI agreement, section 3); the create-on-first-use call is a straightteam.add, no new tool needed.app-designandchatagree the confirmation card's look together (section 3).
3. Distinct in chat
The user's ask was "a teammate's turns show its role name and avatar with a distinct accent, a
handoff shows as a visible card, and teammates' own threads are one tap away." app-design ruled
out the accent-color part of that (below), and the lead confirmed the ruling stands; what
follows is the settled design (app-design, sha 83434944 on work/app-design,
docs/design/system/components/avatar.md and tool-row.md), not a proposal.
No per-teammate colour, anywhere in the Deck, the App or the Capsule. The product's colour
economy is closed: bone for action/focus/running/selection, violet for Needs you (teal the one
alternative), no other hue, and devices/hosts already don't get one
(docs/design/one-app/README.md's System section). A teammate is that same kind of entity, not
a person, so a role-hashed accent (disc, border, dot, ANSI square) was the first crack in a rule
that reads fine at 3 teammates and breaks at 8, and is the kind of thing nobody walks back once
it ships. The earlier draft of this section proposed exactly that hashed accent; it is turned
down.
Distinct instead means three things, none of them colour:
- Tile + name, always together. The teammate's avatar tile (the existing neutral agent tile,
avatar.md, unchanged: lowercase initial, no colour, same askitorjunotoday) is never shown bare. Wherever a teammate appears (its handoff row, its Agents place row, its thread header) the role name sits directly beside the tile in text, same weight as any agent name elsewhere. The name is what tells teammates apart; the tile carries no more meaning than any other agent's. - A "Teammate" tag. A plain
Tag(chip.md:--hoverfill, no border, 12/16--text-2) sits after the role name in exactly those three places, once per surface, so a thread header shows it once at the top, not again on every turn. This is what says "design, a persistent project teammate" rather than a one-off subagent or the assistant, in words, since nothing here is said in colour. - The handoff row. Not a new component: a
Handoffvariant of the existing tool-row (tool-row.md). It uses the teammate's own avatar tile (not the generic subagent icon), and its summary reads "Asked design [Teammate] to make the intake form calmer", verb flips to "Replied" once the result lands. It folds and unfolds like any tool row (default: folded, showing the summary only), but it is exempt fromtool-row.md's "folded run" collapse that bundles quiet tool calls into one summary line, the same exemption a plan or a todo list gets: a handoff is always its own line, and "collapsed" only ever means the reply detail is shut, never that the row itself is missing. The expanded detail is the teammate's prose reply, rendered as turn prose (turn.md), not a code block on--code-bg, since it is written language, not a tool's output. This also settles the earlier open question about a distinct message bubble for a teammate's result: there is no freestanding bubble. A teammate's reply never becomes an ordinary assistant-style message; it lives only inside the handoff row's expanded detail, so there is nothing to mistake for the session's own words and nothing extra to style.- If the result arrives after the person has moved to a different chat (their session slept or
they navigated away), the same row appears retroactively in that session's history next time
it is opened, plus the existing notification/Needs-you-adjacent surfacing ADR 0031 section 4
already specifies for person-originated requests. No new delivery mechanism: this is a
rendering rule over the same
threads_inboxitem.
- If the result arrives after the person has moved to a different chat (their session slept or
they navigated away), the same row appears retroactively in that session's history next time
it is opened, plus the existing notification/Needs-you-adjacent surfacing ADR 0031 section 4
already specifies for person-originated requests. No new delivery mechanism: this is a
rendering rule over the same
- One tap away. Every handoff row, and every row for a teammate anywhere (Agents place,
@rolein the composer, the confirmation card in section 2), opens that teammate's own thread in one tap (list-row.md's existing "row pushes a screen" pattern): its Now/Inbox/Results/Notes/Setup pane (ADR 0031 section 9), scrolled to the request in question when opened from a row. This reuses the Agents place's existing detail pane; chat does not build a second teammate viewer. - The one colour exception: the CLI.
vyre teamandvyre team ask <role>may colour a teammate's name with a role-hashed ANSI 256 colour, but only from a small fixed set (about 8, pre-picked and AA-tested), never an arbitrary hash-to-hue, so a teammate's colour can never land near bone or violet and misread as a status signal. Text-only (never a fill), degrades underNO_COLOR, the same convention terminal tools likegit log --graphalready use, and it never touches the Deck/App/Capsule's colour economy. The palette values land with whoever builds the CLI side; the rule (fixed set, name-only, never a fill) is settled now.
What ships, and by whom
- sessions: the default-on team-tools-and-append wiring (section 1) into session start; the
team.defaultsetting; the cheap "does this project have any teammates" checkcore/teamexposes for it. This is the one piece that must land in sessions/switchboard rather than core/team, per the module boundary. - teammates (
core/team): the two append strings (with/without existing teammates); the@role-created-teammate defaults (Sonnet,folder, minimal tools) as a documentedteam.addcall shape, not a new tool; nothing here needs a schema change toagents_teammates, a Sonnet-purposed,folder-isolated teammate is just a normal row. - chat: the
@rolecomposer routing and create-on-first-use confirmation (section 2); the handoff row, the tile+name+tag treatment, and the one-tap-to-thread links (section 3), against app-design's settled spec. - app-design: done for section 3:
docs/design/system/components/avatar.md(unchanged neutral tile) andtool-row.md(the Handoff variant, the "never folds" exemption), sha 83434944 on work/app-design. The CLI's fixed ANSI palette values are the one open item, for whoever builds the CLI side.
Sequencing: section 1 (sessions) can start immediately, it does not depend on section 2 or 3.
Section 2 (@role create-on-first-use) only needs section 1's team.default setting to exist;
chat can build the routing logic against the existing team.ask/team.add tools today and wire
the setting check in once sessions ships it. Section 3 is a design pass (chat + app-design) that
can run in parallel with 1 and 2, landing once both agree.
Non-goals here
- Not touched: worktree isolation, the integrator, merging (ADR 0031 section 8). An
@roleteammate defaults tofolder, neverworktree, so none of that machinery engages until a person deliberately upgrades it in Setup. - Not touched: sharing across projects (section 12) or the assistant-wide assignment.
@roleonly ever creates a teammate scoped to the current project. - Not a new tool: no
team.autoAddor similar.@role's create-on-first-use ischatcalling the existingteam.addwith a specific default shape; keeping it a plainteam.addcall means Setup,vyre team, and the Agents place all see and can edit the result the same way they see any other teammate.