---
title: Specification
summary: How Vyre is built, from principles and repository layout to the module contract, the core services, the security floor and the milestones.
audience: builders, agents
owner: integrator
status: stable
---

# Specification

Version 0.1. Status: working draft, 26 Sep 2026. This document is the source of truth for how Vyre is built.
If code and this document disagree, one of them is a bug; fix whichever is wrong and say so in
the changelog.

---

## 1. What Vyre is

Vyre runs Claude Code on a machine you own and adds the things that do not belong inside
Claude Code itself:

1. **Claude Code on your box.** It runs on a server you control, not your laptop. Simpler to set
   up, reachable from anywhere, and untrusted code never touches your own computer.
2. **Structure.** Projects, threads and context instead of one pile of sessions.
3. **Memory.** Search across every session, and a memory layer that marks in gold what came
   from memory rather than a model.
4. **Agents on your own quota.** Launch agents that use your own Claude subscription (a setup
   token) or an API key with a budget. Each agent has its own computer.
5. **A private network, built in.** Your Claude Code is on your phone as easily as your laptop, at
   your own address, reachable only by your devices. Nothing to install or sign in to
   ([the network](../concepts/network.md)).
6. **A built-in vault.** Agents use credentials without anyone seeing them; single items can be
   shared with other people's Vyre, relayed by default so one revoke ends access.

### What Vyre is not

- **Not a fork or wrapper of Claude Code.** Vyre plugs into Claude Code the way any plugin does.
  When Claude Code improves, Vyre improves with it. If Claude Code ships a feature Vyre has,
  Vyre steps aside and uses theirs.
- **Not a hosted service.** Vyre AI runs one thing: the name directory for `<you>.vyre.run`. It
  holds no user data.
- **Not an IDE.** It uses Claude Code for coding; Chat is part of the Vyre app, a window onto real
  Claude Code sessions, not a separate assistant.

### Install and onboarding

Installed like a self-hosted server app (n8n, a media server): one line from the landing page,
then a visual onboarding in the browser. The terminal does as little as possible. The whole
journey, and why, is [ADR 0008](../adr/0008-install-journey.md); the one page for users is
[Install](../get-started/install.md).

```
npm install -g vyre
vyre up
```

On a Mac, `vyre up` asks for your server's pairing code, or asks where Vyre should run. The recommended
answer is a server, which is `vyre box add user@host`: it installs Docker and the box over SSH,
opens the tunnel and the browser itself, and waits, so the person never opens a shell on the
server. On a server, `curl -fsSL https://vyre.run/install.sh | sh` does the same from the inside.
Either way the box's `vyre up` makes one link: `http://127.0.0.1:<port>/onboard?t=<one-time
token>` (on a headless server, reached over an SSH tunnel; the `ssh -L` line is printed). The
onboarding is the Vyre app's first screen, and walks through, one step a screen:

1. **You.** Your name, and your assistant's name.
2. **Claude Code.** Detects `claude`; signs in with your subscription (`claude setup-token`) or
   an API key, stored in the Vault. Nothing is typed into a terminal.
3. **Your address.** Claims `<you>.vyre.run`, or takes your own domain, gets the certificate by DNS
   challenge, then switches the page to it ([the network](../concepts/network.md)).
4. **Your history.** Finds existing Claude Code sessions, indexes them in the background with a
   progress bar, and lets you make your first projects by picking sessions from the catalogue.
5. **Your devices.** A QR code to pair the phone, and the pairing code for the Mac.

Finishing creates the assistant, which greets you on the last screen. The Mac's terminal ends
with the same words: "Vyre is ready." and the address.

Every step can be skipped and finished later from Settings; each is also a `vyre` command for
people who prefer the terminal. The loopback onboarding link is single-use, expires after an
hour, and is the only route vyred serves before an owner exists.

---

## 2. Principles

These are rules, not aspirations. A change that breaks one needs a spec change first.

1. **Public Claude Code surfaces only.** Vyre touches Claude Code through: the plugin system
   (hooks, skills, commands, agents, MCP servers), and documented CLI flags (`--plugin-dir`,
   `--append-system-prompt`, `-n`, `--resume`, `-p` with `stream-json`, `--permission-prompts`).
   Reading transcript files on disk is the one exception. It lives in a single adapter
   (`core/transcripts`), is best effort, and must degrade to "no history" rather than fail.
   One flag is a known risk: the Switchboard passes `--permission-prompt-tool stdio` so a
   headless thread's permission questions come to vyred. It is the flag the Agent SDK passes,
   but `claude --help` does not list it, so a release could change it without notice. It lives
   in one place (`core/switchboard/runner.js`), and a thread without it denies every question.
2. **Local first.** Everything runs on machines the user owns. Nothing leaves them except
   through the Gate, and nothing about the user reaches Vyre AI beyond a DNS record.
3. **One process per machine.** `vyred` runs every service on that machine. On the box it runs
   Core; on the Mac it runs Local. Same binary, different modules enabled.
4. **Everything is a module.** Core services, the Harness pieces, surfaces and third-party
   additions all use the same contract (section 5). No special cases.
5. **Boring, readable code.** Node 22.5 or newer, ES modules, plain JavaScript with JSDoc types
   and `// @ts-check`, no build step for the core. Dependencies need a reason in the changelog.
   SQLite through the built-in `node:sqlite`. Tests with the built-in `node:test`.
