docs GitHub

ContributingDraft

Cohesion: making Vyre one system

Status: approved as ranked by the lead, 27 Sep 2026. Contracts: ADR 0036. Tracker: team/archive/work-journals/cohesion.md. Nothing here moves a feature out of its team. Cohesion writes the shared contracts, four small glue modules behind them (sight, context, suggest, waiting), and the tests that keep surfaces from drifting apart again.

The problem in one paragraph

Each module is good on its own, and each surface rebuilds the parts it needs. The Capsule has its own intent rules, model names, command table and memory answer. The Deck keeps a stale copy of the passkey tool list. Five account lists have five shapes. Nobody knows what is on screen or which project the user is in, so every surface guesses. What an agent does on its computer reaches no view at all: the acted events have no listener, and Glass shows only take-overs. There is no suggest tool, no notice contract and no single "waiting on you" count.

Principles

  1. One owner per fact. A surface never keeps a copy of a list another module owns (models, commands, accounts, tool policies, tokens). It reads the registry and follows the change event.
  2. Glue owns no data. A glue module fans out to other modules' tools under a deadline and merges the answers. Switch it off and each owner still works on its own.
  3. Every change has an event, and the event carries enough to act on.
  4. The same input gets the same answer on every surface. Routing, ranking and wording live on the box.
  5. One service, both sides. Where the user and an agent do the same kind of thing (look at a screen, act in an app, fill a password, get reminded), one contract serves both, keyed by who.

Ranked opportunities

Ranked by how much each makes Vyre feel like one system in daily use. Effort: S (a day), M (a few days across two or three teams), L (a week or more, several teams).

#OpportunityOwnersEffortState
1One screen service on both sides: the Capsule sees the Mac, everyone sees what an agent doescohesion (sight), capsule-pro, pwa, mobile, sessions, chatMglue built
2Context now: project, thread, surface, devicecohesion (context), capsule-pro, chat, pwa, mobile, sessionsMglue built
3Connections for a capability ("send an email" offers every account)vault, connectors, capsule-pro, pwaMvault owns
4Suggest: one predictive-text tool for Capsule, chat and phonecohesion (suggest), memory-iq, capsule-pro, chat, native-coreLglue built
5One ask path, with the wife's-name fixmemory-iq, sessions, capsule-pro, chatMowners
6Vyre commands everywhere, rendered in the Capsuleplatform, polish-cli, capsule-pro, chatMowners
7Every key set up once, through the vaultvault, connectors, polish-cli, capsule-pro, pwaMvault owns
8One "waiting on you" and one notice pathcohesion (waiting), pwa, capsule-pro, mobile, polish-cliMglue built
9The hub read live by every surfacenative-core, platform, app-design, capsule-pro, mobile, sessionsMowners
10One live catalog of projects, threads and agentssessions, capsule-pro, chat, mobileSowners
11Tips everywhere, in one slot, relevant to nowdocs (tips), app-design, every surfaceMcontract
12Memory learns from the whole systemmemory-iq, connectors, sessionsMowners
13One devices list and one pairing storyrelay, vault, pwa, e2eLproposed
14Sessions start knowing today (agenda, accounts, what is waiting)sessions, memory-iqSproposed
15One card set for asks, approvals and plansapp-design, chat, pwa, mobile, capsule-proMproposed
16Event vocabulary and a live event catalogplatform, cohesionSproposed
17Shrink the boundaries allowlistci, owners of each edgeMproposed
18Inline pictures in chat: an agent's screen at a step, or an image it madechat (render, owns), sessions, cohesion (sight), glassMproposed, 0.1.1
19A file lands in the right project, a session can join one later, Vyre Drive gets creditprojects (owns routing + layout), files, federation (Vyre Drive), memory-iq, chat, cohesionLproposed, 0.1.x
20The Chrome extension and the Capsule, one login experiencevault (owns), capsule-pro, cohesionSdecided, 0.1.1

1. One screen service on both sides

The user's words: show what is happening on the agent's computer right now and what the agent is doing, and let the Capsule see the screen: the same service, working on two sides.

