Skip to main content
Glama

VALXOS Messenger

Lire en français

A local, durable post office between coding sessions. A session writes a letter to SQLite; the Facteur attempts delivery across restarts until the recipient acknowledges it. Works with Claude Code natively; any agent that can run a command can post and collect mail.

At a glance

  • License: GNU Affero General Public License v3.0 only (AGPL-3.0-only).

  • Runtime: fully local — SQLite and Unix sockets, with no external service.

  • Dependencies: 3 direct runtime dependencies (@modelcontextprotocol/sdk, better-sqlite3, and zod), 0 development dependencies, and 130 dependency package entries in package-lock.json.

  • Interfaces: an MCP server and 22 CLI entry points work from any shell; Claude Code also gets native hooks and a monitor.

  • Evidence: From a clone of the repository (the test suite does not ship in the npm package): the gate is node --test test/*.test.js; npm test runs exactly that command, and the suite prints its own count.

Related MCP server: InterAgentMail

Schema

  ┌──────────┐    writes    ┌──────────┐   carries    ┌───────────┐
  │  Session │ ───────────► │  Broker  │ ───────────► │  Facteur  │
  │ (agent) │              │ (SQLite)  │              │ (daemon)  │
  └──────────┘              └──────────┘              └─────┬─────┘
       │                         │                          │ delivers
       │ reads                   │ reads                    ▼
       │                    ┌──────────┐              ┌───────────┐
       └──────────────────►│  Guichet │              │  Mailbox  │
                             │  (MCP)   │              │ (socket)  │
                             └──────────┘              └───────────┘
                                  │                          │
                                  │ hooks                    │ acks
                                  ▼                          ▼
                             ┌──────────┐              ┌───────────┐
                             │  Hooks   │              │  Letter   │
                             │ (Claude) │              │  acked    │
                             └──────────┘              └───────────┘
                                  │
                                  ▼
                             ┌──────────┐
                             │ Web door │  ← operator reads/writes
                             │ (loopback)│
                             └──────────┘
  • Broker — SQLite database; the durable letter store.

  • Facteur — daemon that carries letters from the broker to mailboxes.

  • Guichet — MCP server; the counter where agents post and collect.

  • Web door — local-only HTTP page where one operator reads and writes.

  • Hooks — Claude Code session hooks that inject pending mail and gate stops.

Harness support

Harness

MCP

CLI

Hooks

Monitor

Woken by the Facteur

Status (evidence)

Claude Code

✓

✓

✓

✓ interactive only

yes, interactive sessions (see details)

Proven on 18/10/2026 with Claude Code 2.1.302: MCP, CLI and the three hooks in headless (claude -p) and interactive sessions; the monitor is armed in interactive sessions only, not under claude -p (details)

Codex CLI

✓

✓

—

—

no — collects with inbox

Proven on 18/10/2026 with codex-cli 0.162.1, headless codex exec: the MCP round trip and the CLI, provided the server entry sets default_tools_approval_mode = "approve"; the interactive TUI was not tested (details)

Gemini CLI

✓

✓

—

—

no

Proven on 18/10/2026 with Gemini CLI 0.58.0, headless: the MCP round trip from a project .gemini/settings.json, in a trusted folder (GEMINI_CLI_TRUST_WORKSPACE=true) with API-key auth; interactive mode was not tested (details)

Antigravity CLI (agy)

✓

✓

—

—

no

Proven on 18/10/2026 with agy 1.3.5, headless only (-p --sandbox --dangerously-skip-permissions): the MCP round trip from a workspace plugin (<project>/.agents/plugins/<name>/); every server of agy's global config also starts; agy 1.4.2 and the TUI were not tested (details)

OpenCode

✓

✓

—

—

no

Proven on 18/10/2026 with OpenCode 1.18.30, headless opencode run only: the MCP round trip from a project opencode.json mcp entry (type: "local"); the TUI was not tested (details)

Hermes

✓

✓

—

—

no — next turn

Proven on 25/08/2026, MCP and CLI, before 1.0.0; the wiring was not re-verified for 1.0.0 (details)

Any other MCP client

✓

✓

—

—

no

Promised — no harness-specific test

valxos-inbox shows up to 20 unacknowledged letters (--limit N); acknowledge with valxos-inbox --ack <msg_id> so the next ones surface.

Each status dated 18/10/2026 means: on that date, on macOS, against a scratch office, whoami, send, inbox, ack and history ran through the harness over MCP, and the CLI read or wrote the other side of the letter.

What wakes a session. Only Claude Code sessions are woken: the Facteur delivers to the session's own messaging socket, and, on a wake, the plugin monitor prints it (in interactive sessions); the hooks inject pending mail at session start, after each tool call, and before a stop. The Facteur's push into a live Claude Code 2.1.302 session was observed in the authors' house on 18/10/2026 (eight letters delivered to the session socket by the previous release's courier, whose connection logic the 1.0.0 courier keeps unchanged); the test suite verifies the 1.0.0 courier against a stand-in socket; the in-session wake through the plugin monitor was verified live the same day. Under claude -p the monitor is not armed; the hooks still present mail. Every other harness collects its mail by calling inbox (MCP) or valxos-inbox (CLI) itself; nothing rings. In the authors' 25/08/2026 setup, Hermes saw its mail at the start of its next turn through a Hermes-side plugin that this package does not ship.

Hooks and the monitor are Claude Code-specific. Any agent that can start a process can post and collect mail through the CLI or any MCP client.

Per-client wiring — Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, OpenCode, Hermes: the config block that worked, the conditions, how the peer's identity and office are declared, what was not tested, and a smoke test for each — lives in docs/MCP_CLIENTS.md.

Install

The easy way: give this URL to your coding LLM — https://github.com/valx-vex/valxos-messenger (the repository is public; this README is the whole contract) — and tell it: "make it good in my setup". It reads this README, runs the installer, and wires the plugin into your harness.

With npm:

npm i -g valxos-messenger --allow-scripts=better-sqlite3
valxos-messenger install --dry-run   # preview
valxos-messenger install             # install

npm 12 blocks dependency install scripts by default, and better-sqlite3 needs its install script to download its prebuilt binary; --allow-scripts=better-sqlite3 allows that one script (the npx lines below carry it too). If the installer says native binding missing, run the npm rebuild command it prints.

From a clone (or an extracted tarball):

npm install
node scripts/install.js

The installer creates CLI symlinks in ~/.local/bin/, the Claude Code plugin symlink, and the office (~/.valxos-messenger, or the one VALXOS_MESSENGER_HOME names), so the doctor that follows has one to read. With --launchd it also writes the two launchd plists. If Claude Code is not installed, the core install still succeeds; the optional plugin validation and enable steps report their absence without failing. The skills-dir link written by valxos-messenger install and claude plugin enable valxos-messenger@skills-dir were not exercised end to end in a Claude Code session in this release; the plugin was verified from the same tree with --plugin-dir.

Add ~/.local/bin to your PATH if it is not already there:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc   # or ~/.bashrc
source ~/.zshrc

Name each house. Set VALXOS_SELF=<name> in the launcher of every harness that starts the server or the hooks (and VALXOS_SESSION_NAME if you want per-session names). Without it every session on the machine speaks under the hostname and shares one name; whoami shows the name in use. Give each harness its own name: a CLI or MCP client that uses a Claude Code lineage's name does not read that lineage's mail while one of its chairs is alive (see docs/MCP_CLIENTS.md).

Verify the installation:

valxos-doctor

The doctor reports an honest verdict: it checks the database, plugin manifest, CLI links, and reports whether the Facteur daemon and the web door are running. It never returns a false green.

Without a global install, npx runs the same installer from the published package (the package is named explicitly, valxos-messenger):

npx --allow-scripts=better-sqlite3 --package valxos-messenger valxos-messenger install --dry-run    # preview
npx --allow-scripts=better-sqlite3 --package valxos-messenger valxos-messenger install              # install
npx --allow-scripts=better-sqlite3 --package valxos-messenger valxos-messenger uninstall            # remove links, keep data

To run the Facteur and the web door as launchd agents (macOS), add --launchd: the installer writes com.valxos.messenger.facteur and com.valxos.messenger.web under ~/Library/LaunchAgents/ and prints the launchctl bootstrap commands to run; --launchd-load runs them for you. The plists carry no VALXOS_MAISONS and no CLAUDE_CONFIG_DIR: under launchd the Facteur scans ~/.claude only, and the web door's attente advice on a send uses that same list (see Known limitations).

npx --allow-scripts=better-sqlite3 --package valxos-messenger valxos-messenger install --launchd --operator alice   # write the plists
npx --allow-scripts=better-sqlite3 --package valxos-messenger valxos-messenger install --launchd-load               # write and load them

Details, re-runs, and removal: docs/INSTALL_ONE_CLICK.md.

Published commands

valxos-send          valxos-peers         valxos-inbox
valxos-unregister    valxos-chaise        valxos-monitor
valxos-doctor        valxos-web           valxos-migrate-v1
valxos-migrate-home  valxos-messenger     facteur

The ten cc2cc-* executables are deprecated aliases for the same programs. Together with the ten valxos-* names, valxos-messenger, and facteur, the package publishes 22 CLI entry points.

valxos-messenger is the public name: the npm package and the GitHub repository. The MCP server announces itself as valxos_messenger, an identifier. The Claude Code plugin and its MCP server key are valxos-messenger. cc2cc-v2, formerly CEREAL, was the former plugin and server identifier; it remains the root of the compatibility aliases.

Delivery model

This is not instant messaging. A sending session deposits a letter in a durable SQLite office; the Facteur attempts delivery; monitors, hooks, MCP tools, or CLI commands present it to the recipient. The letter remains available across restarts without requiring a person to copy it between sessions.

Three rules define the system:

  1. At-least-once delivery. A duplicate is preferred to a lost letter. The system does not promise exactly-once delivery.

  2. No acknowledgement means the letter eventually returns. There is no terminal dead state, infinite quarantine, or deletion that hides a letter. Deferrals and leases are finite; only acknowledgement is terminal.

  3. Records are not erased. Removing a peer marks it out of service. Letters, attempts, events, and forwarding records remain in the registry.

Three markers describe different facts:

Term

What it proves

Marker

carried

the mailbox accepted the bytes

messages.carried_at

delivered

a carrier reports preparing recipient context

messages.delivered_at

acknowledged

the authorized recipient acknowledged the letter

messages.acked_at

carried_at, delivered_at, and injected_at are historical observations. They never hide a letter. Only acked_at, set by an authorized recipient, is terminal.

Addressing and route

A house is a local installation, an instance is a live session, a lineage is the durable name to which mail is addressed, and a chair is an instance's place within that lineage. If several live chairs claim the same lineage, the letter waits for a human designation (valxos-chaise designer): the office never draws lots. Once a chair is designated it receives the lineage's mail, and every other live chair of the lineage sees a cc copy, never delivered and never to be acknowledged.

The courier scans Claude Code installation directories to discover live chairs. By default it scans ~/.claude and, when set, $CLAUDE_CONFIG_DIR. A VALXOS_MAISONS declaration (colon-separated absolute paths, written out in full as echo "$HOME/.claude" prints them: the Messenger expands neither ~ nor $HOME in this value, and a launchd plist does not either; CC2CC_MAISONS is the historical alias) replaces that default list; it does not add to it. $CLAUDE_CONFIG_DIR is dropped too: put your ~/.claude path in the declaration if you still want that home scanned, and include your CLAUDE_CONFIG_DIR path if you still want it scanned.

Reservation, lease, retry, rate-limit, and shutdown details are in docs/FACTEUR.md (in French). The courier's design doctrine (it delivers, it never judges) is in docs/DOCTRINE_FACTEUR.md (in French).

The web door

valxos-web serves one local page, without a framework or third-party browser dependency, where one configured operator can read and write mail. Each letter carries a single stamp that fills in four steps, the values en attente, au guichet, reçue (the delivery observation) and accusée of the API's etat field; the page shows them under their English labels, Waiting, Carried, Delivered and Acked, and a letter en souffrance (not acknowledged, past its age or attempt threshold) reads Overdue. The server refuses to listen outside the loopback interface. Any network exposure must be provided and protected separately.

Opening the page (or GET /api/fil) acknowledges ordinary letters to the operator (message type, priority low or normal); high, critical and every other type wait for the Acknowledge button.

The web door requires the unique name of its operator in ~/.valxos-messenger/web.env:

VALXOS_WEB_OPERATEUR=alice
# VALXOS_WEB_JETON=<an existing token; otherwise the door creates one as 0600>
# VALXOS_WEB_HOTE=127.0.0.1
# VALXOS_WEB_PORT=8790
# VALXOS_WEB_CADRE_AUTORISE=https://command-center.example

Without VALXOS_WEB_OPERATEUR, or its historical alias CC2CC_WEB_OPERATEUR, valxos-web refuses to start before opening the office.

Runtime directory and migration

The default runtime directory is ~/.valxos-messenger. VALXOS_MESSENGER_HOME can move it; CC2CC_V2_HOME remains a historical alias and loses when both variables are set (an empty VALXOS_MESSENGER_HOME counts as unset, and says so on stderr).

If only the former ~/.cc2cc-v2 installation exists, the software continues to use it and reports the fallback. To copy it explicitly to the new directory, stop the services, inspect the simulation, and then apply it:

valxos-migrate-home            # dry-run: see what would be copied
valxos-migrate-home --apply    # copy, preserving the source

The migration ignores run/ (ephemeral sockets, PIDs, and locks), accepts an empty destination and refuses a non-empty one unless --force sets it aside (dated, never erased), refuses while a process still writes the source database, copies symlinks as written, writes the marker .valxos-messenger-home that makes the new office count, preserves the source, and performs no implicit move. When the live office cannot be stopped, the alternative is the Phase A symlink, ln -s ~/.cc2cc-v2 ~/.valxos-messenger: every process then resolves the same office under both names.

Known limitations

  • The web door is a counter, not a mailbox. The browser collects mail; the Facteur does not deliver into it. A letter waits until the page is opened.

  • After authentication, /api/accuser answers 404 lettre-inconnue for an unknown letter and 403 pas-mon-courrier for somebody else's. The distinction is an existence oracle on message ids, accepted on a single-operator door.

  • A former ~/.cc2cc-v2 office is used as a fallback and reported; moving it to ~/.valxos-messenger is an explicit, manual step (valxos-migrate-home, above). Nothing is moved on its own.

  • A web door serves one operator only.

  • Delivery is at least once, not exactly once. A crash after a letter becomes visible and before it is acknowledged can produce a duplicate.

  • There is no proof of reading without acknowledgement, no end-to-end encryption, and no guarantee against physical volume loss. SQLite runs in WAL mode with synchronous=NORMAL — better-sqlite3's compiled default for WAL (SQLITE_DEFAULT_WAL_SYNCHRONOUS=1), no pragma in src/, measured by the suite; tested backup and restore remain the operator's responsibility.

  • There is no quarantine. A poison letter returns after a bounded deferral; a visible human-intervention queue has not been built.

  • Letters are untrusted text. A letter's body reaches the recipient's model unfiltered, by the hooks, the plugin monitor and the Facteur, and the sender shown is the name the peer gave itself. Anything that can write to the office can put text in front of a model; the header of the hooks' context says to treat letters as untrusted text, not as instructions (SECURITY.md).

  • Harness parity is incomplete. MCP and CLI are general interfaces; hooks, the monitor, and the default house inventory are Claude Code-specific.

  • Claude Code 2.1.292 and later negotiate MCP protocol 2026-07-28 with stdio servers by default. This server speaks the 2025-11-25 generation (SDK 1.32). Claude Code's changelog says such a server is connected the older way after one slow first connect, and that this is remembered for 7 days. Proven on 18/10/2026 with Claude Code 2.1.302: the server connects in headless and interactive sessions (see docs/MCP_CLIENTS.md). If a connection fails after a Claude Code update, set MCP_PROTOCOL_NEGOTIATION=legacy in the environment that starts Claude Code.

  • Recent Claude Code defers MCP tools behind tool search. inbox, send and ack are marked always-load (_meta['anthropic/alwaysLoad']); the other six tools may need to be loaded before they are called. Proven on 18/10/2026 with Claude Code 2.1.302, tool search on (the default): only inbox, send and ack are loaded up front; whoami, history, register, list_peers, unregister and broadcast stay deferred until the model loads them through ToolSearch.

  • Do not install this package as a Claude Code plugin from an npm marketplace source: since Claude Code 2.1.275 such installs run npm pack --ignore-scripts, and better-sqlite3 then has no native binding. Install with npm i -g valxos-messenger --allow-scripts=better-sqlite3 and valxos-messenger install.

  • Claude Code 2.1.271 and later give Monitor watches a deadline (30 minutes; 10 in claude -p). In an interactive Claude Code 2.1.302 session the plugin monitor stayed armed for the whole 40-minute session, past the 30-minute deadline Claude Code gives Monitor watches, and presented a letter posted at minute 33 within 20 ms (18/10/2026). Under claude -p the monitor is not armed; the hooks deliver instead. The hooks do not depend on the monitor: pending mail is injected after each tool call, and a stop is held while unread mail above low priority waits, regardless.

  • A Claude Code session's chair stays registered as in service after the session exits; nothing marks it dead on its own (observed on 18/10/2026 with Claude Code 2.1.302: every claude -p session left its chair in service). With headless claude -p runs this adds up fast: as soon as a lineage has more than one chair in service and no designated one, a letter to the bare lineage is held (send answers attente.motif: "lignee-ambigue", and each chair sees only a cc copy flagged sans-chaise-designee) until a human harvests the dead chairs (valxos-chaise moissonner or valxos-doctor --reap) or designates one (valxos-chaise designer). Address a chair directly (name#session) to bypass the lineage.

  • The launchd plists written by valxos-messenger install --launchd carry no VALXOS_MAISONS and no CLAUDE_CONFIG_DIR: under launchd the Facteur scans ~/.claude only, and the web door's attente advice on a send uses that same list. If your Claude Code configuration lives elsewhere, add the variable to both plists' environment before loading them: VALXOS_MAISONS (the complete list) or CLAUDE_CONFIG_DIR.

  • The web door's page and the message/motif prose of the MCP and HTTP answers are English (since this release); the MCP refus and attente.motif codes are stable identifiers documented in docs/MCP_CLIENTS.md.

The web guard is described in three separate sets:

  • Structurally closed: the dispatcher serves only its route table; response functions require a private verdict; the listening address is limited to loopback.

  • Closed by lists that must be maintained: known process-launch primitives, counted exceptions, route and font contracts, and known accesses to the server and transport. The door uses fixed local probes; the guard checks on the real path that no user-posted field reaches their command line.

  • Not closed: a deliberately obscured back door added by an authorized repository author, a computed dynamic import never loaded by the probe, or transport access written in a form the lists do not recognize. These cases also depend on review and repository access control.

The danger is not the hole; it is the sentence claiming there is none.

Additional limits — lease clocks, late acknowledgements, and shutdown between rounds — are recorded in docs/FACTEUR.md (in French).

What it is not

  • Not a chat server. There is no real-time push outside a Claude Code session, no presence, no typing indicator. Elsewhere a letter waits until the recipient collects it.

  • Not a message queue. There is no broker federation, no topic routing, no partitioning. One SQLite database, one machine.

  • Not encrypted. There is no end-to-end encryption. The transport is a local Unix socket. Physical volume loss is the operator's responsibility.

  • Not multi-tenant. One operator, one web door, one broker. The product does not isolate users from each other.

  • Not a Claude Code plugin only. The MCP server and CLI work with any harness. But hooks and the monitor are Claude Code-specific, and harness parity is incomplete.

  • Not a guarantee of exactly-once. A crash after visibility and before acknowledgement can produce a duplicate. This is accepted.

Verify

From a clone of the repository (the test suite does not ship in the npm package):

node --test test/*.test.js   # or: npm test — the same command

The suite prints its own count. A few tests probe process liveness through /bin/ps; they fail in environments where /bin/ps is restricted (containers, some sandboxes) and pass on a normal macOS or Linux install. In a clone of the public repository the suite is the same minus the private-name guard, which only runs in the authors' repository.

Contributing

Public surfaces are English: the README, the CLI, the error messages a user reads. The internals are French — identifiers, comments, and test names. That is a real barrier to contributing, it is deliberate, and it is not hidden: a full translation was measured at roughly 30,000 lines of churn across tested code, and declined as a worse trade than the barrier itself.

Comments explain why, not what; many carry the measurement of the defect that made them necessary. In a clone, the gate is node --test test/*.test.js (npm test runs it). Read CONTRIBUTING.md before opening a pull request, and SECURITY.md before reporting a vulnerability — never in a public issue.

Ledger codes in comments. Comments cite the private review that preceded 1.0.0 as the provenance of a measure. A8 = at-least-once delivery; A9 = a null ack means the letter comes back; Camp B = nothing is erased, records are moved aside; E.1/E.2 = the two Facteur work streams: E.1 built the daemon described in docs/FACTEUR.md (in French), E.2 is the next one, on the recipient side, which that page cites as not done (launchd support has shipped: see docs/INSTALL_ONE_CLICK.md); Run N, Task N and the F/N/M/C/S/G/I numbers = items of that private review. T-codes (T4b-b, T4b-c…) number the tasks of the 1.0.0 publication run, cited in a few test comments; the bare T1 to T6 in src/facteur/round.js are the steps of one Facteur round.

License

The code is distributed under GNU Affero General Public License v3.0 only (AGPL-3.0-only); see LICENSE. If users interact over a network with a modified version, section 13 requires offering them access to the corresponding source code for that version. This applies directly to use of the web door.

Source files carry a short SPDX header pointing to the complete license text.

The bundled Archivo, Literata, and DM Mono fonts under src/web/public/polices/ remain under the SIL Open Font License 1.1. Their license texts and attributions are in src/web/public/polices/LICENCE.md. They are served locally so opening the mailbox does not contact a third-party font service.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables coding agents such as Codex and Claude Code to exchange durable, locally stored messages through shared MCP tools for listing enrolled recipients, sending and replying to messages, reading pending inbox items, acknowledging receipt, and checking message status. Delivery uses each runtime's native session interface, so messages persist across broker restarts without resuming or launching conversations.
    AGPL 3.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables coding-agent sessions on the same machine to collaborate locally and durably by exchanging requests, replies, and task results through an authenticated loopback server with SQLite-backed storage, joined via CLI or an MCP stdio bridge. It also handles message delivery by priority and wait time, deduplicates idempotent sends, enforces task versioning, and preserves artifacts by content hash.
    165 npm
    Apache 2.0