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
- 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.
- 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.
- Every change has an event, and the event carries enough to act on.
- The same input gets the same answer on every surface. Routing, ranking and wording live on the box.
- 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).
| # | Opportunity | Owners | Effort | State |
|---|---|---|---|---|
| 1 | One screen service on both sides: the Capsule sees the Mac, everyone sees what an agent does | cohesion (sight), capsule-pro, pwa, mobile, sessions, chat | M | glue built |
| 2 | Context now: project, thread, surface, device | cohesion (context), capsule-pro, chat, pwa, mobile, sessions | M | glue built |
| 3 | Connections for a capability ("send an email" offers every account) | vault, connectors, capsule-pro, pwa | M | vault owns |
| 4 | Suggest: one predictive-text tool for Capsule, chat and phone | cohesion (suggest), memory-iq, capsule-pro, chat, native-core | L | glue built |
| 5 | One ask path, with the wife's-name fix | memory-iq, sessions, capsule-pro, chat | M | owners |
| 6 | Vyre commands everywhere, rendered in the Capsule | platform, polish-cli, capsule-pro, chat | M | owners |
| 7 | Every key set up once, through the vault | vault, connectors, polish-cli, capsule-pro, pwa | M | vault owns |
| 8 | One "waiting on you" and one notice path | cohesion (waiting), pwa, capsule-pro, mobile, polish-cli | M | glue built |
| 9 | The hub read live by every surface | native-core, platform, app-design, capsule-pro, mobile, sessions | M | owners |
| 10 | One live catalog of projects, threads and agents | sessions, capsule-pro, chat, mobile | S | owners |
| 11 | Tips everywhere, in one slot, relevant to now | docs (tips), app-design, every surface | M | contract |
| 12 | Memory learns from the whole system | memory-iq, connectors, sessions | M | owners |
| 13 | One devices list and one pairing story | relay, vault, pwa, e2e | L | proposed |
| 14 | Sessions start knowing today (agenda, accounts, what is waiting) | sessions, memory-iq | S | proposed |
| 15 | One card set for asks, approvals and plans | app-design, chat, pwa, mobile, capsule-pro | M | proposed |
| 16 | Event vocabulary and a live event catalog | platform, cohesion | S | proposed |
| 17 | Shrink the boundaries allowlist | ci, owners of each edge | M | proposed |
| 18 | Inline pictures in chat: an agent's screen at a step, or an image it made | chat (render, owns), sessions, cohesion (sight), glass | M | proposed, 0.1.1 |
| 19 | A file lands in the right project, a session can join one later, Vyre Drive gets credit | projects (owns routing + layout), files, federation (Vyre Drive), memory-iq, chat, cohesion | L | proposed, 0.1.x |
| 20 | The Chrome extension and the Capsule, one login experience | vault (owns), capsule-pro, cohesion | S | decided, 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.targetslists 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 answerslocal_only.sight.stepsand the eventsight.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 latestsight.steppedfor a running agent. - pwa and mobile: a Glass mini-view (the frame from
sight.watchand the step line) on Now and in a thread. - sessions and chat: tool rows carry the tool call id; acted events carry
threadandcall, so a row opens the frame at that step. - Acting modules (hands-desktop, hands-chrome, hands-mac): add
thread,callandappto their acted events, and cut query strings from summaries.chrome.openputs 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 onsight.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.
| Pair | The user's side | The agent's side | One contract |
|---|---|---|---|
| Seeing a screen | screen.context on the Mac | tree, snapshot and frames in Glass | sight.* (built) |
| Acting in an app | hands 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 happened | nothing on the Mac | acted events, unread | sight.stepped for both |
| Watching a screen | side view on the Mac | Glass on the box | side view opens a Glass target; one viewer |
| Files | files on the Mac and the box | Glass files in the agent's home | one files contract keyed by where, so the phone reaches the Mac too |
| Filling a password | autofill on the user's devices | fill into an agent's computer | one vault fill with a grant log for both |
| Reminders | planner for the person | watchers and scheduled work for agents | one "due" row in waiting for both |
| Being asked | the gate for the person's sends | an agent's question in a thread | one waiting list (built) |
| Memory | the person's facts | project and agent rooms | one answer path with scope, not two stores |
| Terminal | term on the Mac | term on the box | already one module |
Cross-cutting contracts
| Contract | Tools | Event | Module |
|---|---|---|---|
| Screen, both sides | sight.targets, sight.now, sight.watch, sight.steps | sight.stepped | core/sight |
| Context now | context.report, context.now | context.changed | core/context |
| Suggest | suggest.query, suggest.offer, suggest.picked | none | core/suggest |
| Waiting on you | waiting.list, waiting.count | waiting.changed | core/waiting |
| Connections | the vault's connections list | vault's | vault (ADR 0028) |
| Credentials | the vault's need and connect | one needs_credential error shape | vault (ADR 0028) |
| Commands | platform's commands field | the module list | platform (ADR 0033) |
| Tips | docs' tips module | none | docs |