Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Telex

Telex

Telex is a CLI-first message fabric for AI agent sessions. Ephemeral sessions attach to durable addresses, exchange typed operational messages with answerback liveness, and leave an auditable disposition record. It runs as a single binary, telex (telex.exe on Windows), over local SQLite (zero configuration) or networked Postgres.

Who this is for

This guide is for people who install, configure, and operate telex, and who point their agent sessions at it. Agents themselves read their operating instructions from the binary at runtime with telex skill and telex copilot skill; those instructions are version-matched to the installed binary and are not duplicated here.

What it does

  • A durable address names a responsibility to be served (a node, a workstream, a session).
  • A per-user exchange daemon owns presence, delivery buffering, and the message store.
  • A session attaches to an address, sends and receives typed messages, and records disposition by message id.
  • Delivery is at-least-once and durable: a message persists until the recipient acks it, and a full history is available for audit.

Where to go next

  • Install the binary.
  • Run the Quickstart on the zero-config local store.
  • Read the Concepts to understand addresses, delivery, attention, and disposition.
  • Follow a Guide for agent setup, Copilot CLI push delivery, multi-session coordination, or a networked backend.
  • Consult the CLI reference, generated from the installed binary.

Install

Telex is a single binary. Install a prebuilt binary, or build from source with Rust.

macOS / Linux

curl -fsSL https://raw.githubusercontent.com/lossyrob/telex/main/install.sh | sh

Windows (PowerShell)

irm https://raw.githubusercontent.com/lossyrob/telex/main/install.ps1 | iex

With Rust (any platform)

cargo install --git https://github.com/lossyrob/telex --features entra

The entra feature adds Azure Entra authentication for Postgres backends; the published release binaries include it. Omit the feature if you do not need it.

Prebuilt binaries are also attached to each GitHub release.

Supported platforms

Prebuilt release binaries are published for Windows (x86_64 and ARM64), Linux (x86_64), and macOS (Apple Silicon and Intel). On other platforms — including ARM Linux (Raspberry Pi, Graviton, ARM WSL) — install from source with cargo install (the install script points ARM-Linux users there automatically).

Verify

telex --version

Updating

Once telex is installed, update in place to the latest compatible public release:

telex upgrade

This discovers the latest GitHub release, downloads this platform’s asset, verifies its SHA-256 checksum, and installs it through the versioned layout (keeping the previous version for telex rollback). Pin an explicit release with telex upgrade --version vX.Y.Z, or install a local build with telex upgrade --from <binary>. See Operating telex for details, including the fail-closed behavior and GITHUB_TOKEN for higher API rate limits.

Initialize (optional)

Telex creates its local store and schema on first use, so no init step is required for the default SQLite store. To pre-create and validate a backend (useful for Postgres, to surface connection or permission errors early):

telex init --backend <name>

Shell notes

Examples in this guide use POSIX shell syntax. On Windows PowerShell, set environment variables with $env: instead of export, for example $env:TELEX_SESSION_ID = "quickstart". The binary is telex.exe, invoked as telex.

Copilot CLI plugin

If you drive agents with GitHub Copilot CLI, install the telex plugin from the marketplace so messages arrive as turns (push delivery):

copilot plugin marketplace add lossyrob/telex
copilot plugin install telex@telex