6. **The terminal is first class.** Anything the Vyre app can do, `vyre` can do.
7. **The security floor cannot be configured away** (section 11).
8. **Light by default.** Vyre runs all day on the user's own machines, so idle must cost almost
   nothing. Budgets, checked by `scripts/perf-check` and in CI:
   - vyred idle: under 0.9% of one core (p95) and under 1.8% sustained, under 60 MB of heap
     after a full GC, with no polling faster than once a minute when nothing is happening; work
     is driven by events and file-system notice. Resident size is reported and gates only on the
     Node the box ships (150 MB settled, 200 MB in the first 30 seconds), because other Node
     versions keep a heap they have not collected yet.
   - Lumen hidden: under 0.2% CPU, no GPU use, under 250 MB resident for all its processes;
     shown and idle, under 2% CPU. It wakes in under 100 ms.
   - Vyre app web build in a background tab: no timers faster than a minute; the event stream only.
   - Heavy work (indexing, embedding, curation) runs at low priority, yields, pauses on battery
     and when the user is active, and never blocks a hook or Lumen.
   - Memory that grows with the corpus (search indexes, caches) is bounded and measured.
   A change that breaks a budget is a bug, like a failing test. Measured numbers are in
   [Performance](performance.md).
9. **Nothing personal in the repo.** No names, folders, domains, clients or keys. Personal
   settings live in `~/.vyre/config.json`. A test fails if the source names a real person.

---

## 3. Repository layout

```
vyre/
  bin/vyre                 the CLI entry (thin: parses argv, calls core/cli)
  core/                    services that run inside vyred
    config/                ~/.vyre paths, config.json, env overrides
    store/                 the SQLite store: open, migrate, WAL, busy timeout
    events/                the event log and in-process bus
    modules/               the module loader and contract validation
    daemon/                vyred: lifecycle, HTTP API, unix socket
    transcripts/           the ONE adapter that reads Claude Code transcript files
    projects/              projects, threads, markers, the session catalogue
    recall/                full-text and vector search over every turn
    memory/                the graph and the curator (the only writer)
    vault/                 credentials, sealing, passes          (workstream: vault)
    watchers/              the watcher runtime                    (workstream: watchers)
    gate/                  outbound control and approvals          (workstream: gate)
    switchboard/           headless sessions, streaming, lease    (workstream: switchboard)
    ship/                  preview, repo, live                     (later)
    computers/             agents' containers and the screen pool  (workstream: computers)
    names/                 <you>.vyre.run, certificates (workstream: box)
    link/                  the Mac and the box as one system: pairing, ctx.remote, box events (workstream: link)
    files/                 search, preview and fetch files on both machines, inside their roots (workstream: link)
    planner/               alarms, timers, reminders, todos, notes, a calendar; one scheduler (workstream: planner)
    cli/                   every `vyre` command
  harness/                 a Claude Code plugin
    .claude-plugin/plugin.json
    hooks/hooks.json       Brief, Enrich, Rules, Learn, Stream entry points
    hooks/hook.js          the one script every hook runs; it calls vyred
    skills/                write-a-watcher, use-the-vault, work-in-a-project
    commands/              /vyre slash commands
    mcp/                   the Vyre MCP server (tools Claude can call)
  local/                   Mac-only modules
    capsule/               Lumen                             (workstream: capsule)
    hands-mac/             computer use on macOS                   (workstream: capsule)
  apps/app/                the Vyre app (Expo); vyred serves its web build at /app/
  modules/                 first-party optional modules (hands-desktop, hands-chrome)
  docs/                    this spec, the module guide, ADRs, workstream notes
  test/                    cross-module tests; unit tests sit beside their code
  CHANGELOG.md
  LICENSE                  Apache 2.0
```

