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).
Print the agent usage guide
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
interrupttofyi, 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 examplerepo: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:
attachregisters the session and address, then exits.waitblocks 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:
| Recipient | delivery_role | delivered_to | requires disposition |
|---|---|---|---|
node:worker (to) | to | node:worker | set when the sender passes --requires-disposition |
node:lead (cc) | cc | node:lead | no |
--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 (usesaz login, or--entra-cred managedon a devbox or VM with a managed identity). Requires a build with theentrafeature, 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--dbor$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 detachor 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 (
--sessionor$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 waitclient blocked for a single delivery. - Attention: the urgency of a message:
interrupt,next-checkpoint,background, orfyi. 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_idandparent_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:
- Arm one
wait(optionally with--min-attention interruptwhile focused). - It blocks until one message, exits, and the runtime wakes the agent.
- Read the result,
ack, dedupe by id, then arm a freshwaitbefore 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
--schemato 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
| Command | Effect |
|---|---|
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 --drain | Stop the local exchange after draining in-flight work. |
telex copilot detach | Copilot 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
- Stop the daemon:
telex daemon stop --drain. - Remove local state: delete
~/.telex/(the SQLite store and config). - Remove the Copilot plugin, if installed:
copilot plugin uninstall telex@telex. - 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
telexbinary (telex 0.1.1 (build bfa8f64a74201255572ae297e4caca8063e33619)) bydocs/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
| Field | Meaning |
|---|---|
id | Message id. Use it with ack, handle, read, and for dedupe. |
thread_id | Root message id of the thread. |
parent_id | Parent message id for a reply, otherwise null. |
from | Sender address; a reply routes here. |
to | The address the message was sent to. |
primary_to | The primary recipient address. |
delivered_to | The recipient address this delivery is for. |
delivery_role | to or cc: this recipient’s role in the message. |
cc | CC (observer) addresses. |
kind | Message kind label (default note). |
attention | interrupt, next-checkpoint, background, or fyi. |
requires_disposition | Whether the sender marked the message as requiring disposition. |
requires_disposition_for_current_recipient | The same, scoped to this recipient. |
subject | Subject, if any. |
body | Message body. |
sent_at_ms, buffered_at_ms | Unix millisecond timestamps. |
lease_epoch | The 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.
| Exit | Meaning | What to do |
|---|---|---|
| 0 | Delivered | Read delivery.json (or message.json), ack and dedupe by id, then re-arm a fresh wait before longer processing. |
| 1 | Error | Unexpected failure. Inspect stderr and status.json. |
| 2 | Idle timeout | Nothing arrived before --timeout-ms. Re-arm if still attending. |
| 3 | Daemon gone / not running | Run telex attach (the spawning and recovery verb), then re-arm. |
| 4 | Daemon hung / no response after the --timeout-ms + --hang-ms watchdog | Re-arm, or restart the daemon if it repeats. |
| 5 | Presence ended | Non-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.
| Option | Purpose |
|---|---|
--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 / --text | Output 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:
- DESIGN.md: the working design.
- DECISIONS.md: the decision log (ADRs).
- ARCHITECTURE.md: the component architecture.
- daemon.md: the local exchange daemon.
- copilot-bridge-push.md: Copilot CLI push delivery.
The forward-looking discovery and dispatch proposals live in DISPATCH.md and EXTENSIONS.md.