Today. The Mac side is screen.context (front app, window, focused control, URL, visible text, redacted and blind in the floor's places), local only. The agent side is a VNC stream (computers.watch), a control tree (hands-desktop.tree, chrome.snapshot) and acted events (desktop.acted, chrome.acted, hands.acted) that nothing listens to. No link from a chat tool row to what happened on screen.

Contract (built, core/sight). A target is mac or agent:<name>.

  • sight.targets lists both kinds.
  • sight.now {target, parts?} gives app, window, URL (no query), the last step and who holds the keyboard. The Mac adds text only when asked and only on the Mac; an agent adds its controls.
  • sight.watch {target} gives an agent's live frames; the Mac answers local_only.
  • sight.steps and the event sight.stepped {target, agent, thread, call, action, summary, ok} are "what the agent is doing now": the Glass mini-view with its current step, in the Deck, the Capsule, chat and the phone.

Owners' parts.

  • capsule-pro: the Capsule's screen context and "about this page" read sight.now {target:"mac"}; a "Kit is working" pill shows the latest sight.stepped for a running agent.
  • pwa and mobile: a Glass mini-view (the frame from sight.watch and the step line) on Now and in a thread.
  • sessions and chat: tool rows carry the tool call id; acted events carry thread and call, so a row opens the frame at that step.
  • Acting modules (hands-desktop, hands-chrome, hands-mac): add thread, call and app to their acted events, and cut query strings from summaries. chrome.open puts the full URL with its query in its summary today.
  • Registry (platform): carry the tool call id in call meta, so modules can put it on events.

2. Context now

Contract (built, core/context). context.report is how a surface says where the user is (the Capsule on app switch, chat on thread open, the phone on foreground, the CLI with its folder). context.now merges the latest of each field across surfaces and finds the project from the folder. context.changed names what changed, at most once a second, and never carries the app, window or URL. Text and selection are refused. A model cannot report or read it.

Consumers. The Capsule (default project, ask folder, screen), chat (a new thread lands in the right project), harness and Vyre Memory (scope and boost), push (ring the device in use), tips (item 11), suggest (ranking).

3. Connections for a capability

The vault owns it (ADR 0028 decision 9b): one connections table fed by google, mcp and vault events, capability names, per-surface grants, and a use call per row. Cohesion does not build a second one. What cohesion checks: the Capsule's "send an email" rows, gate.route's options, Slack's server choice and the Deck's Connections view all read that one list; suggest's account lane reads it too. Open with the lead: vault's and connectors' ADRs both plan a mail module.

4. Suggest

Contract (built, core/suggest). suggest.query {text, cursor, surface} answers in about 2 ms from cached lists (agents, projects, threads, people, upcoming planner items, connections), and asks offered sources with a 25 ms deadline. @x is mentions, /x commands, anything else entities, accounts and times on the last word. suggest.offer is how a module adds a source at start; suggest.picked teaches ranking.

Owners' parts. memory-iq offers personal entities and aliases ("my wife", graph labels) from memory already loaded, never a model call; the Capsule and the chat composer (chat-core, so the Deck, PWA and Expo all get it) call it per keystroke. Vyre Memory answers come on pause, not per key.

5. One ask path

The box decides question versus task and the model for every surface, and the memory answer comes from memory, then Vyre Memory. The Capsule retires Said.swift, its own question rules and its hardcoded model names. Rides with the wife's-name fix: memory-iq and sessions own it, and memory-iq's source-trust rules apply.

6. Commands everywhere

Platform's manifest field for commands (ADR 0033) is the one source for the CLI, the Capsule's commands, chat's slash menu and the Deck. A result shape (table, text or card) lets the Capsule and chat draw Vyre output instead of printing terminal text. polish-cli adds JSON output to the seven commands without it (capsule, connect, hooks, mcp, sideview, voice, send).

7. Every key set up once

The vault owns it (ADR 0028 decision 9a: a need per module, one connect step that stores and grants). Six flows retire into it: the voice key command, the grant command for the Claude token, onboarding's Claude step, connectors' client item, the push and tailnet keys. Cohesion's part is one error shape every module answers with when a credential is missing, needs_credential with the module and the need, so every surface opens the same sheet. The names module still reads its token from the environment before the vault.

8. One "waiting on you"

Contract (built, core/waiting). waiting.list merges open questions, held drafts, ringing reminders and pending pairings into one row shape and one count; each row says which owner tool answers it and what the person still gives. waiting.changed fires when the count moves.

Owners' parts. Push, the Capsule's "Waiting on you" row, the Deck, the phone and the status line all render from it, so one answer clears every device and a phone stops ringing twice. Modules declare their notice kinds in their manifest instead of push keeping a fixed map.

9. The hub read live everywhere

In flight with native-core (ADR 0035) and platform. Cohesion's part: the change event carries the value, and a drift test fails any surface that ships its own model list, effort list, tool-policy list (the Deck's and the app's copies of the passkey list are already out of step with the box) or a token file not generated from the hub.

10. One live catalog

Projects, threads (with status) and agents in one call, with one documented refresh event set. Fixes the Capsule's per-project loop and its second copy in sight's extension.

11. Tips everywhere

The user's ask: unobtrusive tips about the module in use and about modules not tried yet, and "what's new" after an update.

Split. The docs team owns the words and a small tips module (next tip for a surface and a context); tips live in each module's manifest next to what it teaches memory. Cohesion owns the slot and the signals:

  • One tip slot per surface, specified once by app-design: the Capsule's empty state, chat's composer hint line, the Deck's empty states, the phone's empty screens, the CLI's "next" line and the status line. One tip at a time, dismissable, never over a question or an approval.
  • Relevance comes from the shared signals: context.now (which project, which app is in front), sight.now (what is on screen, on the Mac only), waiting.count (never tip while something waits), suggest's picks (what the user already uses) and the registry's module list (what is installed and never used).
  • "Module never used" needs a per-module use count. Today nothing records it; the event log's source column is a rough stand-in. Ask platform for a count in the registry.
  • "What's new" needs the last version each surface saw, and entries from the changelog or a per-module note.

12. Memory learns from the whole system

Memory listens only to thread and session events. Add planner (people and places), approved sends (who the user writes to), the user's own accounts, and calendar attendees: first-party records only, never tool or test text.

13 to 17, in short

  • Devices: six lists (vault devices, relay devices, person sessions, push devices, onboarding peers, link peers). One device row with its capabilities, one list in Settings, one pairing story.
  • Sessions start knowing today: the session brief adds the agenda, the waiting count and the project's connections, inside its 600-token budget.
  • One card set: ask, approval and plan cards from the design system, built once in chat-core for web and Expo, mirrored in Swift from the same spec.
  • Events: a catalog tool from the manifests; one noun form (file versus files, computer versus computers) with aliases for a release; names built at run time documented.
  • Boundaries: 26 frozen edges; most are shared-library moves (tailscale, transport, auth).

18. Inline pictures in chat

The user's ask: now that chat runs through the Agent SDK, sometimes show a picture inline in the transcript, not just a row of text: what an agent's screen looked like at a step, or an image it made or a tool returned.

Two sources, two capture paths, one render path.

  • A step's screen. sight.frame {target, maxWidth} (built, core/sight) already returns one scaled JPEG keyed to the last step on that target, refreshed on sight.stepped, never a timer. Glass calls it directly for its own reconnect-fallback still and resting-tile preview (agreed with glass, 2026-09-28); a chat row can call the same tool for the same reason, keyed by the tool call id sessions already carries.
  • An image the agent made. A Canva render, a saved screenshot, a generated image, whatever a tool call returned or wrote. This is not sight's data: no live screen, no target, no step. It needs its own small attachment convention (thread + call -> a file reference, or the tool's result carrying an image block directly), owned by whoever already writes those files today.