Runtime data never lives in the repo. It lives in `~/.vyre/` (override with `VYRE_HOME`):

```
~/.vyre/
  config.json              the user's settings (section 4)
  vyre.db                  the store
  vault/                   sealed vault items
  modules/                 third-party modules the user installed
  watchers/                watchers Claude wrote, one folder each
  certs/                   ACME account key and each name's certificate and key
  models/                  the embedding weights, a cache
  logs/                    vyred logs, one file per day
  env                      optional environment for the systemd unit
  vyred.sock               the local API socket
```

A project folder carries `.vyre/project.json` (section 7.2).

---

## 4. Configuration

`~/.vyre/config.json`. Every key is optional; defaults are sensible for one person on one Mac.

```json
{
  "name": "alex",
  "role": "box",
  "projectsDir": "~/Vyre/projects",
  "roots": ["~/Work"],
  "me": { "domains": ["example.com"], "emails": ["alex@example.com"] },
  "transcripts": ["~/.claude/projects", "~/.claude/projects-archive"],
  "modules": { "enable": ["watchers", "vault"], "disable": [] },
  "network": { "address": "alex.vyre.run" }
}
```

`role` is `box` or `local`. It decides which modules `vyred` starts by default.

---

## 5. The module contract

Every module is a folder with a `module.json` and an entry file. The loader validates the
manifest, resolves dependencies, and starts modules in order. The short way in, with a
complete example, is [Writing a module](../build/writing-a-module.md). A module that fails to start is
disabled and reported; it never takes `vyred` down.

### 5.1 The manifest: five verbs

```json
{
  "name": "watchers",
  "version": "0.1.0",
  "roles": ["box", "local"],
  "requires": ["projects", "vault"],
  "does":    { "tools": ["watchers.create", "watchers.list", "watchers.pause"] },
  "watches": { "emits": ["watcher.fired", "watcher.failed"] },
  "shows":   { "deck": ["panel:watchers"], "capsule": [], "cli": ["watchers"] },
  "needs":   { "vault": ["per-watcher"] },
  "teaches": { "memory": ["watcher.item"] }
}
```

| Verb | Meaning |
|---|---|
| `does` | Tools this module offers. Each becomes an MCP tool for Claude, an HTTP route, and (where it makes sense) a CLI command, from one definition. |
| `watches` | Events it emits into the event log. |
| `shows` | Where it appears: app panels, Lumen actions, CLI commands. |
| `needs` | Vault items it asks for. It never reads the vault any other way. |
| `teaches` | Kinds of fact it hands the curator. It never writes Memory directly. |

`requires` names other modules only. The store and the event log are the kernel: every module has
them, so they are never listed. The published schema for every key, including those module API 1
adds, is `packages/module-sdk/manifest.schema.json` ([ADR 0033](../adr/0033-hackable-vyre.md)).
Today Lumen reads `shows.capsule`; `shows.deck` is declared for the app's slot registry,
which does not read it yet (ADR 0033, phase 4).

### 5.2 The entry file

```js
// @ts-check
/** @type {import('@vyre/module-sdk').Module} */
export default {
  async start(ctx) {
    // ctx.store      the module's own namespace in vyre.db (tables prefixed with its name)
    // ctx.events     emit(type, payload), on(type, fn), since(id), latestId()
    // ctx.vault      fetch(name) for items its manifest declares
    // ctx.memory     teach(kind, fact): goes to the curator's queue
    // ctx.call       another module's tool, through the same checks as any caller; projects
    //                and threads are read this way (projects.list, threads.list)
    // ctx.log        structured logging
    // ctx.tool(name, { input, run })   register a tool declared in `does`; run(input, { caller,
    //                thread?, agent? }): thread and agent only when vyred verified them
    return { async stop() {} };
  },
};
```

### 5.3 One tool, three surfaces

A tool is defined once with a JSON schema for its input and an async `run`. The loader exposes
it as:

- an **MCP tool** in the Harness's MCP server, so Claude can call it;
- an **HTTP route** `POST /v1/tools/<module>.<tool>` on vyred;
- a **CLI command** `vyre <module> <tool>` when the manifest lists it under `shows.cli`.