Release install scripts print a tag-pinned marketplace command (copilot plugin marketplace add lossyrob/telex#vX.Y.Z) so the plugin and the installed binary stay on the same release. See the Copilot CLI push delivery guide.

Quickstart

No server setup and no configuration are required. The first daemon-backed command auto-spawns a per-user local exchange over a SQLite store at ~/.telex/telex.db.

Send and read a message

Set a stable session id once, then send a message to yourself and read it back:

export TELEX_SESSION_ID=quickstart   # PowerShell: $env:TELEX_SESSION_ID = "quickstart"
telex --address me send --to me --body "hello"
telex --address me inbox --all

send needs a session id (from --session or $TELEX_SESSION_ID) and a sender address. Here --to me is the recipient and the global --address me supplies the sender. inbox --all lists recent messages; the default inbox lists only actionable ones (messages requiring disposition).

The binary carries its own runtime instructions for agents. Print them with:

telex skill

This is the generic (pull) workflow. In Copilot CLI, run telex copilot skill for the push-delivery workflow instead.

A two-session taste

One session attaches to an address and waits; another finds it and sends. Run these in two terminals.

Session A registers an address and waits for one message:

export TELEX_SESSION_ID=session-a   # PowerShell: $env:TELEX_SESSION_ID = "session-a"
telex attach --address session:a --description "session A waiting for coordination"
telex wait --address session:a

Session B registers, finds A by its description, and sends:

export TELEX_SESSION_ID=session-b   # PowerShell: $env:TELEX_SESSION_ID = "session-b"
telex attach --address session:b --description "session B requesting status"
telex resolve --match "waiting for coordination"
telex send --to session:a --subject "Status request" --body "Please send status." --attention interrupt

Session A’s wait returns the message as JSON. A then acks it, dispositions it, and can reply in the same thread:

telex ack --address session:a --id <message-id>
telex handle --id <message-id> --note "status prepared"
telex reply --to-message <message-id> --body "Holder is live; continuing work."

See Coordinate multiple sessions for the full pattern, including the re-arm loop and attention phases.

Concepts overview

Telex has a small set of concepts. This page names them; the following pages cover each in detail.

  • Address: a durable name for a responsibility. Sessions attach to addresses; messages are sent to addresses.
  • Exchange and daemon: a per-user local process that owns presence, delivery buffering, and the message store, reached over local IPC.
  • Stations and presence: the in-memory registration a session holds while it attends an address, with a liveness lease.
  • Messages and threads: typed messages with a sender, body, and thread context; replies thread under a parent.
  • Attention: how urgent a message is, from interrupt to fyi, which controls when the recipient sees it.
  • Disposition: the auditable outcome recorded for a message: acknowledged, handled, deferred, rejected, closed, or escalated.
  • Delivery guarantees: messages are durable and delivered at-least-once; consumption is an explicit ack.
  • Backends: the configured store: local SQLite by default, or networked Postgres.

The shape of a session

A session has a stable identity (--session or $TELEX_SESSION_ID). It attaches to one or more addresses, sends and receives messages, records disposition by message id, and detaches or stops its station when done. The exchange retains the durable message buffer across daemon restarts.


Next: Addresses

Addresses

An address is a durable name for a responsibility being served. Sessions attach to addresses, and messages are sent to addresses rather than to a process or a person. Because the address is durable and the session is ephemeral, a message survives the session that was attending it and is delivered when a session next attends the address.

Identity vs. address

A session’s identity (--session or $TELEX_SESSION_ID) is the stable id the exchange uses to track membership. An address is what the session serves. One session can attend more than one address.

Sender address

Every send and reply stamps a from address so replies can route back. It resolves from an explicit --from, then $TELEX_ADDRESS or the global --address, then the single address the session attends. If the session attends more than one address and no --from is given, the send is refused as ambiguous rather than guessed.

Directory metadata

When a session attaches it can register directory metadata so other sessions can find it:

  • --description: a one-line statement of what the session is doing.
  • --scope: the project or workstream the address belongs to.
  • --tags: coarse comma-separated tags (for example repo:telex,role:worker).

Find addresses by description substring, scope, or tag:

telex address list --scope <scope>
telex resolve --match "<substring>"
telex resolve --tag <tag> --scope <scope>

Retire an address so it drops from normal listings with telex address retire --address <addr>.


Next: Exchange and daemon

Exchange and daemon

The exchange is a per-user local process (a daemon) that owns presence, delivery buffering, and the message store, reached by the CLI over local IPC. Sessions do not run a resident holder process of their own; they issue one-shot commands to the exchange.

Auto-spawn

There is no manual server to start. The first daemon-backed command auto-spawns the exchange for the selected backend. With no configuration this is a local SQLite store at ~/.telex/telex.db.

One-shot commands

Most verbs register or act and then exit:

  • attach registers the session and address, then exits.
  • wait blocks as one client for a single delivery, then exits.
  • send, reply, ack, and the disposition verbs act once and exit.

Because commands are one-shot, telex is driven from a script or an agent’s own turn cycle rather than a long-lived foreground process. See Set up an agent (pull).

Restart recovery

The exchange holds the durable message buffer in its store. If the daemon restarts, the next verb reconnects and re-registers on a NeedsAttach signal and continues against the retained buffer. A wait that finds no daemon exits with a distinct code so the caller can run attach (the spawning and recovery verb) and re-arm. See Exit codes.


Next: Stations and presence

Stations and presence

A station is the in-memory registration a session holds while it attends an address. attach creates the station and claims a liveness lease (an epoch) for the address; the exchange tracks the station’s health and any live waiter.

Presence is non-destructive

Detaching or a lease reap is non-destructive: the station and the durable message buffer remain. A blocked wait returns a distinct presence-ended result rather than losing the buffered messages. A later attach re-registers and continues.

Liveness backstop

attach --watch-pid anchor:<pid> registers a non-destructive liveness backstop. When the watched process dies, blocked waits return a presence-ended result, but the station and durable buffer are retained. This lets a station be cleaned up when its owning process goes away without dropping messages.

Inspecting stations

Get a machine-readable projection of a session’s attended addresses, waiter counts, and station health:

telex station status --session <id>

Stop a station as the symmetric inverse of the attach and wait loop; it marks the station non-attending, releases membership durably, and waits for tracked live waiters to exit:

telex station stop --address <addr>

After station stop, a later message to the address stays queued until a future attach or wait; it is not consumed by an orphaned waiter.


Next: Messages and threads

Messages and threads

A telex message is a typed operational message with a sender (from), a recipient (to), an optional subject, and a body. Messages can carry CC recipients (visible observers), a kind label, an attention level, and arbitrary metadata.

Sending

telex send --to <addr> --subject "<subject>" --body "<body>"
telex send --to <addr> --subject "<subject>" --body-file <path>   # UTF-8 file; - for stdin
telex send --to <addr> --subject "<subject>" --body-stdin          # read body from stdin (UTF-8)

--body, --body-file, and --body-stdin are mutually exclusive and exactly one is required. Prefer --body-file or --body-stdin for multiline or structured content (Markdown, code blocks, JSON) to avoid shell quoting limits. Files and stdin are read as UTF-8 and sent exactly as written. --body-file - and --body-stdin are equivalent: both read the body from stdin.

Windows / PowerShell: piped stdin must be UTF-8. Before piping non-ASCII content, run:

$OutputEncoding = [System.Text.Encoding]::UTF8
"Status: café ✓" | telex send --to <addr> --subject "Status" --body-stdin

For generated bodies, writing to a UTF-8 file first is the most reliable path:

$body | Out-File -Encoding utf8 body.txt
telex send --to <addr> --subject "Status" --body-file body.txt

send prints a receipt: delivered or queued-unoccupied, plus the new message id. A queued-unoccupied receipt is durable: the message is persisted and delivered when a station next attends the address. Sending to a retired address is an error (address <addr> is retired), not a receipt.

Threads and replies

Reply under a parent message; the reply threads under it and routes to the parent’s sender:

telex reply --to-message <message-id> --body "<body>"

CC (observers)

Add --cc <addr> to copy observer addresses on a message. Each recipient gets its own delivery of the same message id, with a delivery_role of to or cc:

telex send --to node:worker --cc node:lead --subject "Status" --body "..."

The primary recipient and each CC observer see the same id, thread_id, and primary_to, but their own delivery_role and delivered_to:

Recipientdelivery_roledelivered_torequires disposition
node:worker (to)tonode:workerset when the sender passes --requires-disposition
node:lead (cc)ccnode:leadno

--cc may be repeated and accepts comma-separated values. Ack and disposition are per recipient: acking for node:lead does not consume the copy for node:worker. An observer reads its copy with telex inbox --all or telex read, and acks its own (message_id, address).

Reading

telex inbox --address <addr>              # actionable messages (add --all for recent)
telex inbox --address <addr> --all --limit N
telex read --id <message-id> --thread     # a message with compact thread context
telex read --id <message-id> --full       # full history

Next: Attention levels

Attention levels

Every message carries exactly one attention level. It expresses how urgent the message is and controls when the recipient sees it.

interrupt | next-checkpoint | background | fyi
  • interrupt: highest urgency; handled ahead of all other messages.
  • next-checkpoint: handle after the current safe stopping point.
  • background: visible in the inbox; does not interrupt current work.
  • fyi: visible and auditable, non-actionable by default.

Set the level on send or reply with --attention <level>.

How attention maps to delivery

In push delivery (Copilot CLI), the exchange maps attention to send mode automatically: interrupt is delivered as an immediate steering interjection into the running turn, ahead of enqueued messages; every other level is enqueued and arrives at the next turn boundary. Neither preempts a turn already running.

In pull mode, a waiter armed with --min-attention interrupt wakes only for urgent messages; lower levels stay durably buffered for the recipient’s next checkpoint. See Coordinate multiple sessions for the two-phase attention loop.

Latency note: in push mode interrupt is seen mid-stream between the model’s iterations; other levels wait for the turn boundary. In pull mode, agent wake dominates perceived latency, so interrupt means “handle at the next turn boundary.”


Next: Delivery guarantees

Delivery guarantees

Durable

The exchange never loses an accepted message. A queued-unoccupied send is persisted and delivered when a station next attends the address. The durable buffer survives daemon restarts.

At-least-once

Delivery is at-least-once. A message may occasionally be delivered more than once (for example after a reconnect, or in push mode after a session re-attach). Consumers dedupe by message id. A message remains eligible for delivery until the recipient explicitly acks it:

telex ack --address <addr> --id <message-id>

Printing or displaying a message is transport only; it is not consumption. This is deliberate: the safe failure direction is a duplicate, never a silent loss.

Ack is per recipient

Ack consumes (message_id, recipient-address). Acking for address A does not consume the same message id for a CC recipient B, so observers see their own copy.

Terminal workflow disposition is separate

Ack is transport consumption. Closing out the work is a separate disposition (handle, reject, or close). A message can be acked (consumed from the delivery buffer) while still needing a terminal workflow disposition.


Next: Disposition

Disposition

Disposition is the auditable outcome recorded for a message. It is separate from transport: receiving a message does not disposition it, and dispositioning a message is an explicit, recorded act.

Transport consumption: ack

ack marks the delivered (message_id, recipient-address) consumed in the exchange delivery buffer. Ack is per recipient, so acking a message for address A never consumes the same message id for a CC recipient B.

telex ack --address <addr> --id <message-id>

Workflow disposition states

acknowledged | handled | deferred | rejected | closed | escalated
  • Terminal (removed from the actionable inbox): handled, rejected, closed.
  • Non-terminal (still needing final disposition): acknowledged, deferred, escalated.

Record a workflow disposition with the matching verb, optionally with a note:

telex handle --id <message-id> --note "completed"
telex defer --id <message-id> --note "waiting on input"
telex reject --id <message-id> --note "out of scope"
telex close --id <message-id>
telex escalate --id <message-id> --note "needs operator"

Dispositions default to the current --address recipient. Pass --recipient only to record for another recipient intentionally.

Ordering

Ack first (transport consumption), then apply the workflow disposition that reflects the actual outcome. A message that requires disposition (--requires-disposition on send) stays actionable until it reaches a terminal state.


Next: Backends

Backends

A backend is a named, configured store. Selection is by name: --backend <name>, then $TELEX_BACKEND, then the configured default, then an implicit default SQLite store at ~/.telex/telex.db. With no setup, telex works on local SQLite.

SQLite (default)

Local, zero-config, single-user. Nothing to configure; the implicit default store is created on first use. Override the path for one invocation with --db <path> or $TELEX_DB.

Postgres (networked)

Configure a Postgres backend once, then select it by name or make it the default. The connection string is a libpq URI or a key=value DSN. Provide the password by reference, never embedded in the config:

  • --entra: Azure Entra; telex fetches the token itself (uses az login, or --entra-cred managed on a devbox or VM with a managed identity). Requires a build with the entra feature, which the release binaries include.
  • --password-env <VAR>: read the password from an environment variable.
  • --password-command <cmd>: run a command that prints the password.
# Postgres with a password from an env var:
telex backend add staging \
  --postgres "postgresql://app@staging-db:5432/telex?sslmode=require" \
  --password-env STAGING_PG_PASSWORD --schema telex

# Azure Postgres with Entra:
telex backend add prod \
  --postgres "host=myserver.postgres.database.azure.com port=5432 user=me@example.com dbname=postgres sslmode=require" \
  --entra --schema telex --default

Managing backends

telex backend list
telex backend show <name>
telex backend default <name>
telex backend remove <name>
telex backend kinds       # backend kinds compiled into this build

The first backend added becomes the default; --default or telex backend default <name> changes it. See the Networked Postgres backend guide.


Next: Security and data

Security and data

Where data lives

  • Local SQLite store: ~/.telex/telex.db (override with --db or $TELEX_DB).
  • Config (named backends): ~/.telex/config.toml.
  • Runtime directory (daemon IPC and lease state): a per-user runtime directory (on Windows under %LOCALAPPDATA%\telex\run; on Unix a per-user socket/runtime directory).
  • Copilot bridge files: in the Copilot session’s extension directory; removed on telex copilot detach or session end.

Trust model

The local exchange serves a single operating-system user over local IPC. A same-user process operates under the current trust model. Do not place a telex store or socket where other users can read it if the messages are sensitive.

Secrets

Postgres passwords are referenced, never written to the config file: use --entra, --password-env, or --password-command. telex backend show redacts secrets.

Message content

Message bodies, subjects, metadata, and disposition history are stored in the selected backend and are readable by anyone with access to that store (the local file, or the Postgres schema). Treat the store as sensitive if the messages are.

Postgres sharing

A Postgres backend is shared across the machines and users configured to use it. Scope access with database and schema grants, and use a dedicated --schema.

Reporting a vulnerability

Report security issues privately, not through public issues or pull requests. See the repository security policy for the private reporting channel and what to expect.


Next: Glossary

Glossary

  • Address: a durable name for a responsibility being served. Messages are sent to addresses. See Addresses.
  • Session identity: the stable id (--session or $TELEX_SESSION_ID) the exchange uses to track a session’s membership.
  • Exchange (daemon): the per-user local process that owns presence, delivery buffering, and the message store. See Exchange and daemon.
  • Station: the in-memory registration a session holds while it attends an address. See Stations and presence.
  • Lease / epoch: the liveness claim a station holds on an address; the epoch increments when ownership changes.
  • Waiter: a one-shot telex wait client blocked for a single delivery.
  • Attention: the urgency of a message: interrupt, next-checkpoint, background, or fyi. See Attention levels.
  • Disposition: the recorded outcome of a message: acknowledged, handled, deferred, rejected, closed, or escalated. See Disposition.
  • Ack: the explicit durable mark that a delivered message was consumed from the delivery buffer, per recipient.
  • Backend: the configured store (local SQLite or networked Postgres). See Backends.
  • Thread: a message and its replies, linked by thread_id and parent_id.

Next: Set up an agent (pull)

Set up an agent (pull)

This guide covers pointing an agent session at telex in a generic harness (scripts, CI, or any harness without an in-session extension). Copilot CLI uses push delivery instead; see Copilot CLI push delivery.

Agents read the full, version-matched pull workflow from the binary with telex skill. This guide is the operator’s view of how the pieces fit together.

1. Give the session a stable identity

Generic telex commands need a stable session id on each invocation, from --session or $TELEX_SESSION_ID. Telex fails closed rather than guessing.

export TELEX_SESSION_ID=<stable-session-id>   # PowerShell: $env:TELEX_SESSION_ID = "<stable-session-id>"

2. Attach once

telex attach --address <addr> --description "<what this session is doing>"

attach registers the session and address and exits. Add --scope and --tags so other sessions can resolve this one.

3. Wait for one message, then re-arm

wait is one-shot: it blocks for a single delivery and exits. Drive the loop from the agent’s own turn cycle, one wait per delivery. Write the result to files with --out-dir so a detached waiter’s result is readable after it exits:

telex wait --address <addr> --session <id> --out-dir <dir>

On exit the waiter writes into <dir>: message.json and delivery.json (on exit 0), status.json (always), and exit.code (written last, as the completion marker). Read exit.code first; see Exit codes.

Do not wrap wait in an infinite shell loop. Many harnesses surface output only when a command completes, so an internal loop hides delivered messages. Arm one wait, handle its completion, then arm the next.

4. Ack and disposition

After reading the delivered JSON, ack it (transport consumption), then record the workflow disposition:

telex ack --address <addr> --session <id> --id <message-id>
telex handle --address <addr> --session <id> --id <message-id> --note "completed"

Dedupe by message id: delivery is at-least-once.

5. Stop the station when done

telex station stop --address <addr>

This releases membership durably and waits for tracked waiters to exit. Later messages stay queued until a future attach or wait.

Copilot CLI push delivery

In GitHub Copilot CLI, telex delivers messages to the agent as turns. The agent does not run or re-arm a waiter. The full, version-matched Copilot workflow is printed by the installed binary:

telex copilot skill

That command is the source of truth for the Copilot path. This guide is the operator’s overview.

Install the plugin

copilot plugin marketplace add lossyrob/telex
copilot plugin install telex@telex

The plugin contributes session lifecycle hooks and provisions the push bridge. It maps $COPILOT_AGENT_SESSION_ID to the generic telex session id. In bridge mode, the extension heartbeat, not $COPILOT_LOADER_PID, is the push liveness signal.

Bind and provision the bridge

First enable Copilot Extensions under /experimental. Copilot exposes the extensions_reload tool only when this experimental feature is enabled.

telex --address <addr> copilot attach --copilot-bridge --description "<work>"

Then run the extensions_reload tool once (the agent does this; telex cannot trigger a reload). After that, delivered telex messages arrive as new turns labelled [telex] from <addr> (<attention>).

If extensions_reload is unavailable, enable Copilot Extensions under /experimental, re-provision with telex --address <addr> copilot resume --description "<work>", and then run extensions_reload. If Copilot Extensions cannot be enabled, use the supported pull fallback or detach with telex --address <addr> copilot detach.

Receive and disposition

A pushed turn includes the ack and handle commands with the address, id, and session filled in. The generic verbs do not read Copilot env vars, so they need --session "$COPILOT_AGENT_SESSION_ID":

telex ack --address <addr> --id <message-id> --session "$COPILOT_AGENT_SESSION_ID"
telex handle --address <addr> --id <message-id> --session "$COPILOT_AGENT_SESSION_ID" --note "completed"

Sending is not push: telex send and telex reply also need --session.

CC observers

A session that should receive CC (observer) copies as turns opts in at bind time with --wake-on-cc:

telex --address <addr> copilot attach --copilot-bridge --wake-on-cc --description "<work>"

Then run extensions_reload as usual. Without --wake-on-cc, CC copies are still buffered and visible in telex inbox --all, but are not delivered as turns. (telex wait --wake-on-cc is the separate pull-mode equivalent for non-Copilot harnesses.)

Tear down

telex --address <addr> copilot detach

This detaches the address and, when it was the last binding, removes the bridge files so nothing reloads on a later resume. Session end also removes them.

Inspect stale bridge files left by other sessions with telex copilot gc --dry-run.

Fallback

If the bridge cannot load because Copilot Extensions cannot be enabled, push is unavailable. Surface that plainly and prepare one Telex-owned pull fallback run:

telex --address <addr> copilot fallback prepare --description "<work>"

The JSON result contains a unique run_dir plus launcher.program, launcher.args, and a ready-to-run launcher.command. Run that launcher as one fully detached Copilot task. Unix uses the current telex binary directly; Windows uses a generated PowerShell file for the detached-task compatibility path. Telex does not detach the task itself and does not run an internal delivery loop.

Preparation is idempotent until the run writes exit.code, and it leaves push unchanged if the launcher never starts. The running launcher atomically clears push before entering exactly one pull-mode wait. On completion, read exit.code first, then the exact run’s delivery.json/message.json; ack and dedupe primary deliveries by message id before preparing the next run.

telex --address <addr> status reports delivery_mode separately from station_health: push is bridge delivery, pull is the Copilot fallback, and conflict is a version-skew/race tripwire. The daemon rejects simultaneous push and pull coverage.

To return to push, stop the waiter before binding the bridge:

telex --address <addr> station stop --session "$COPILOT_AGENT_SESSION_ID"
telex --address <addr> copilot attach --copilot-bridge --description "<work>"

Then run extensions_reload. The version-matched telex copilot skill contains the full artifact, timeout, recovery, and re-arm procedure.

Compatibility

telex copilot skill prints the installed version and source build identifier, the bridge protocol version, and the minimum compatible plugin version, and warns if the plugin is older than the binary supports. telex --version includes the same build identifier, while telex --json version exposes it as version.build_id. Published release binaries are gated to report the release commit; source builds without Git metadata may report unknown. Git fallback is accepted only for a standalone Telex checkout, not an unrelated ancestor repository, and the value is diagnostic rather than an attestation.

If the drain hook reports skew, inspect version.current_exe plus Get-Command telex or command -v telex, reinstall the plugin and binary from the same release, and make the versioned launcher precede stale shims on PATH before restarting Copilot. A binary rollback must be paired with the plugin from the same release.

Release install scripts pin the plugin and binary to the same tag. The plugin shape is validated against a specific Copilot CLI version; see the acceptance matrix.

Coordinate multiple sessions

This is the full two-session pattern: one session serves an address and waits, the other finds it and sends a message that requires disposition.

Session A: attend and wait

export TELEX_SESSION_ID=session-a   # PowerShell: $env:TELEX_SESSION_ID = "session-a"
telex attach --address session:a \
  --description "session A waiting for coordination" \
  --scope project:telex --tags repo:telex,role:worker
telex wait --address session:a

attach is one-shot; only wait blocks. When A’s wait returns (exit 0) with the message as JSON, A acks and dedupes by id, arms a fresh wait before longer processing, then handles and replies:

telex ack --address session:a --id <message-id>
telex wait --address session:a                       # re-arm before longer work
telex handle --id <message-id> --note "status prepared"
telex reply --to-message <message-id> \
  --body "Holder is live; continuing work." --attention next-checkpoint

Session B: find A and send

export TELEX_SESSION_ID=session-b   # PowerShell: $env:TELEX_SESSION_ID = "session-b"
telex attach --address session:b --description "session B requesting status" \
  --scope project:telex --tags repo:telex,role:requester
telex resolve --match "waiting for coordination" --scope project:telex
telex send --to session:a --subject "Status request" \
  --body "Please send your current status." --attention interrupt --requires-disposition

B then waits for A’s reply and closes it out:

telex wait --address session:b
telex ack --address session:b --id <reply-id>
telex handle --id <reply-id> --note "reply received"

The re-arm loop

Drive the loop from the agent’s turn cycle, one wait per delivery:

  1. Arm one wait (optionally with --min-attention interrupt while focused).
  2. It blocks until one message, exits, and the runtime wakes the agent.
  3. Read the result, ack, dedupe by id, then arm a fresh wait before longer processing.

Do not wrap wait in an infinite shell loop.

Two-phase attention

While actively working, arm a phase-1 waiter with --min-attention interrupt; it wakes only for urgent messages, while next-checkpoint, background, and fyi messages stay durably buffered. At a checkpoint, inspect telex inbox --all --address <addr>, disposition what you are ready to handle, then continue with an interrupt-only waiter or, if idle, an unfiltered waiter.

Only one live waiter per station is permitted. To switch modes, let the current waiter complete or run telex station stop, then re-attach and arm the new mode.

Networked Postgres backend

Local SQLite is the default. Use a Postgres backend when sessions on different machines need to share one exchange, or to persist an audit trail centrally.

Add a backend

Configure once with telex backend add. Provide the password by reference, never embedded in the connection string.

Password from an environment variable

telex backend add staging \
  --postgres "postgresql://app@staging-db:5432/telex?sslmode=require" \
  --password-env STAGING_PG_PASSWORD --schema telex

Azure Postgres with Entra

Telex fetches the token itself. On a laptop it uses your az login; on a devbox or VM with a managed identity, use --entra-cred managed.

telex backend add prod \
  --postgres "host=myserver.postgres.database.azure.com port=5432 user=me@example.com dbname=postgres sslmode=require" \
  --entra --schema telex --default

--entra requires a build with the entra feature; the release binaries include it. On a build without it, supply the token with --password-command (for example az account get-access-token ...).

Select a backend

telex --backend staging inbox
telex send --to node:x --body "hi"     # uses the default backend
telex backend list

The first backend added becomes the default; change it with --default on add or telex backend default <name>.

Notes

  • The connection string is a libpq URI or a key=value DSN.
  • Use --schema to place telex tables in a dedicated schema.
  • Secrets are referenced (--entra, --password-env, --password-command) and are never written to the config file. telex backend show <name> redacts them.

Schema and privileges

Set the schema for telex tables with --schema when adding the backend. The configured database role needs privileges to create objects in that schema on first use (or to use an existing one). Pre-create and validate the schema:

telex init --backend <name>

This connects with the configured credentials, creates the schema and tables if they are absent, and surfaces connection or permission errors early.

TLS

Request TLS in the connection string with sslmode=require, or a stricter mode such as verify-full with the appropriate root certificate configured for your environment. Azure Postgres requires TLS.

Backup

A telex Postgres backend is an ordinary schema in your database. Back it up with standard Postgres tooling:

pg_dump --schema telex "postgresql://.../telex" > telex-backup.sql

Multiple machines

Point each machine’s telex at the same Postgres backend (same connection string and schema) to share one durable store and audit trail. Each machine still runs its own local exchange daemon; the daemon is per user and local, while the store is the shared Postgres schema.

Operating telex

The daemon lifecycle

The exchange auto-spawns on the first daemon-backed command; there is no manual start. Inspect the resolved backend and address projection with:

telex status --address <addr>

The daemon runs per user. Inspect and control it with the telex daemon family (run telex daemon --help for the full set):

telex daemon status            # daemon internals
telex daemon version           # running daemon version
telex daemon stop --drain      # stop after draining in-flight work

Runtime state (the IPC socket and lease state) lives in a per-user runtime directory: on Windows under %LOCALAPPDATA%\telex\run, on Unix a per-user socket directory. The local message store is the SQLite file at ~/.telex/telex.db.

Stopping a station

station stop is the symmetric inverse of the attach and wait loop. It marks the station non-attending, releases membership durably, and waits for tracked waiters to exit:

telex station stop --address <addr>

After it returns, a later message to the address stays queued until a future attach or wait; it is not consumed by an orphaned waiter.

Teardown: which command to use

CommandEffect
telex detach --address <addr>Drop this session’s membership of the address, non-destructively. The station and durable buffer remain.
telex station stop --address <addr>Mark the station non-attending, release membership durably, and wait for tracked waiters to exit.
telex address retire --address <addr>Retire the address so it drops from directory listings.
telex daemon stop --drainStop the local exchange after draining in-flight work.
telex copilot detachCopilot push sessions: detach the address and remove the bridge files.

None of these delete durable messages; a later attach or wait resumes against the retained buffer.

Upgrading the binary

Release installs use a versioned layout instead of overwriting the binary on PATH in place. A stable launcher lives under the install root’s bin/, immutable binaries live under versions/<tag>/, and current selects the version new invocations use. Old in-flight processes keep running on their version while new shells use the selected one.

<install-root>/
  bin/telex(.exe)
  versions/<tag>/telex(.exe)
  current
  previous

Upgrade, roll back, and inspect versions:

telex version --json

# Fetch, verify, and install the latest compatible public release:
telex upgrade

# Install a specific public release by tag:
telex upgrade --version vX.Y.Z

# Install a local/manual build (no download):
telex upgrade --from <path-to-telex-binary> --version vX.Y.Z

telex rollback
telex gc --dry-run

Without --from, telex upgrade discovers a GitHub release (the latest full release by default, or --version <tag>), selects this platform’s asset, downloads the archive and its .sha256 sidecar, and verifies the checksum before installing — then installs through the same versioned layout as the local path. It is fail-closed: a missing or mismatched checksum, a missing platform asset, an unsupported platform, an incompatible version, or a network/rate-limit error aborts without changing current. Set GITHUB_TOKEN to raise the API rate limit. Prebuilt binaries are published for Windows (x86_64, ARM64), Linux (x86_64), and macOS (Apple Silicon, Intel); on other platforms install from source with cargo install --git https://github.com/lossyrob/telex --features entra. If telex is already on the resolved release it reports “already current” and does nothing (override with --force).

telex upgrade reads the downloaded (checksum-verified) binary’s own metadata by running it once (telex --json version) before installing; in locked-down environments an OS quarantine prompt (macOS Gatekeeper, Windows SmartScreen) on that step is the likely cause if an upgrade stalls. The checksum verifies integrity, not authenticity — it protects against a corrupted or truncated download, and the trust root is the GitHub repository the asset comes from.

telex upgrade and telex rollback drain the current local daemon before switching current, unless --skip-drain is passed. Rollback refuses installed versions whose manifest is incompatible with this build’s protocol/schema floor.

For a manual in-place replacement, drain and replace in this order:

telex station stop --address <addr>
telex daemon stop --drain
# replace the telex binary
telex attach --address <addr> --description "<s>"
telex wait --address <addr> --out-dir <dir>

If a session resumes without an armed waiter, recovery is durable: inspect telex inbox --address <addr> and telex read --id <id>, then arm a fresh wait.

Auditing

Export messages and disposition history as JSON lines for provenance:

telex export --address <addr>
telex export --thread <id>
telex export --since <id>

Recovering from a lost daemon

A wait that finds no daemon exits with a distinct code (see Exit codes). Run telex attach (the spawning and recovery verb) and re-arm the wait. If a replacement daemon already exists, a wait can reconnect during its bounded reconnect grace.

Turn-end and resume reconciliation

For turn-end guards or resume reconciliation, use telex station status --session <id> to get a compact JSON projection of the session’s attended addresses, waiter counts, station health, and pending unconsumed counts.

Uninstall and cleanup

  1. Stop the daemon: telex daemon stop --drain.
  2. Remove local state: delete ~/.telex/ (the SQLite store and config).
  3. Remove the Copilot plugin, if installed: copilot plugin uninstall telex@telex.
  4. For a Postgres backend, drop the telex schema in the database if it is no longer needed.

Troubleshooting

no session id available

send, reply, ack, and the disposition verbs need a stable session id. Pass --session <id> or set TELEX_SESSION_ID. In Copilot CLI, pass --session "$COPILOT_AGENT_SESSION_ID". Telex fails closed rather than guessing an identity.

wait exits 3 (daemon gone / not running)

wait does not spawn a missing daemon. Run telex attach --address <addr> ... (the spawning and recovery verb), then re-arm the wait. See Exit codes.

one live waiter is already armed

Only one live waiter per station is allowed. Let the current waiter complete, or run telex station stop --address <addr>, then re-attach and arm the new mode.

Cannot re-arm because the prior message is unacked

Ack the delivered message first (telex ack --id <id> --session <id>), then arm a fresh wait.

Send refused as ambiguous, or a warning that it is un-repliable

Every send stamps a from. If your session attends more than one address, pass --from <addr>. If no --from, --address, or attended station is set, the send warns that it is un-repliable; attach the address first or pass --from.

address <addr> is retired

The target address was retired and dropped from listings. Use a live address, or the owner can attend it again.

Copilot: messages do not arrive as turns

The push bridge may not be loaded. If extensions_reload is unavailable, enable Copilot Extensions under /experimental. Then re-provision with telex --address <addr> copilot resume and run extensions_reload. If Copilot Extensions cannot be enabled, push is unavailable; use the supported Copilot pull fallback with telex --address <addr> copilot fallback prepare, or detach with telex --address <addr> copilot detach.

Backend authentication failures (Postgres / Entra)

Check the connection string and credentials. telex init --backend <name> validates connectivity and creates the schema, surfacing errors early. For Entra, ensure az login has run, or use --entra-cred managed on a host with a managed identity.

Inspecting state

  • telex status --address <addr>: resolved backend and address projection.
  • telex station status --session <id>: attended addresses, waiter counts, and station health for a session.
  • telex daemon status: daemon internals.

CLI reference

This page is generated from the installed telex binary (telex 0.1.1 (build bfa8f64a74201255572ae297e4caca8063e33619)) by docs/guide/generate-reference.sh. Do not edit it by hand; it is regenerated on every docs build so it stays matched to the binary. For the workflow narrative, see the Guides.

telex

Telex lets ephemeral agent sessions attach to durable addresses, exchange typed operational messages with answerback liveness, and leave an auditable disposition record. Run `telex skill` to load agent usage instructions for this build.

Usage: telex [OPTIONS] <COMMAND>

Commands:
  init      Initialize ~/.telex and the backend schema
  status    Show config, backend, address, station/occupancy status
  version   Show telex binary, launcher, protocol, and install metadata
  upgrade   Install a versioned telex binary and optionally switch current to it
  rollback  Switch current back to a previously installed version
  gc        Garbage-collect old installed telex versions
  skill     Print the agent usage skill (how to use telex) for this build
  attach    Attach this session to an address and exit
  detach    Detach this session's address membership
  station   Station lifecycle operations
  wait      Block until an actionable message arrives, print it as JSON, and exit
  inbox     List actionable and recent messages for an address
  read      Read a message (optionally with thread context)
  send      Send a message to an address
  reply     Reply to a message; threads under it
  ack       Acknowledge a message
  handle    Mark a message handled (terminal)
  defer     Defer a message
  reject    Reject a message (terminal)
  close     Close a message/thread (terminal)
  escalate  Escalate a message
  address   Address directory operations
  resolve   Resolve target address(es) by description match or tag
  backend   Manage configured backends (named profiles in ~/.telex/config.toml)
  export    Export messages and disposition history as JSON lines
  help      Print this message or the help of the given subcommand(s)

Options:
      --backend <BACKEND>
          Configured backend to use, by name (default: the configured default backend)
          
          [env: TELEX_BACKEND=]

      --db <DB>
          Override the SQLite path for this invocation (sqlite backends only)
          
          [env: TELEX_DB=]

      --address <ADDRESS>
          Address to operate on (default for commands that act on one address)
          
          [env: TELEX_ADDRESS=]

      --json
          Force JSON output

      --text
          Force concise text output

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

telex init

Initialize ~/.telex and the backend schema

Usage: telex init [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex status

Show config, backend, address, station/occupancy status

Usage: telex status [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex version

Show telex binary, launcher, protocol, and install metadata

Usage: telex version [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --root <ROOT>        Inspect a specific versioned install root instead of inferring it from this executable
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex upgrade

Install a versioned telex binary and optionally switch current to it

Usage: telex upgrade [OPTIONS]

Options:
      --backend <BACKEND>
          Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --from <PATH>
          Local telex binary or directory containing telex(.exe) to install (manual/local upgrade path). Omit to discover, download, verify, and install the latest compatible public GitHub release
      --db <DB>
          Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --version <VERSION>
          Release tag to install/switch to. Without --from this selects an explicit public release (e.g. v0.2.0); with --from it labels the local install (defaults to this binary's package version)
      --address <ADDRESS>
          Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --force
          Reinstall/switch even when the resolved release is already the current version
      --json
          Force JSON output
      --root <ROOT>
          Versioned install root (default: inferred install root or platform default)
      --text
          Force concise text output
      --no-switch
          Install into versions/<tag> but do not switch current
      --skip-drain
          Skip daemon drain before switching current (not recommended)
      --drain-timeout-ms <DRAIN_TIMEOUT_MS>
          Bound daemon drain before switching current [default: 10000]
  -h, --help
          Print help

telex rollback

Switch current back to a previously installed version

Usage: telex rollback [OPTIONS]

Options:
      --backend <BACKEND>
          Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --version <VERSION>
          Installed version tag to switch to (default: previous)
      --db <DB>
          Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --root <ROOT>
          Versioned install root (default: inferred install root or platform default)
      --address <ADDRESS>
          Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --skip-drain
          Skip daemon drain before switching current (not recommended)
      --drain-timeout-ms <DRAIN_TIMEOUT_MS>
          Bound daemon drain before switching current [default: 10000]
      --json
          Force JSON output
      --text
          Force concise text output
  -h, --help
          Print help

telex gc

Garbage-collect old installed telex versions

Usage: telex gc [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --root <ROOT>        Versioned install root (default: inferred install root or platform default)
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --dry-run            Report what would be removed without deleting anything
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --force              Try removal of stale versions even after ordinary removal errors; never removes current, previous, or the active process version
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex skill

Print the agent usage skill (how to use telex) for this build

Usage: telex skill [OPTIONS]

Options:
      --address <ADDRESS>  Tailor the instructions for a specific assigned address
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --raw                Print the embedded SKILL.md verbatim (including frontmatter)
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex attach

Attach this session to an address and exit

Usage: telex attach [OPTIONS]

Options:
      --backend <BACKEND>
          Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --description <DESCRIPTION>
          One-line directory description of what this session is doing
      --db <DB>
          Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --scope <SCOPE>
          Project/workstream scope this address belongs to
      --address <ADDRESS>
          Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --tags <TAGS>
          Comma-separated coarse tags (e.g. issue:215,repo:telex)
      --heartbeat-secs <HEARTBEAT_SECS>
          Deprecated compatibility flag; the daemon owns lease heartbeat cadence [default: 5]
      --json
          Force JSON output
      --poll-secs <POLL_SECS>
          Deprecated compatibility flag; the daemon owns backend polling [default: 1]
      --text
          Force concise text output
      --keepalive-secs <KEEPALIVE_SECS>
          Deprecated compatibility flag; daemon waiters use daemon IPC frames [default: 3]
      --occupant <OCCUPANT>
          Occupant identity recorded on the lease (default: session host/pid)
      --session <SESSION>
          Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --push
          Deprecated compatibility flag; daemon delivery owns push/poll behavior
      --session-pid <SESSION_PID>
          Back-compat watch pid. Converted to an anchor watch-pid for daemon liveness
      --watch-pid <WATCH_PID>
          Watch a pid as a typed liveness predicate. Accepts PID, anchor:PID, required:PID, PID:anchor, or PID:required. Repeat to add multiple watch pids
      --session-poll-secs <SESSION_POLL_SECS>
          Deprecated compatibility flag; daemon liveness cadence is internal [default: 2]
      --no-session-bind
          Do not convert `$TELEX_SESSION_PID` into a daemon watch-pid
  -h, --help
          Print help

telex detach

Detach this session's address membership

Usage: telex detach [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --session <SESSION>  Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex station

Station lifecycle operations

Usage: telex station [OPTIONS] <COMMAND>

Commands:
  status  Show this session's attended addresses and waiter state
  stop    Stop this session's station: release membership and drain its live waiters
  help    Print this message or the help of the given subcommand(s)

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex station status

Show this session's attended addresses and waiter state

Usage: telex station status [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --session <SESSION>  Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --all-sessions       Show all stations in the selected store instead of only this session
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex station stop

Stop this session's station: release membership and drain its live waiters

Usage: telex station stop [OPTIONS]

Options:
      --backend <BACKEND>              Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --session <SESSION>              Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --db <DB>                        Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --wait-grace-ms <WAIT_GRACE_MS>  How long to wait for live waiter processes to exit after teardown is signaled (ms) [default: 3000]
      --address <ADDRESS>              Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json                           Force JSON output
      --text                           Force concise text output
  -h, --help                           Print help

telex wait

Block until an actionable message arrives, print it as JSON, and exit

Usage: telex wait [OPTIONS]

Options:
      --backend <BACKEND>
          Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --session <SESSION>
          Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --db <DB>
          Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --timeout-ms <TIMEOUT_MS>
          Give up waiting after this many milliseconds (exit code 2); default is no idle timeout
      --address <ADDRESS>
          Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --min-attention <MIN_ATTENTION>
          Only wake for messages at this attention or higher priority
      --json
          Force JSON output
      --wake-on-cc
          Also wake for live CC traffic without making CC ack-required
      --since <SINCE>
          Resume delivery strictly after this message id [default: 0]
      --text
          Force concise text output
      --hang-ms <HANG_MS>
          Deprecated idle-wait compatibility watchdog. For daemon waits, only applies after timeout-ms [default: 8000]
      --reconnect-grace-ms <RECONNECT_GRACE_MS>
          Retry daemon reconnect/re-register for this long after EOF/restart (ms) [env: TELEX_RECONNECT_GRACE_MS=]
      --stale-heartbeat-ms <STALE_HEARTBEAT_MS>
          Holder DB-heartbeat age beyond which it is considered degraded (ms) [default: 15000]
      --out-dir <OUT_DIR>
          Write outcome artifacts into this directory so a detached, variable-free invocation can deliver results without relying on captured stdout. Writes `message.json` (on delivery), `status.json` (always), and `exit.code` (always, written last as the completion marker)
  -h, --help
          Print help

telex inbox

List actionable and recent messages for an address

Usage: telex inbox [OPTIONS]

Options:
      --all                Include all recent messages, not just actionable ones
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --limit <LIMIT>      Maximum messages to list [default: 50]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex read

Read a message (optionally with thread context)

Usage: telex read [OPTIONS] --id <ID>

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --id <ID>            Message id to read
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --thread             Include compact thread context
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --full               Include full thread history and dispositions
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex send

Send a message to an address

Usage: telex send [OPTIONS] --to <TO>

Options:
      --backend <BACKEND>      Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --to <TO>                Destination address
      --db <DB>                Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --subject <SUBJECT>      Subject line
      --address <ADDRESS>      Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --body <BODY>            Message body (inline). Body/subject/metadata are capped below the 1 MiB IPC frame
      --body-file <BODY_FILE>  Read the message body from a UTF-8 file (`-` = stdin); capped below the 1 MiB IPC frame
      --json                   Force JSON output
      --body-stdin             Read the message body from stdin (UTF-8). Equivalent to `--body-file -`. On Windows / PowerShell, run `$OutputEncoding = [System.Text.Encoding]::UTF8` before piping non-ASCII content, or write a UTF-8 file and use `--body-file <path>` instead
      --text                   Force concise text output
      --cc <CC>                CC addresses (visible observers). May be repeated and/or comma-separated
      --kind <KIND>            Message kind/profile label [default: note]
      --attention <ATTENTION>  Attention level: interrupt | next-checkpoint | background | fyi [default: background]
      --requires-disposition   Mark that the recipient must disposition this message
      --from <FROM>            Sender address (defaults to the global --address if set)
      --metadata <METADATA>    Arbitrary JSON metadata; counted with body/subject against the IPC payload cap
      --session <SESSION>      Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
  -h, --help                   Print help

telex reply

Reply to a message; threads under it

Usage: telex reply [OPTIONS] --to-message <TO_MESSAGE>

Options:
      --backend <BACKEND>        Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --to-message <TO_MESSAGE>  The message id being replied to
      --body <BODY>              Reply body (inline). Body/subject are capped below the 1 MiB IPC frame
      --db <DB>                  Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>        Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --body-file <BODY_FILE>    Read the reply body from a UTF-8 file (`-` = stdin); capped below the 1 MiB IPC frame
      --body-stdin               Read the reply body from stdin (UTF-8). Equivalent to `--body-file -`. On Windows / PowerShell, run `$OutputEncoding = [System.Text.Encoding]::UTF8` before piping non-ASCII content, or write a UTF-8 file and use `--body-file <path>` instead
      --json                     Force JSON output
      --subject <SUBJECT>        Subject (defaults to "Re: <parent subject>")
      --text                     Force concise text output
      --cc <CC>                  CC addresses (visible observers). May be repeated and/or comma-separated
      --attention <ATTENTION>    Attention level [default: background]
      --requires-disposition     Mark that the recipient must disposition this reply
      --from <FROM>              Sender address (defaults to the global --address if set)
      --kind <KIND>              Message kind/profile label [default: note]
      --session <SESSION>        Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
  -h, --help                     Print help

telex ack

Acknowledge a message

Usage: telex ack [OPTIONS] --id <ID>

Options:
      --backend <BACKEND>      Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --id <ID>                Message id to disposition
      --db <DB>                Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --note <NOTE>            Optional note recorded with the disposition
      --address <ADDRESS>      Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --recipient <RECIPIENT>  Recipient address whose disposition this is (defaults to the message's to_addr)
      --json                   Force JSON output
      --session <SESSION>      Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --text                   Force concise text output
  -h, --help                   Print help

telex handle

Mark a message handled (terminal)

Usage: telex handle [OPTIONS] --id <ID>

Options:
      --backend <BACKEND>      Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --id <ID>                Message id to disposition
      --db <DB>                Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --note <NOTE>            Optional note recorded with the disposition
      --address <ADDRESS>      Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --recipient <RECIPIENT>  Recipient address whose disposition this is (defaults to the message's to_addr)
      --json                   Force JSON output
      --session <SESSION>      Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --text                   Force concise text output
  -h, --help                   Print help

telex defer

Defer a message

Usage: telex defer [OPTIONS] --id <ID>

Options:
      --backend <BACKEND>      Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --id <ID>                Message id to disposition
      --db <DB>                Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --note <NOTE>            Optional note recorded with the disposition
      --address <ADDRESS>      Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --recipient <RECIPIENT>  Recipient address whose disposition this is (defaults to the message's to_addr)
      --json                   Force JSON output
      --session <SESSION>      Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --text                   Force concise text output
  -h, --help                   Print help

telex reject

Reject a message (terminal)

Usage: telex reject [OPTIONS] --id <ID>

Options:
      --backend <BACKEND>      Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --id <ID>                Message id to disposition
      --db <DB>                Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --note <NOTE>            Optional note recorded with the disposition
      --address <ADDRESS>      Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --recipient <RECIPIENT>  Recipient address whose disposition this is (defaults to the message's to_addr)
      --json                   Force JSON output
      --session <SESSION>      Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --text                   Force concise text output
  -h, --help                   Print help

telex close

Close a message/thread (terminal)

Usage: telex close [OPTIONS] --id <ID>

Options:
      --backend <BACKEND>      Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --id <ID>                Message id to disposition
      --db <DB>                Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --note <NOTE>            Optional note recorded with the disposition
      --address <ADDRESS>      Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --recipient <RECIPIENT>  Recipient address whose disposition this is (defaults to the message's to_addr)
      --json                   Force JSON output
      --session <SESSION>      Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --text                   Force concise text output
  -h, --help                   Print help

telex escalate

Escalate a message

Usage: telex escalate [OPTIONS] --id <ID>

Options:
      --backend <BACKEND>      Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --id <ID>                Message id to disposition
      --db <DB>                Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --note <NOTE>            Optional note recorded with the disposition
      --address <ADDRESS>      Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --recipient <RECIPIENT>  Recipient address whose disposition this is (defaults to the message's to_addr)
      --json                   Force JSON output
      --session <SESSION>      Stable session identity for daemon membership [env: TELEX_SESSION_ID=]
      --text                   Force concise text output
  -h, --help                   Print help

telex address

Address directory operations

Usage: telex address [OPTIONS] <COMMAND>

Commands:
  list    List addresses with description, occupancy, and liveness
  show    Show detail for one address (uses --address)
  retire  Retire an address (drops from normal listings)
  help    Print this message or the help of the given subcommand(s)

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex address list

List addresses with description, occupancy, and liveness

Usage: telex address list [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --scope <SCOPE>      Limit to addresses in this scope
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --match <MATCH>      Substring match against address or description
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --tag <TAG>          Match a tag (substring of the tags field)
      --all                Include retired addresses
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex address show

Show detail for one address (uses --address)

Usage: telex address show [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex address retire

Retire an address (drops from normal listings)

Usage: telex address retire [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex resolve

Resolve target address(es) by description match or tag

Usage: telex resolve [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --match <MATCH>      Substring to match against address or description
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --tag <TAG>          Tag to match
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --scope <SCOPE>      Limit to a scope
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex backend

Manage configured backends (named profiles in ~/.telex/config.toml)

Usage: telex backend [OPTIONS] <COMMAND>

Commands:
  add      Add (or update) a named backend
  list     List configured backends
  show     Show one backend's configuration (secrets redacted)
  remove   Remove a configured backend
  default  Set the default backend
  kinds    List the backend kinds compiled into this build
  help     Print this message or the help of the given subcommand(s)

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex backend add

Add (or update) a named backend

Usage: telex backend add [OPTIONS] <NAME>

Arguments:
  <NAME>  Name (key) for this backend

Options:
      --backend <BACKEND>
          Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --sqlite
          Configure a SQLite backend (path defaults to ~/.telex/telex.db)
      --db <DB>
          Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --postgres <CONN>
          Configure a Postgres backend from this connection string (libpq URI or key=value DSN)
      --address <ADDRESS>
          Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --path <PATH>
          SQLite file path (with --sqlite)
      --json
          Force JSON output
      --schema <SCHEMA>
          Postgres schema to isolate telex tables in
      --password-env <PASSWORD_ENV>
          Read the Postgres password from this environment variable
      --text
          Force concise text output
      --password-command <PASSWORD_COMMAND>
          Obtain the Postgres password by running this shell command (its stdout)
      --entra
          Use Microsoft Entra auth for Postgres (token fetched via the Azure SDK)
      --entra-cred <MODE>
          Entra credential mode: auto (dev/CLI login), cli, or managed (devbox/VM identity)
      --entra-scope <ENTRA_SCOPE>
          Override the Entra token scope
      --default
          Make this the default backend
  -h, --help
          Print help

telex backend list

List configured backends

Usage: telex backend list [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex backend show

Show one backend's configuration (secrets redacted)

Usage: telex backend show [OPTIONS] <NAME>

Arguments:
  <NAME>  Backend name

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex backend remove

Remove a configured backend

Usage: telex backend remove [OPTIONS] <NAME>

Arguments:
  <NAME>  Backend name

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex backend default

Set the default backend

Usage: telex backend default [OPTIONS] <NAME>

Arguments:
  <NAME>  Backend name

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex backend kinds

List the backend kinds compiled into this build

Usage: telex backend kinds [OPTIONS]

Options:
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --address <ADDRESS>  Address to operate on (default for commands that act on one address) [env: TELEX_ADDRESS=]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

telex export

Export messages and disposition history as JSON lines

Usage: telex export [OPTIONS]

Options:
      --address <ADDRESS>  Limit to messages to/from this address (defaults to the global --address)
      --backend <BACKEND>  Configured backend to use, by name (default: the configured default backend) [env: TELEX_BACKEND=]
      --db <DB>            Override the SQLite path for this invocation (sqlite backends only) [env: TELEX_DB=]
      --thread <THREAD>    Limit to a thread id
      --since <SINCE>      Only messages with id greater than this [default: 0]
      --json               Force JSON output
      --text               Force concise text output
  -h, --help               Print help

Advanced commands

The telex copilot and telex daemon command families serve the plugin adapter and operators, and are omitted from the list above. Document them from the binary with telex copilot --help and telex daemon --help.

Delivered message shape

telex wait prints one delivered message as JSON on exit 0. With --out-dir it writes the same message to message.json, and an envelope form to delivery.json ({ "message": {...}, "delivery": {...}, "status": {...} }). A pull-mode script or harness parses this to drive an agent.

Example

Output of telex wait --json for one delivered message:

{
  "id": 118,
  "thread_id": 118,
  "parent_id": null,
  "from": "demo:jsondoc",
  "to": "demo:jsondoc",
  "primary_to": "demo:jsondoc",
  "delivered_to": "demo:jsondoc",
  "delivery_role": "to",
  "cc": [],
  "kind": "note",
  "attention": "next-checkpoint",
  "requires_disposition": true,
  "requires_disposition_for_current_recipient": true,
  "subject": "Hello",
  "body": "world",
  "sent_at_ms": 1783355088938,
  "buffered_at_ms": 1783355089013,
  "lease_epoch": 1
}

Fields

FieldMeaning
idMessage id. Use it with ack, handle, read, and for dedupe.
thread_idRoot message id of the thread.
parent_idParent message id for a reply, otherwise null.
fromSender address; a reply routes here.
toThe address the message was sent to.
primary_toThe primary recipient address.
delivered_toThe recipient address this delivery is for.
delivery_roleto or cc: this recipient’s role in the message.
ccCC (observer) addresses.
kindMessage kind label (default note).
attentioninterrupt, next-checkpoint, background, or fyi.
requires_dispositionWhether the sender marked the message as requiring disposition.
requires_disposition_for_current_recipientThe same, scoped to this recipient.
subjectSubject, if any.
bodyMessage body.
sent_at_ms, buffered_at_msUnix millisecond timestamps.
lease_epochThe recipient station’s lease epoch at delivery.

Fields whose names end in _ms such as backend_ms, send_to_exit_ms, and waiter_exit_ms are timing diagnostics, not message content.

Consume by id

Delivery is at-least-once, so dedupe by id and ack it to consume it from the delivery buffer:

telex ack --address <addr> --id <id> --session <session-id>

Files written by --out-dir

  • message.json: the flat delivered message (exit 0 only).
  • delivery.json: the envelope { message, delivery, status } (exit 0 only).
  • status.json: { outcome, exit_code, detail, ... } (always).
  • exit.code: the integer exit code, written last as the completion marker.

See Exit codes.

Exit codes

telex wait uses distinct exit codes so a caller can react without parsing output. When a waiter writes --out-dir, the integer code is also written to exit.code (last, as the completion marker); trust that artifact rather than a detached task’s reported exit code.

ExitMeaningWhat to do
0DeliveredRead delivery.json (or message.json), ack and dedupe by id, then re-arm a fresh wait before longer processing.
1ErrorUnexpected failure. Inspect stderr and status.json.
2Idle timeoutNothing arrived before --timeout-ms. Re-arm if still attending.
3Daemon gone / not runningRun telex attach (the spawning and recovery verb), then re-arm.
4Daemon hung / no response after the --timeout-ms + --hang-ms watchdogRe-arm, or restart the daemon if it repeats.
5Presence endedNon-destructive reap. A live session should attach and wait again.

wait does not spawn a missing daemon; that is what attach is for. If a replacement daemon already exists, a wait can reconnect and re-register during its bounded reconnect grace.

For the authoritative flags of any command, run its --help (see the CLI reference). The hidden telex copilot and telex daemon families are documented by telex copilot --help and telex daemon --help.

Global options

Global options apply to all subcommands and are set before the subcommand, for example telex --backend prod inbox.

OptionPurpose
--backend <name>Use a configured backend by name (or $TELEX_BACKEND). Defaults to the configured default backend, or an implicit default SQLite store.
--db <path>Override the SQLite path for this invocation (SQLite backends only; or $TELEX_DB).
--address <addr>Default address (or $TELEX_ADDRESS) for commands that act on one address; also a from fallback for send and reply.
--json / --textOutput format. Defaults to JSON when stdout is not a TTY, text when interactive.

Relevant environment variables

  • TELEX_SESSION_ID: stable session identity for daemon membership.
  • TELEX_ADDRESS: default address.
  • TELEX_BACKEND: default backend by name.
  • TELEX_DB: SQLite path override.

Postgres connections are configured once as named backends with telex backend add (see Backends), not through per-call environment variables.

For the full, version-matched flag set of any command, run its --help; see the CLI reference.

Design and internals

This guide covers using and operating telex. The design and implementation are documented separately in the repository:

The forward-looking discovery and dispatch proposals live in DISPATCH.md and EXTENSIONS.md.