Owners, decided by the lead 2026-09-28. Chat owns rendering in every transcript (Deck, PWA, Expo, all from chat-core so it is built once). Sessions passes image blocks through from the Agent SDK's own message shape rather than chat re-deriving them. Cohesion's part stays sight.frame and sight.stepped, already built; cohesion does not render anything. Glass is a second consumer of sight.frame, not a second capture path: one still, two callers.

Open for 0.1.1, when sessions and chat relaunch. Whether "an image the agent made" is a new small module or rides on an existing one (files, drive); how large an inline image gets before it is a link instead; whether a sight.frame call from chat needs its own rate limit alongside Glass's (both call the same tool, on different cadences).

19. A file lands in the right project, and can join one later

The user's ask, three parts. Every file sent to a session, by whatever door, lands in its project's files and the project folder keeps itself tidy. A session can start with no project and attach to one later: its workspace, transcript and files move, its memory joins the project. Where Vyre Drive did the moving, the UI says so.

Today. Three doors, three destinations, none of them project-aware: Taildrop's inbox lands everything in one place (/work/inbox, core/files/drop.js); chat's own upload path (paused, pre-relaunch); and files.drive (core/files/drive.js, Taildrive) shares a whole configured folder, not a per-session destination. projects.move (core/projects/move.js) is the nearest existing pattern for "move something into a project's home safely": dry run first, rename falls back to copy-then-remove across a Docker volume boundary (EXDEV), a symlink left at the old path so an open Claude Code session (which keys its transcript by folder) can still resume, one outcome record. Nothing today moves a single session, only every project home at once, on a box.

