docs GitHub

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 calls team.list (or a cheaper does-this-project-have-any-teammates call, name TBD with sessions) for the project and includes team.* in every project session's tool set regardless of the result.

  • The append (ADR 0031 section 5's paragraph) is written by core/team today (core/team/index.js's preamble/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 to sessions to 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 name exists in the project (and team.default is on), chat calls team.add there and then, with:
    • role: name (lowercased, SLUG-checked, same charset as agentName today);
    • 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 (never worktree by 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 the teammate purpose (Opus); a teammate created by @role on 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_ONLY today): the chat message that typed @role is 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 check team.add already makes.
    • Chat shows a small inline confirmation before the request goes: "There's no research teammate 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.default is off and no teammate exists, @role shows "There's no role teammate in this project. Add one in Setup, or turn Teammates on for this project." and does not send.
  • Ambiguity: @role matches on the teammate's role slug 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 no desing teammate 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 straight team.add, no new tool needed. app-design and chat agree 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 as kit or juno today) 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: --hover fill, 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 Handoff variant 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 from tool-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_inbox item.
  • One tap away. Every handoff row, and every row for a teammate anywhere (Agents place, @role in 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 team and vyre 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 under NO_COLOR, the same convention terminal tools like git log --graph already 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

  1. sessions: the default-on team-tools-and-append wiring (section 1) into session start; the team.default setting; the cheap "does this project have any teammates" check core/team exposes for it. This is the one piece that must land in sessions/switchboard rather than core/team, per the module boundary.
  2. teammates (core/team): the two append strings (with/without existing teammates); the @role-created-teammate defaults (Sonnet, folder, minimal tools) as a documented team.add call shape, not a new tool; nothing here needs a schema change to agents_teammates, a Sonnet-purposed, folder-isolated teammate is just a normal row.
  3. chat: the @role composer 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.
  4. app-design: done for section 3: docs/design/system/components/avatar.md (unchanged neutral tile) and tool-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 @role teammate defaults to folder, never worktree, 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. @role only ever creates a teammate scoped to the current project.
  • Not a new tool: no team.autoAdd or similar. @role's create-on-first-use is chat calling the existing team.add with a specific default shape; keeping it a plain team.add call means Setup, vyre team, and the Agents place all see and can edit the result the same way they see any other teammate.