Every call passes through the Rules (section 11) before `run` executes.

A tool learns where a call came from only from what vyred checked. `caller` is a claim. `agent`
and `thread` are set when the caller proved them: an agent's thread by the key the Switchboard
gave it, any other session by the key its SessionStart hook was given for its `claude` process.
A thread named in the input is a claim like any other.

---

## 6. Events

Append-only log in `vyre.db`, plus an in-process bus.

```sql
CREATE TABLE events (
  id INTEGER PRIMARY KEY,
  at INTEGER NOT NULL,          -- ms since epoch
  type TEXT NOT NULL,           -- "watcher.fired", "thread.started", "gate.held"
  source TEXT NOT NULL,         -- the module that emitted it
  project TEXT,                 -- the project it belongs to, when known
  thread TEXT,                  -- the Claude Code session id, when known
  payload TEXT NOT NULL         -- JSON; never a secret value
);
```

Rules: events are facts about the past, named `<noun>.<past-verb>`. Payloads never carry
credentials (the store rejects values that look like secrets, using the shared redactor).
Consumers are idempotent: the same event delivered twice changes nothing.

---

## 7. Core services

Each is a module under `core/`. Their public tools are listed; everything else is private.

### 7.1 Store, config, events, modules, daemon

The plumbing every module uses. The store opens SQLite with WAL and a 10-second busy timeout on
every connection; migrations are numbered SQL files per module.

`vyred` listens on `~/.vyre/vyred.sock` for local clients and, when networking is on, over the
built-in network and the relay, identifying each caller from its entry on the identity list
([the network](../concepts/network.md)); it opens no listener of its own on the network. HTTP API: `/v1/...`, JSON, responses are
`{ "data": ... }` or `{ "error": { "code", "message" } }`.

### 7.2 Projects

A project is a home folder, the other folders it owns, the threads in it, and its watchers.
Declared by `<home>/.vyre/project.json`:

```json
{
  "name": "Juniper Studio",
  "org": "Rivera Studio",
  "workspaces": ["../juniper-site"],
  "threads": ["<claude session id>"],
  "people": [{ "name": "Dana Reyes", "email": "dana@juniperstudio.example" }],
  "watchers": ["juniper-invoices"]
}
```

- A **thread** is a Claude Code session. It belongs to a project because a person picked it, or
  because it ran in one of the project's folders. A thread can belong to several projects.
  Nothing automatic removes a pick.
- The **catalogue** lists every session on the device from the configured transcript folders,
  with its `/rename` name, first message, folder and last activity, searchable by what was said.
- Tools: `projects.list`, `projects.create`, `projects.add-threads`, `projects.remove-threads`,
  `projects.catalog`, `projects.context`.

Port from: `the prototype's bin/projects.cjs` (tests in `test/t-projects.cjs`).

### 7.3 Recall

Search over every turn: full-text (FTS5) plus local embeddings reranked over a wide pool. Grown
transcripts append new turns in place and keep their vectors. Measured on a real 100k-turn
corpus: dense search over assistant turns beats full-text alone (MRR 0.195 against 0.142).

Tools: `recall.search`, `recall.thread`.
Port from: `the prototype's bin/recall.cjs`, `embed.cjs`, `sanitize.cjs`.

### 7.4 Memory

The graph and the curator. The curator is the only writer. It extracts entities, addresses,
domains, repos and named things from threads, keeps bi-temporal edges with provenance (every
fact points at the turn it came from), and never runs a model on the hot path. Modules teach it
through `ctx.memory.teach`; sessions only read.

Measured: the graph is precise for identity and routing and did not improve passage retrieval,
so Recall owns retrieval and Memory owns facts, people and links.

**Project graphs.** Each project has its own context graph: the facts from its own sessions and
from what its watchers teach. Together they make the main graph, which only the assistant and
agents granted those projects can see. A project's brief and a session in that project draw only
on its own graph; nothing from one client's project reaches another's.

Tools: `memory.facts`, `memory.pin`, `memory.mute`, `memory.why`.
Port from: `the prototype's bin/curator.cjs`, `graph.cjs`.

### 7.5 Vault · workstream

Credentials, sealed at rest, released one item at a time to a module or agent that declared it.
No screen, log or event ever shows a value. Passes share an item with another person's Vyre:
**relayed** by default (the value never leaves your box; their calls go through your Gate over
the network; revoke ends it at once) or **sealed** (an encrypted copy; revoking means rotating).
Offboarding is one action: revoke everything a person holds and list what must be rotated.