1. File router. Owner: projects and files (the lead's lean). projects owns the rule (which project a file belongs to, from the same thread/project context context.report already carries) and the folder layout inside a project's files; files owns the actual write and the guard (no secret, no key, same rules Vyre Drive's sharing already applies to a shared folder). Every upload door - chat, the Deck, the phone, the Capsule, Taildrop's inbox (core/files/drop.js) - passes thread context in (from context.now or its own session id) and gets back where the file landed; none of them decide the destination themselves. The Taildrop inbox becomes the fallback only when no thread context comes with the file at all.

2. Attach a project later. Owner: projects (the move), memory-iq (the graph). Shaped like projects.move, scoped to one session: dry run first, the workspace directory and any files it already has move into the project's home, a symlink left at the old path for resume, one outcome record naming what moved and what was skipped. projects.attach {session, project, dryRun?} is the likely tool shape; projects.move's EXDEV fallback and its "never run twice, check the record" caution both apply. Once moved, memory-iq re-scopes that session's context onto the project's own facts and entities, the same join a session started inside a project gets from the start - this is memory-iq's design to write, cohesion is not proposing memory internals here.

Attach is person-only (decided by the lead, 2026-09-28). dryRun is not an option, it is the first step every time: a preview of what moves where, then one confirm, no Touch ID (a person setting, per the no-nag rule - approving a Touch ID dialog for this is not the pattern). An agent may suggest attaching a session to a project; it may never call the tool that does it.

3. Vyre Drive gets credit. Owner: federation (owns Vyre Drive and Mac-box file access). Whatever tool or event the router (part 1) or the attach (part 2) uses to say "this file moved" needs to carry how alongside where - a share, a Taildrop (files.received, core/files/drop.js), a direct write - so a surface can say "saved to Harlow Legal / files via Vyre Drive" instead of staying silent about the mechanism. Federation decides the field's shape (likely via: "drive"|"taildrop"|"upload" on whatever event the router settles on); cohesion is not proposing UI copy.

Chat's part, spec'd for hand-over (chat is paused). Chat's upload surfaces (Deck, PWA, Expo, Capsule) always send thread context with a file, never a bare upload; wherever chat shows a project's files it can now credit the Drive; the attach flow (part 2) needs a chat-facing entry point - "move this chat into a project" - but building it waits for chat's relaunch, per the lead.

Open, for federation and memory-iq to resolve. The router's exact contract (call shape, which module a surface actually calls); what "the project's graph" means precisely for a session that already has its own memory before attaching. This spec sets direction and owners; it does not fix the tool names.

20. The Chrome extension and the Capsule, one login experience

Decided by the lead 2026-09-28: the extension ships in 0.1.1, and cohesion's part is making it and the Capsule feel like one product, not two front doors to the same passwords. One vault (the extension reads and fills through the same vault items and the same grants the Capsule already uses, never its own store); one grants model (a fill from the extension shows up in the same grant/audit log as a fill from the Capsule, no separate history); one "waiting on you" (an extension-side need_credential or a pending grant is a waiting.list row, cohesion item 8, the same as everything else); the same account-row look (item 3's Connections list is what both render from, so an account added in one shows up in the other with no separate "connect the extension" step). Vault owns the vault side; capsule-pro keeps the Capsule's own fill UI as the reference look the extension matches, not the other way around.

One service, both sides

Every pair where the user and an agent do the same kind of thing through two services today.

PairThe user's sideThe agent's sideOne contract
Seeing a screenscreen.context on the Mactree, snapshot and frames in Glasssight.* (built)
Acting in an apphands on the Mac (holds sends for commit)hands-desktop and chrome (refuse sends)one hands vocabulary and one send rule, both held for the person
Showing what happenednothing on the Macacted events, unreadsight.stepped for both
Watching a screenside view on the MacGlass on the boxside view opens a Glass target; one viewer
Filesfiles on the Mac and the boxGlass files in the agent's homeone files contract keyed by where, so the phone reaches the Mac too
Filling a passwordautofill on the user's devicesfill into an agent's computerone vault fill with a grant log for both
Remindersplanner for the personwatchers and scheduled work for agentsone "due" row in waiting for both
Being askedthe gate for the person's sendsan agent's question in a threadone waiting list (built)
Memorythe person's factsproject and agent roomsone answer path with scope, not two stores
Terminalterm on the Macterm on the boxalready one module

Cross-cutting contracts

ContractToolsEventModule
Screen, both sidessight.targets, sight.now, sight.watch, sight.stepssight.steppedcore/sight
Context nowcontext.report, context.nowcontext.changedcore/context
Suggestsuggest.query, suggest.offer, suggest.pickednonecore/suggest
Waiting on youwaiting.list, waiting.countwaiting.changedcore/waiting
Connectionsthe vault's connections listvault'svault (ADR 0028)
Credentialsthe vault's need and connectone needs_credential error shapevault (ADR 0028)
Commandsplatform's commands fieldthe module listplatform (ADR 0033)
Tipsdocs' tips modulenonedocs