Concepts
Product boundaries, private IPC and gateway processes, legacy ports, and command destinations
This page explains which product owns each capability, which processes run, and how clients connect. It also covers Lattices sessions, panes, layers and the agent API.
Lattices and the product family
The products share a repository, website and skills catalog. Each product ships its own app and keeps its own lifecycle. Quitting one app does not stop another.
| Product | What it owns | Runtime | Docs |
|---|---|---|---|
| Lattices | Windows, tiling, spaces, workspace layers, tmux sessions, screen text, voice commands, the lats CLI and the lats mcp server |
macOS menu bar app; on Linux, lattices-host plus a Quickshell app |
/lattices/docs/overview |
| Action | Native observe, resolve, act and record; private agent layers; Action-owned browsers | Action.app and its local agent |
/action/docs/overview |
| Canvas | Spatial notes: each note is a floating panel backed by a Markdown file | Canvas.app and the canvas CLI |
/canvas/docs/overview |
| Voice | Spoken output: synthesis, the playback queue and the playback HUD | Voice.app (bundle ID dev.lattices.Speech) |
/voice/docs/overview |
Canvas 2.3.0 ships Canvas.app and the canvas CLI. Blink.app and blink
remain compatibility names. Existing storage, bundle identity and blink.sock
remain unchanged. The rename does not implement the older shared-canvas API proposal. See Canvas installation.
The product map shows the same family for people choosing what to install. The rest of this page describes processes, not products.
Shared entry points
lats and lats mcp are Lattices interfaces. Some of their commands reach
another product:
lats action ...installs, launches and calls Action.lats voice say ...asks the Lattices daemon to forward speech to Voice.app.lats mcpserves thebrowser_*tools, which are Action Browser code that ships inside thelatticesnpm package.
These commands are routing. They do not move ownership into Lattices. The destination table below names the process that does the work.
Processes and ports
Lattices 0.15.1, Action 0.4.0 and Voice 0.3.0 use private IPC for local APIs. Remote access uses a separate, opt-in gateway on the verified Tailscale address, port 9399 by default. Starting a product does not start that gateway.
Install matching app and CLI versions. Earlier releases use the legacy ports; their TCP clients must be upgraded.
macOS
Remote gateway Separate process, started explicitly
Tailscale identity, signed requests, and approved product/method grants
- Remote RPC and companion relay
- Tailscale
:9399 - Local gateway control
gateway.sock
Approved remote calls reach product sockets below. Local clients connect directly to those sockets.
Lattices.app
Workspace, windows, layers, and sessions
- Agent API
lattices.sock- Voice input, local only
voice.sock- Companion bridge
companion.sock
lats and local scripts use private IPC. Remote companion clients use the gateway relay and their separate device pairing.
voice.* forwards playback requestsVoice.app Voice
Synthesis, playback queue, and readalong
- Authenticated Voice API
speech.sock
Also accepts direct speech.* clients
Action.app + helper processes
Observe, resolve, act, and record
- ActionAgent.app
action.sock- Chrome bridge
action-chrome.sock- Job service, in Action support
runtime/companion.sock
lats action calls the agent directly. The Chrome native messaging host connects to the Chrome bridge. Recording probes and the durable job service keep separate processes.
Canvas.app Spatial notes
Note panels and named desks
- Local interfaces
- Files +
blink.sock
canvas CLI, note files, socket clients
The default socket directory is ~/.lattices/run, including Action's Chrome
bridge. Canvas and Action's job service keep their sockets in their own
application-support directories.
| Endpoint | Process | Owner | Replaces in earlier releases |
|---|---|---|---|
lattices.sock |
DaemonServer in the menu bar app |
Lattices | Local agent API on 9399 |
voice.sock |
Embedded voice input runtime (Vox, through Hudson Voice) | Lattices | Voice runtime on 9398 |
companion.sock |
Companion bridge in the menu bar app | Lattices | Companion listener on 5287 and its Bonjour advertisement |
action.sock |
Action local agent | Action | Agent API on 4319 |
action-chrome.sock |
Chrome extension bridge | Action | Chrome companion HTTP/WebSocket listener on 4321 |
speech.sock |
Voice.app RPC server | Voice | Voice API on 9397 |
blink.sock in CANVAS_HOME (then BLINK_HOME, then the existing default) |
Canvas.app | Canvas | Unchanged private socket |
runtime/companion.sock in Action's application support |
Durable job service: jobs, artifacts and timeline | Action | Existing Unix socket; no fixed TCP port |
gateway.sock |
Separate gateway process, local control | Lattices | New endpoint |
| Tailscale address, TCP 9399 by default | Separate gateway process, remote RPC and companion relay | Lattices gateway | Direct remote connections to product runtimes |
The local APIs use filesystem permissions and authentication as described in Private IPC and the gateway. The embedded voice runtime is available only in builds that include Hudson Voice. Voice input has no remote gateway route.
The Chrome extension uses native messaging over stdio. Its native host connects
to the bridge through authenticated action-chrome.sock. Action's job service
uses its own runtime/companion.sock. Lattices' device companion bridge uses
~/.lattices/run/companion.sock for paired remote visits. These are three
separate services despite the shared companion name.
Private IPC does not remove every network listener. Action-owned Chrome still uses an intentional loopback Chrome DevTools Protocol (CDP) port, 9335 for the default workspace. That browser endpoint is separate from Action's agent API and from the gateway.
Linux
lats CLIlattices-host Bun service
Hyprland backend for windows, workspaces, and computer use
- Host API
lattices.sock- Companion bridge
companion.sock
Remote gateway Separate, opt-in process
- Tailscale network listener
:9399by default
Remote clients require Tailscale identity and pairing. Action RemoteEngine requests reach the host only with approved grants.
In 0.15.1, lattices-host is a Bun process with
lattices.sock and companion.sock in ~/.lattices/run. The Quickshell app's
backend and the tray use the local host. Remote requests enter through the
separate gateway. The Linux host has no TCP compatibility listener.
host.describe reports the host's platform, capabilities and methods.
Action.app, Canvas.app and Voice.app remain macOS apps. Action's RemoteEngine
can call the Linux host's computer.* methods through the gateway. See
Install on Linux and
Remote engine. The macOS rollout does not establish
Linux or physical iOS runtime verification.
Published-release ports
This table applies to Lattices 0.14.1, Action 0.3.1 and Voice 0.2.0. It is retained for upgrade reference. Binding and authorization are separate boundaries.
| Port | Process | Binding and authorization in earlier releases |
|---|---|---|
| 9399 | Lattices DaemonServer on macOS |
Loopback; core methods have no token check. Speaking verbs require the Voice capability token, which lats sends |
| 9398 | Embedded Lattices voice runtime | Wildcard listener; non-loopback peers rejected after accept. Requests require the token in ~/Library/Application Support/Lattices/Voice/hudson-voice-runtime.json |
| 9397 | Voice.app RPC server | Loopback; token from ~/Library/Application Support/Speech/RPC/capability in x-lattices-speech-token; browser origins refused |
| 4319 | Action local agent | Loopback; token required for attention-mode drive.begin, workspace.dragFile and workspace.cancelOperation. Other methods have no token check |
| 4321 | Action Chrome companion bridge | HTTP/WebSocket listener; the Chrome extension connected directly to the bridge |
| 5287 | Lattices companion bridge on macOS | All IPv4 interfaces with Bonjour discovery; human-approved pairing and signed requests |
| 9399 and 5287 | Linux lattices-host and companion bridge |
Local host calls use loopback; the host also listens on its tailnet address when available. Remote devices must pair |
| none | Blink.app local API | blink.sock in the BLINK_HOME directory, mode 0600 |
In Lattices 0.15.1, port 9399 belongs to the opt-in gateway by default. In versions before 0.15.1, it belongs to the host or daemon. Matching the port number alone does not make clients compatible.
Private IPC and the gateway
Versions: Lattices 0.15.1, Action 0.4.0 and Voice 0.3.0 use private IPC. The process table lists the default endpoints. Local clients connect directly to each product. Remote clients use the gateway and its approved method grants.
Local services use private Unix sockets. The main Lattices, Action and
Voice APIs use ~/.lattices/run; Canvas keeps its socket in its own data directory,
~/Library/Application Support/Blink by default. A nonempty CANVAS_HOME
overrides that path, followed by a nonempty BLINK_HOME.
Socket directories are mode 0700, and sockets are mode 0600. Services in ~/.lattices/run
write a random capability token beside the socket, also mode 0600.
WebSocket services require the token in x-lattices-token during the upgrade.
The companion bridge requires that header on its HTTP requests too. These
services refuse browser Origin headers and unexpected Host headers. The
macOS core, companion and Voice servers also check the connected peer's user
ID. Action and the Bun services rely on the private directory and token.
Canvas uses a peer-user-ID check and private filesystem permissions without a
token. This is an account boundary, not isolation between programs running as
the same user.
The product clients find their sockets and credentials. For example, lats
and daemonCall() find the Lattices socket and token. A raw WebSocket client
must read the service's token file, connect to its socket, and send
Host: localhost with the token. The token is a local capability: the shared
client sends it over private IPC by default. An explicit TCP target, even a
loopback address that could be an SSH tunnel, receives no local token unless
loopback compatibility is explicitly enabled. For the CLI and Pi client, that
requires LATTICES_ALLOW_LOOPBACK_TCP=1. Non-loopback targets never receive
local tokens. Canvas keeps its newline JSON protocol and needs no HTTP headers
or token.
LATTICES_RUNTIME_DIR moves the runtime directory for every participating
process. Use it for isolated tests, not for normal operation.
Loopback TCP is a compatibility switch, not a default. These macOS runtimes can open a loopback port when explicitly configured. The Linux host has no TCP compatibility listener.
| Product | Variable | Port | Token still required |
|---|---|---|---|
| Lattices on macOS | LATTICES_ALLOW_LOOPBACK_TCP=1 |
9399 | Yes |
| Voice | LATTICES_SPEECH_TCP=1 |
9397 | Yes |
| Action | ACTION_ALLOW_TCP=1 |
4319 | Yes |
Each product keeps its own lifecycle. Launching or quitting Lattices, Action, Canvas or Voice.app does not start, stop or restart another product, and it does not start the gateway. The CLI connects to whichever runtimes are running and reports the others as unavailable.
Remote RPC and companion access use the gateway, and only on request. The gateway is a separate per-user process. Start it explicitly:
lats gateway start --tailnet # background, until `lats gateway stop`
lats gateway statusIt does not install a login item or boot service. It requires Tailscale and
binds only this machine's verified Tailscale address, on port 9399 by default.
There is no LAN, wildcard or unauthenticated fallback. It admits devices owned
by this machine's Tailscale user unless you set --allow-user or
--allow-tag.
A remote client pairs, and a person on this machine approves it:
# On the remote machine
lats --host HOST pair --read-only
# Or request exactly these methods instead of the read-only scope:
lats --host HOST pair --grant action:status --grant speech:speech.enqueue
# On this machine: check the fingerprint and grants, then approve
lats gateway clients
lats gateway approve CLIENT_ID
lats gateway revoke CLIENT_IDEvery stored grant names one exact product:method. --read-only requests
the reviewed core Lattices read methods, which exclude screen contents. Add
--screen for screenshots, streams, OCR and terminal contents, or --shell
for typing and launching commands. Plain pair covers read, screen and act;
--drive adds desktop input and shell. Clients paired earlier keep their
exact grants. Repeated --grant flags request
exactly the listed methods, from any product, instead of a scope. You cannot
combine them with --read-only or --drive. A request for a new capability
needs another local approval. Re-pairing with the same client key adds approved
grants to the existing set. To reduce access, revoke the client and pair again.
Only methods on the gateway's reviewed list can be granted; the gateway rejects
unknown methods and never lets a remote client choose a socket path or call
local administration. If a product is not running, the gateway reports it as
unavailable. It does not start it.
These stay local and the gateway does not forward them:
- raw computer action events and Canvas event streams;
- Action's connection-owned drive leases and workspace drag/cancel operations;
- Voice's native playback reservation and event streams;
- the embedded voice-input runtime and core
voice.*forwarding; - the VNC
capture.livelistener. The gateway exposes no VNC.
Remote visits use the authenticated companion relay through the gateway port. Companion device trust and gateway client trust are separate stores, and existing direct-host RPC pairings must pair again with the gateway. Companion device pairings remain in the companion's own store. The native Machines/Hosts view reuses the CLI gateway pairing; a companion pairing alone does not unlock remote host status or screens. Canvas's paired iOS note sync remains a separate Multipeer Connectivity service.
Where commands go
This table names the destination in the current versions. Earlier releases have the same product ownership but use the older transports.
| You run | Process that receives it | Method family | Process that does the work |
|---|---|---|---|
lats tile, lats place, lats search, lats layer, lats call ... |
Lattices daemon via lattices.sock |
windows.*, spaces.*, layers.*, ... |
Lattices |
lats start, lats sync |
tmux, directly | n/a | tmux, configured by Lattices |
lats computer ... |
Lattices daemon via lattices.sock |
computer.* |
Lattices |
lats voice listen, stopListening, simulate, intents |
Lattices daemon via lattices.sock |
voice.* |
Lattices; transcription uses the embedded runtime via voice.sock |
lats voice say, stop, pause, resume, skip, seek, list, select |
Lattices daemon via lattices.sock |
voice.* |
Voice.app via speech.sock, as speech.* |
lats action call <method> |
Action agent via action.sock |
Action methods | Action |
lats action <verb> (other verbs) |
Action CLI from a runtime bundle or checkout | n/a | Action |
lats mcp tools browser_* |
lats mcp stdio process |
n/a | Action Browser code in the lattices package, using Action-owned Chrome profiles |
lats mcp tools host_* |
Local Lattices runtime via lattices.sock, or the gateway for a configured remote host |
daemon protocol | Lattices on the selected host |
lats --host <name> ... |
Separate gateway on the verified Tailscale address, port 9399 by default | Signed requests with approved method grants | The selected product runtime via its private socket |
canvas ... (or blink ...) |
Note files on disk; canvas desk also uses blink.sock |
File operations and newline-delimited JSON | Canvas.app watches the same files |
Current mismatches
Some interfaces cross or overlap the product boundaries. Read these before choosing an interface.
lats computeris Lattices, not Action. Thecomputer.*methods run in the Lattices daemon (see thecomputer.*entries in the API reference). Action is the separate computer-use product with its own agent onaction.sock.- Action reaches Linux through Lattices. Action's
RemoteEnginedrives a Linux machine by callingcomputer.*on that machine'slattices-host. Current versions reach it through the gateway and approved grants. The native Action app remains on macOS. See Remote engine. lats voicehas two destinations. Listening verbs stay in Lattices. Speaking verbs are a bridge: the daemon renames each call (for examplevoice.saybecomesspeech.enqueue) and forwards it to Voice.app throughspeech.sock. If Voice.app is not running, the speaking verbs fail. There is no fallback inside Lattices. See Voice and Voice.
The Hands-Off worker is a separate case. It speaks its replies through the
embedded voice runtime on voice.sock. It does not use Voice.app.
Glossary
| Term | Definition |
|---|---|
| Command Bar | The menu bar app's primary interface (Control+Option+Space by default). Search and run actions for apps, windows, workspaces, and settings. |
| Workspace layer | A named group of windows that you switch as one unit with ⌃⌥⇧ arrows or lats layer. See Layers. Not the same as an Action private layer. |
| Window Tiling | Snap terminal windows to preset screen positions (halves, quarters, thirds, maximize, center). Works from the CLI (lats tile) or the command bar. |
| Agent API | Lattices RPC methods and events hosted by the menu bar app on macOS or lattices-host on Linux. Current versions use authenticated WebSocket over lattices.sock; versions before 0.15.1 use TCP 9399. See the API reference. |
| Private IPC | Local communication over a Unix socket owned by the current user. Product APIs authenticate local clients before accepting requests; the gateway is not involved. |
| Gateway | Separate, opt-in process for remote access. It verifies Tailscale identity and signed requests, checks approved product/method grants, and connects to the product through private IPC. |
| Agent | Any program that calls the agent API autonomously: an AI coding agent, a shell script, a CI pipeline, or a custom tool. |
| Session | A persistent tmux workspace that lives in the background. Survives terminal crashes, disconnects, and closing your laptop. One session per project. Requires tmux. |
| Pane | A single terminal view inside a session. A typical setup has two panes side by side: shell on the left, dev server on the right. Requires tmux. |
| Attach / Detach | Attaching connects your terminal to an existing session. Detaching disconnects but keeps the session alive. Your shell and dev server keep running. Requires tmux. |
| Sync / Reconcile | lats sync brings a running session back in line with its declared config. It recreates missing panes, re-applies layout, restores labels, re-runs commands in idle panes. Requires tmux. |
| Ensure / Prefill | Two modes for restoring exited commands on reattach. Ensure auto-reruns the command. Prefill types it but waits for you to press Enter. Set via .lattices.json. Requires tmux. |
| tmux | Terminal multiplexer (optional). Provides persistent sessions, pane layouts, and command restoration. Install with brew install tmux on macOS if you want session management. |
How it works
- You create a
.lattices.jsonfile in your project root (or runlats init) - The menu bar app discovers the project and adds it to the command bar
- You can tile windows, switch layers, search via OCR, and use the agent API
- With tmux installed,
lats startalso creates persistent terminal sessions:- Each pane gets its command (shell, dev server, tests, etc.)
- The session persists in the background until you kill it
- You can attach/detach from any terminal at any time
- If
ensureis enabled, exited commands auto-restart on reattach
Core architecture
- The menu bar app is the core on macOS. It provides the command bar,
window tiling, OCR, project discovery and the agent API. Versions from 0.15.1 expose
that API through
lattices.sock; versions before 0.15.1 use TCP 9399. The app works with or without tmux. - The
latsCLI calls the agent API for windows, layers, search and OCR. It runstmuxcommands directly for session management. - Local agents and scripts connect to the agent API over WebSocket, using private IPC in current versions. Remote access goes through the gateway and is limited to approved methods. The API lets agents discover projects, tile windows, switch layers, read on-screen text and subscribe to real-time events. See the API reference.
- The app and the CLI share one session naming convention, so they always agree on which session belongs to which project.
Session naming
A session name is <basename>-<hash>, where:
basenameis the project directory name (non-alphanumeric chars replaced with-)hashis the first 6 hex chars of the SHA-256 of the full absolute path
This guarantees unique session names even if two projects share the
same directory name (e.g. ~/work/app and ~/personal/app).
Both the CLI (Node.js crypto.createHash) and the app (Swift
CryptoKit.SHA256) produce identical hashes.
Window discovery via title tags
When lattices creates a tmux session, it sets the tmux option:
set-titles-string "[lattices:<session-name>] #{pane_title}"This embeds a [lattices:name] tag in the terminal window title. The
menu bar app uses this tag to find windows via three fallback paths:
- CGWindowList (needs Screen Recording permission): fastest, reads window names from the window server
- Accessibility API (needs Accessibility permission): queries AXUIElement window titles for the terminal app
- AppleScript: iterates Terminal.app or iTerm2 windows by name
Space switching via SkyLight
The menu bar app can switch macOS Spaces to reach a session's window.
It uses private SkyLight framework APIs loaded at runtime via dlopen:
CGSMainConnectionID: get the connection to the window serverCGSGetActiveSpace: current Space IDCGSCopyManagedDisplaySpaces: enumerate all Spaces per displaySLSCopySpacesForWindows: find which Space a window is onSLSManagedDisplaySetCurrentSpace: switch a display to a Space
This is the same approach used by Loop and other macOS window managers.
Ensure/prefill restoration (requires tmux)
When you run lats start and a session already exists:
- lattices checks the
ensure/prefillflag in.lattices.json - For each pane, it queries
#{pane_current_command}via tmux - If the pane is running a shell (bash, zsh, fish, sh, dash), the original
command has exited. lattices then does one of these:
- ensure: sends the command + Enter (auto-restart)
- prefill: sends the command without Enter (manual restart)
- Then it attaches to the session as normal
Agent control
The agent API gives agents the same control as a human using the
menu bar app. An agent can list projects and windows, launch sessions,
tile windows to screen positions, subscribe to real-time events
(windows.changed, tmux.changed, layer.switched), and sync
sessions back to their declared config.
A typical orchestrator sets up a multi-project workspace in a few
daemonCall() invocations. See the Agent API reference
for the full method list and code examples.
Key shortcuts (inside tmux)
These work when you're inside a tmux session:
| Shortcut | Action |
|---|---|
| Ctrl+B D | Detach from session |
| Ctrl+B X | Kill current pane |
| Ctrl+B Left | Move to left pane |
| Ctrl+B Right | Move to right pane |
| Ctrl+B Up | Move to pane above |
| Ctrl+B Down | Move to pane below |
| Ctrl+B Z | Zoom pane (toggle) |
| Ctrl+B [ | Scroll mode (q exits) |
The prefix Ctrl+B means: hold Control, press B, release both,
then press the next key.