The Vault is meant to replace 1Password entirely, for a person and for their agents: logins
(with TOTP codes), cards, secure notes, API keys and env sets, passkeys later; a password
generator; import from 1Password, Bitwarden, Chrome and Safari; autofill in the browser through
an extension, and on the phone; Lumen can fill a login into the front app. Agents use
items without seeing them; people see them only after unlocking on their own device.

Tools: `vault.put`, `vault.list` (names only), `vault.grant`, `vault.revoke`, `vault.pass.create`,
`vault.pass.revoke`, `vault.offboard`. Modules call `ctx.vault.fetch(name)`, never the store.
Port from: `the prototype's bin/vault.cjs`, `broker.cjs`.

### 7.6 Watchers · workstream

A runtime, not a set of integrations. Vyre hosts watchers; Claude writes them.

A watcher is a folder in `~/.vyre/watchers/<name>/` with `watcher.json` and `watch.js`:

```json
{ "name": "juniper-invoices", "project": "juniper-legal",
  "schedule": "*/15 * * * *", "needs": ["billing-inbox"], "emits": "invoice.seen" }
```

```js
export default async function watch({ vault, since, emit, log }) {
  // fetch whatever changed since `since`; call emit({ ... }) for each item
}
```

The runtime handles scheduling (cron or webhook), credentials from the Vault, `since` cursors,
retries with backoff, filing items into the project, logs, pause and resume. The Harness ships a
skill, **write-a-watcher**, that turns "watch X and file it into Y" into a watcher and a dry run.

Tools: `watchers.create`, `watchers.test`, `watchers.list`, `watchers.pause`, `watchers.logs`.

### 7.7 Gate · workstream

The only way out of an agent's container. It adds credentials at the boundary, and holds
anything that would send as the user, spend money or delete until the user approves the final
content. Held items appear in Now, Lumen and the phone.

Tools: `gate.held`, `gate.approve`, `gate.reject`.

### 7.8 Switchboard · workstream

Runs Claude Code sessions headless (`claude -p --input-format stream-json --output-format
stream-json --permission-prompts host`), streams every event to any open surface, gives one
surface the keyboard at a time, and routes each permission question to wherever the user is.

Tools: `threads.start`, `threads.send`, `threads.stream`, `threads.lease`, `threads.stop`.

### 7.9 Computers · workstream

Each agent gets a container with a desktop, Chrome and a terminal. Screens come from a shared
pool and are checked out only while an agent needs to look at something; idle containers are
frozen. Glass streams a screen to the Vyre app and supports take-over.

### 7.10 Names and network · workstream

`<you>.vyre.run` points at your box's address on the private network, so only your devices can
reach it. Certificates are issued by DNS challenge, which works for a private address. A device
that reaches the box arrives as `device:<id>`, identified from its entry on the identity list and
never by a header, so there is no separate login ([the network](../concepts/network.md)). The
name directory at vyre.run holds only the DNS record.

### 7.11 Learning · workstream

Vyre learns from how you correct it, and enforces what it learned. A lesson that only sits in
memory is advice; Vyre's lessons are checked by hooks, so a model cannot forget them.

1. **Signals.** Corrections in your prompts ("no", "don't", "always", "I told you"), the edits you
   make to a draft before you approve it (the Gate), tool calls you deny, changes you revert,
   and `/vyre remember`. Each signal keeps the turn it came from.
2. **Lessons.** A signal becomes a lesson: `{ scope: all | project | agent, when, rule,
   check?, level: remind | ask | block, source }`. Distilling free text into a rule may use a
   model, off the hot path, on your own quota. You see every new lesson and can edit, narrow or
   retire it.
