| 1 | # Channels |
| 2 | |
| 3 | People and agents talk in the same place the code lives. A workspace gets |
| 4 | channels and direct messages, the way a team chat app has them, and every |
| 5 | channel can be linked to a project so its pull requests, issues, checks and |
| 6 | deploys arrive there as cards a person can act on. Agents are members of |
| 7 | channels like anyone else. |
| 8 | |
| 9 | Design canvas: the "g1t Channels" artifact (v2 row is the target). |
| 10 | |
| 11 | ## The point |
| 12 | |
| 13 | Today a person reads g1t in two places: g1t itself, and a chat app where |
| 14 | the team talks about what g1t is doing. Links get pasted across, approvals |
| 15 | wait in the wrong window, and an agent's question goes unseen. Channels put |
| 16 | the talk next to the work, and make one thing true everywhere: a thread |
| 17 | about a pull request *is* that pull request's conversation. |
| 18 | |
| 19 | ## Product model |
| 20 | |
| 21 | - **Modes.** The shell gets a rail: Home, Code, Chat, Agents, Inbox. Code |
| 22 | keeps today's sidebar exactly. Chat has a sidebar of its own. Inbox is |
| 23 | shared by both. No mode's sidebar ever lists the other mode's things. |
| 24 | - **Channel.** Belongs to a workspace. Public or private. Optionally linked |
| 25 | to one or more projects, and optionally to a team. Has members: people |
| 26 | and agents. |
| 27 | - **Message.** Text by a person or an agent, or a card posted by g1t for an |
| 28 | event (pull request opened, checks failed, deployed, an agent asking for |
| 29 | approval). |
| 30 | - **Thread.** Replies under a message. A card about an issue or pull |
| 31 | request opens that issue's or pull request's own timeline: replying there |
| 32 | is commenting on it. Other threads belong to the channel. |
| 33 | - **Direct message.** A channel with no name, between two or more members, |
| 34 | any of whom may be agents. |
| 35 | - **Project chat.** In Code mode, a project page shows a dock with only the |
| 36 | channels linked to that project. |
| 37 | |
| 38 | ## Scaling the sidebar |
| 39 | |
| 40 | Workspaces will have hundreds of channels. The Chat sidebar never lists |
| 41 | them all: |
| 42 | |
| 43 | - **Starred** first, chosen by the person. |
| 44 | - **Active projects:** channels linked to projects the person worked on |
| 45 | this week, computed, not chosen. |
| 46 | - **Teams:** one collapsed section per team the person is on, showing a |
| 47 | count and what is unread. |
| 48 | - **Direct messages**, newest first. |
| 49 | - **All / Unread / Mentions** filter, and a jump box. Everything else is |
| 50 | behind *Browse channels* and *Muted*. |
| 51 | |
| 52 | ## How it fits what exists |
| 53 | |
| 54 | | Need | Already in g1t | What changes | |
| 55 | | --- | --- | --- | |
| 56 | | Live delivery | Durable Objects (runner, models) | One Durable Object per channel holds the open WebSockets, with hibernation. D1 keeps the messages. | |
| 57 | | Cards for events | `services/events` bus and per-subscriber queues (`g1t_contracts::subscribers`) | `chat` subscribes to pull, issue, check run, deployment and agent run types, and posts a card in each linked channel. | |
| 58 | | Mentions and unread | `services/events` inbox, threads and reasons | A message that mentions someone publishes `chat.message.created`; the inbox tells them with reason `mention`. Thread keys reuse `<repo_id>#<number>`. | |
| 59 | | Steering an agent | `agent_messages` in `services/work` | `@agent` in a pull request's thread becomes a steering message on that pull request. | |
| 60 | | One timeline | Issue and pull request timelines in `services/work` | The card's thread reads and writes that timeline. Chat stores only a pointer. | |
| 61 | | Navigation | `workspace-nav.ts`, `shell.tsx` | A rail, a `mode` beside `SidebarKey`, and a Chat sidebar. | |
| 62 | | Search | `services/search` | Index channel names and messages people can see. | |
| 63 | |
| 64 | ## Data |
| 65 | |
| 66 | A new service, `services/chat` (TypeScript, like `services/projects`, |
| 67 | because the Durable Object and WebSocket code is simplest there), with its |
| 68 | own D1 database: |
| 69 | |
| 70 | - `channels`: id, workspace, name, kind (`channel` | `dm`), private, topic, |
| 71 | team_id, created_by, archived_at. |
| 72 | - `channel_links`: channel_id, project_id. What makes a channel a |
| 73 | project's chat, and where cards go. |
| 74 | - `channel_members`: channel_id, principal (person or agent), role, |
| 75 | starred, section, muted, last_read_id, joined_at. |
| 76 | - `messages`: id (time-sortable), channel_id, author, kind (`text` | |
| 77 | `card`), body, card (JSON: subject key, state, actions), thread_root, |
| 78 | reply_count, edited_at, deleted_at. |
| 79 | - `reactions`: message_id, principal, emoji. |
| 80 | |
| 81 | Unread counts come from `last_read_id` against the newest message id, kept |
| 82 | in the channel's Durable Object, so the sidebar needs one read per |
| 83 | section, not per channel. |
| 84 | |
| 85 | ## Agents in channels |
| 86 | |
| 87 | - An agent is added to a channel like a person, with the agent's own |
| 88 | credentials, guardrails and audit log. Private channels exclude agents |
| 89 | unless added by name. |
| 90 | - A mention in a pull request's thread steers that pull request's agent |
| 91 | (`agent_messages`). A mention anywhere else starts an agent run whose |
| 92 | replies post in the thread. |
| 93 | - Asking a person for approval (merge past branch protection, deploy to |
| 94 | production) posts a card with the actions on it, and the same item lands |
| 95 | in the person's inbox. Acting in either place resolves both. |
| 96 | |
| 97 | ## Build order |
| 98 | |
| 99 | 1. **Contracts.** Channel, message and card types in `packages/contracts` |
| 100 | and `crates/contracts`; the RPC methods; the new event types. |
| 101 | 2. **Channels.** `services/chat`, its migrations, a Durable Object per |
| 102 | channel, and `deploy/stack.jsonc` entries. Create, join, post, edit, |
| 103 | threads, reactions, read state. |
| 104 | 3. **Chat mode.** The rail in `shell.tsx`; the Chat sidebar with starred, |
| 105 | sections, filters and browse; the channel page; the composer with |
| 106 | mentions. Code mode is unchanged apart from the rail. |
| 107 | 4. **Mentions to the inbox.** `chat.message.created` and inbox rows with |
| 108 | reason `mention`. |
| 109 | 5. **Project links and cards.** Link a channel to a project; the events |
| 110 | subscriber; cards for pull requests, issues, checks and deploys; the |
| 111 | project chat dock in Code mode. |
| 112 | 6. **One timeline.** A card's thread is the issue's or pull request's |
| 113 | timeline, both ways, with "also send to channel". |
| 114 | 7. **Agents.** Agents as members, steering by mention, approval cards, |
| 115 | direct messages with agents. |
| 116 | 8. **Scale.** Team sections, computed active-project section, muting, |
| 117 | browse, search. |
| 118 | |
| 119 | Steps 1 to 4 are a usable chat; 5 and 6 are what no chat app can do. |
| 120 | |
| 121 | ## Open questions |
| 122 | |
| 123 | - Must every channel belong to a project or team? Requiring one makes the |
| 124 | sidebar's sections better and orphans fewer channels. |
| 125 | - Do agents get their own mode, or only appear as direct messages? |
| 126 | - Retention and export for workspaces that need it. |
| 127 | - Does a self-hosted install ship chat, or is it hosted only at first? |