3. **Enforcement.** A lesson with a check becomes code. Before a tool runs, the Rules hook
   denies or asks with the lesson quoted. Before a turn ends, the Stop hook runs the output
   checks (banned characters, required steps such as "update the changelog", "test before
   commit") and, when one fails, refuses to end the turn and tells Claude which lesson it broke,
   so Claude fixes it first. A lesson without a check is added to the brief and the prompt
   whenever its `when` matches, every time.
4. **Escalation.** Every lesson counts how often it applied and how often it was broken. A
   lesson broken again moves up a level: remind, then ask, then block.

Tools: `learn.lessons`, `learn.add`, `learn.edit`, `learn.retire`, `learn.check {stage, ...}`
(what the hooks call). Events: `lesson.learned`, `lesson.broken`, `lesson.escalated`.

---

## 8. The Harness

A Claude Code plugin in `harness/`. Every thread Vyre starts loads it with `--plugin-dir`, so a
user's global Claude Code setup is never modified. Users may also install it for plain `claude`.

| Hook | Harness piece | What it does |
|---|---|---|
| `SessionStart` | **Brief** | Adds what the project is, its people, its other threads and its memory. From that project only; capped at about 600 tokens. |
| `UserPromptSubmit` | **Enrich** | Adds relevant memory, marked as memory with source, age and confidence; nothing when nothing is relevant. |
| `PreToolUse` | **Rules** | Checks the call against the security floor and the user's rules; allows, denies, or asks. |
| `PostToolUse` | **Learn**, **Stream** | Records files touched; compares a draft with what the user finally sent. |
| `Stop` | **Stream**, **Check** | Marks the turn complete for every surface. Runs the lessons' output checks first; a failed check keeps the turn going with the broken lesson named (section 7.11). |

Every hook is a few lines that call vyred over the socket and print what it returns. If vyred
is not running, hooks exit silently and Claude Code behaves exactly as without Vyre.

The Harness also ships:

- **MCP server** `vyre`: every module tool (section 5.3).
- **Skills:** `write-a-watcher`, `use-the-vault`, `work-in-a-project`.
- **Commands:** `/vyre status`, `/vyre project`, `/vyre recall <query>`.

---

## 9. Surfaces

| Surface | What it is | Built from |
|---|---|---|
| CLI | `vyre`: home, projects, threads, context, up, status | `core/cli` |
| Lumen | Control-Control command bar on the Mac: talk to the assistant, to any agent, or to any session | `local/capsule` |
| Vyre app | One Expo app: the web build at `<you>.vyre.run/app/`, iPhone and Android. Now, Projects, Memory, Agents, Vault, Settings | `apps/app` |
| Glass | An agent's screen, live, with take-over | `apps/app/screens/glass` + `core/computers` |
| Chat | Vyre's own chat layer: projects, then sessions, each session the terminal mirrored as a readable conversation (tool calls folded, diffs, asks and held items inline), on phone and computer, driving the same Claude Code sessions as the terminal. No third-party chat server. | `apps/app/src/chat` |
| Phone | Now, approvals, drafts, Glass, Ask | the Vyre app on iPhone and Android |

Every surface talks to vyred's API. None reads the store directly.

---

## 10. Agents and the assistant

Every Vyre session is a real Claude Code session: in a terminal, or headless under the
Switchboard. Vyre never imitates Claude Code; every surface (the terminal, Chat, the Vyre app, the
Lumen, the phone) drives the same real sessions.

- **The assistant.** Every install has one, made at onboarding. It is yours: its name, voice,
  instructions and skills are configurable. It can see every project and every session, and it
  can start, drive, monitor and stop any session in any project, or outside one, through the
  `threads.*` and `agents.*` tools. It is who you talk to in Lumen by default.
- **Agents.** Others you create, e.g. a research agent or a bookkeeping agent. Each is headless,
  runs on your Claude subscription (a setup token in the Vault) or an API key with a budget,
  and can draw context from several projects, never from projects outside its list. When a
  subscription's limit is reached it falls back to the API key if one is allowed, and says so
  in its thread. Each can have its own computer (section 7.9).
- **Talking to them.** From Lumen, the Vyre app, Chat or the terminal you can talk to the
  assistant, to any agent directly, or to any session directly. Talking to a session types
  into it (one keyboard at a time, floor rule 4).

An agent is a record in `core/agents` (owned by the switchboard workstream):

```json
{ "name": "juno", "kind": "assistant", "auth": { "vault": "claude-setup-token", "fallback": "anthropic-api-key", "budget_usd": 20 },
  "projects": "*", "instructions": "...", "skills": ["write-a-watcher"], "computer": false }
```

Tools: `agents.list`, `agents.create`, `agents.update`, `agents.ask {agent, text}` (to its
current thread, starting one if needed), `agents.threads {agent}`, `agents.stop`.

### The `vyre` home

`vyre` with no arguments, from any folder, opens the home: every project (with thread count and
last activity), **New session without a project**, and every agent (with what it is doing).
Pick a project to see its sessions, newest first: resume one or start a new one in it. Pick an
agent to talk to it. Inside a project's folder, that project is preselected, not opened. It is
an arrow-key list with type-to-filter in a terminal and a plain list when piped. Over SSH it
works exactly the same.

---

## 11. The security floor

Enforced outside the model, in the Rules and the Gate. None can be switched off.

1. Nothing goes out as the user until the user has seen the final words.
2. The user always sees where something is going before it goes.
3. A thread is one thing wherever it is viewed.
4. One screen types into a thread at a time.
5. Every file change is visible, including changes a command made without saying so.
6. Only an explicit question from an agent asks for the user's attention.
7. Anything Vyre tells the user, it can show the source of.
8. No value from the Vault appears on any screen, log or event, except to a person who has just
   proved presence on their own device, for that one value ([ADR 0004](../adr/0004-presence.md),
   [ADR 0006](../adr/0006-vault-next.md)). Never to a model,
   an agent, a log or an event.
9. Lumen works offline for the user's own Mac.

---

## 12. Milestones

| | Milestone | Done when |
|---|---|---|
| **M0** | Skeleton | `npm install -g .` then `vyre status` shows vyred running; module loader, store, events, config, API, test runner, CI. |
| **M1** | Projects and memory | Projects, catalogue, recall, curator and brief ported with their tests; `vyre` works as it does today, on anyone's machine. |
| **M2** | Harness | The plugin loads with `--plugin-dir`; Brief, Rules and the MCP server work in a real Claude Code session. |
| **M3** | Vault | Put, grant, fetch, revoke, offboard; relayed passes between two machines. |
| **M4** | Watchers | The runtime plus the write-a-watcher skill; one watcher written by Claude, running, filing into a project. |
| **M5** | Box | `vyre up` on a Linux server; paired over the built-in network; `<you>.vyre.run` resolves privately with HTTPS. |
| **M6** | Switchboard and app | Headless threads streamed to the Vyre app; Now and Projects working. |
| **M7** | Lumen | Ported from the current Mac app onto vyred's API. |
| **M8** | Computers and Glass | An agent's desktop, live, with take-over. |
| **M9** | Chat, Gate, phone | Vyre Chat (projects, sessions, the terminal mirrored) on phone and computer; the Gate holding sends. |

---

## 13. Workstreams

After M0, M1 and M2 land on `main`, work splits into parallel workstreams. Each runs in its own
Claude Code session, in its own git worktree, on its own branch, touching only its own folders.
The contracts in sections 5 to 8 are the boundaries: a workstream may use another's tools only
through `ctx` or the API, never by importing its files.

| Workstream | Owns | Depends on | Milestone |
|---|---|---|---|
| vault | `core/vault/` | store, events | M3 |
| watchers | `core/watchers/`, `harness/skills/write-a-watcher/` | vault (through `ctx.vault`) | M4 |
| box | `core/names/`, `vyre up` | daemon | M5 |
| switchboard | `core/switchboard/`, `core/agents/` | projects, events | M6 |
| learning | `core/learn/` | harness, memory | M6 |
| app | `apps/app` | the API only | M6 |
| capsule | `local/capsule/`, `local/hands-mac/` | the API only | M7 |
| computers | `core/computers/`, `modules/hands-desktop/`, `modules/hands-chrome/` | switchboard | M8 |
| gate + chat | `core/gate/`, `apps/app/src/chat/` | switchboard, vault, app | M9 |

How to start them, and the order (wave 1 now, wave 2 after the switchboard and vault merge), is
in `team/archive/work-journals/LAUNCH.md`.

Each workstream keeps `team/archive/work-journals/<stream>.md` current: what is done, what is next, what it needs
from others. A workstream merges to `main` only with its tests passing and the full suite green.

---

## 14. Engineering rules

- **Tests beside the code**, `node --test`. A bug fix comes with the test that would have caught
  it. Tests never touch the user's real `~/.vyre` or real transcripts; they get a temp
  `VYRE_HOME`.
- **Real data before merge.** Anything that talks to Claude Code or a network is
  exercised for real once before it merges, not only against a mock.
- **CHANGELOG.md** is updated with every change, in plain sentences that say why.
- **Commits** are conventional (`feat:`, `fix:`, `refactor:`, `docs:`, `test:`), one concern each,
  committed by path so parallel sessions never sweep up each other's files.
- **No secrets, no personal data** in code, tests, fixtures or commits.
- **ADRs** in `docs/adr/` for any decision a future contributor would ask about.
