| name | telex |
| description | Coordinate messages between AI agent sessions with telex. |
Telex skill
What telex is
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 auditable disposition records. Use the single binary as telex (telex.exe on Windows).
Your operator will tell you which address to attach to. You can reload these instructions anytime with telex skill, or telex skill --address <addr> for instructions tailored to your assigned address.
Harness-specific delivery
Some agent harnesses have a native telex integration that delivers messages to you
as turns (push delivery) instead of requiring you to run a pull waiter. If your
harness has one, load its version-matched instructions from the installed binary and
follow those instead of the generic pull loop below:
telex <harness> skill
The pull workflow below is the generic path for harnesses without a native
integration (scripts, CI, and any harness where a push bridge is unavailable).
Pull mode: telex wait
telex wait is the generic pull primitive for scripts, CI, and harnesses
without an in-session push integration.
Use Telex as a one-shot command loop backed by an auto-spawned per-user local
exchange (daemon). Sessions no longer run a resident holder process. attach
registers your session/address with the local exchange and exits; wait blocks
as one daemon client for one delivery and exits; ack is the explicit durable
consumed mark for the message you just received.
Before attaching, make sure the session has a stable identity. Generic telex
commands use --session or $TELEX_SESSION_ID; harness-specific integrations may
map the harness's own session id for you. Telex fails closed rather than guessing.
-
Register your session's address once:
export TELEX_SESSION_ID=<stable-session-id>
telex attach --address <addr> --description "<what this session is doing>"
Generic telex commands need the session id on each invocation, so either set
TELEX_SESSION_ID in the shell that invokes telex or pass --session <id>
explicitly:
telex --address <addr> wait --session <id> --out-dir <dir>
Detached waiter stdout may not be delivered to the agent, so read message.json /
delivery.json from --out-dir after the completion wake.
Optional metadata:
telex attach --address <addr> --session <id> --description "<s>" --scope <s> --tags <a,b> --watch-pid anchor:<pid>
--watch-pid is a non-destructive liveness backstop. The v1 floor is a loader
anchor pid with a start-time reuse guard; when it dies, blocked waits return
PresenceEnded but the station and durable message buffer remain.
-
Wait for one message with a single-shot background telex wait. Prefer a
backgrounded/detached wait for UX: a foreground waiter ties up the session,
while a background task that wakes the session on completion lets normal work
continue. Pass --out-dir <dir> so the waiter writes its result to files; read
those instead of relying on captured stdout, since some harnesses do not return
background stdout to the agent:
telex wait --address <addr> --session <session-id> --out-dir <dir>
On exit, telex wait --out-dir writes into <dir>:
message.json — the delivered message (only on exit 0);
delivery.json — envelope { message, delivery, status } (only on exit 0);
status.json — , always;
The re-arm pattern (one wait per delivery, not a shell loop)
Drive the loop from your own turn cycle:
once: telex attach --address <addr> --session <session-id> --description "<s>"
then repeat:
1. start one detached background command named `TELEX MESSAGE WAITER`:
while focused on other work: `telex wait --address <addr> --session <session-id> --timeout-ms <T> --min-attention interrupt --out-dir <dir>`
while observing a table: `telex wait --address <addr> --session <session-id> --timeout-ms <T> --wake-on-cc --out-dir <dir>`
while idle/ready for anything: `telex wait --address <addr> --session <session-id> --timeout-ms <T> --out-dir <dir>`
2. it blocks until one message, exits, and the runtime completion wakes you
3. read `<dir>\exit.code` (not the shell task exit code):
0 -> parse `delivery.json` (or `message.json`), run `telex ack --session <session-id>`, dedupe by id, then start a fresh wait before longer processing
5 -> attach/wait again if the session is still live
2/3/4 -> re-arm or restart as indicated above (see `status.json` for detail)
Two-phase attention loop
When you are actively working, arm a phase-1 waiter with
--min-attention interrupt. It wakes only for urgent messages; next-checkpoint,
background, and fyi messages stay durably buffered for your next checkpoint.
When you finish the current unit of work or reach a natural checkpoint, do phase
2: inspect telex inbox --all --address <addr>, read/ack/disposition the
pending messages you are ready to handle, then either continue with an
interrupt-only waiter or, if you are idle, arm an unfiltered waiter.
--wake-on-cc composes with this pattern. Use it only for seats that deliberately
want live CC observer traffic to wake them, and combine it with --min-attention
when the observer should wake only for urgent table traffic. After a CC wake,
inspect inbox --all or the thread if you need surrounding observer context.
Do not run an interrupt-only waiter and an unfiltered waiter at the same time:
the daemon permits only one live waiter per station. To switch modes, let the
current waiter complete, or stop the station (telex station stop), re-attach,
and arm the new mode.
Gotcha — the invisible-loop trap. Do not wrap telex wait in an
infinite background loop (while true; do telex wait; done). Many agent
runtimes surface output only when the command completes; an internal loop hides
delivered messages in a background buffer.
Tearing down a waiter
wait writes <dir>/wait.pid at startup. To tear down the station before a
message arrives, prefer telex station stop --address <addr>: it releases the
station and waits for the live waiter to exit. The PID file is a diagnostic
fallback only; do not hunt OS process lists unless station stop reports a
still-live waiter after its grace window.
Do not use a task-list/status view as the source of truth for whether the
waiter is armed or finished — a background command can show as completed while
its child is still alive. The completion wake plus the exit.code artifact are the
authoritative signal.
Shape note: wait --out-dir/message.json is the flat delivery message for
back-compat. delivery.json is the envelope form (message, delivery,
status) and is closer to read --id, which returns an enveloped
{ message, dispositions, ... } shape.
Teardown and upgrade
Use telex station stop --address <addr> as the symmetric inverse of the
attach + wait loop. It marks the station non-attending, releases membership
durably, and waits for tracked live waiters to exit. After it returns with
waiters_after: 0, a later message to the address remains queued until a future
attach/wait; it is not consumed by an orphan waiter.
For turn-end guards or resume reconciliation, use
telex station status --session <id> to get a compact JSON projection of the
current session's attended addresses, waiter counts, station health, and pending
unconsumed counts.
For a local binary upgrade on Windows, use this order:
telex station stop --address <addr>
telex daemon stop --drain
telex attach --address <addr> --description "<s>"
telex wait --address <addr> --out-dir <dir>
If the session resumes without an armed waiter, recovery is durable: inspect
telex inbox --address <addr> and telex read --id <id>, then arm a fresh
detached wait.
Sending and finding other sessions
Your identity — the from address. Every send/reply stamps a from so replies can route
back to you. It resolves inside the local exchange from explicit --from, else
$TELEX_ADDRESS / the global --address, else the single address your
TELEX_SESSION_ID currently attends. If your session attends multiple addresses,
the send is refused as ambiguous until you pass --from.
Guardrails the binary enforces:
- Unknown session/address returns
NeedsAttach. If the daemon does not know
your (store_key, session_id, from-address), the CLI re-registers when it has
enough identity, otherwise it fails actionably.
- Ambiguous inference is refused. If your session attends more than one address
and pass no
--from/env, the send is refused rather than guessing.
- Explicit
--from must be attended. A same-user process can operate under
the v1 trust model, but the daemon still validates that the named session
attends the explicit sender address before using it.
Use explicit --from when you attend multiple addresses or when the harness needs
to re-register after a daemon restart. Do not rely on a silent from = None.
Find targets by their self-registered attach descriptions, scope, or tags.
telex address list --scope <scope>
telex address list --match "<substring>"
telex resolve --match "<substring>"
telex resolve --tag <tag> --scope <scope>
Then send to the selected address.
For every agent-authored operational send or new conversation, provide a concise,
non-empty --subject. The subject is the human/operator scan surface in timelines,
operator views, and message lists, so choose a short title that communicates the
outcome, requested action, or topic at a glance. Do not substitute an opaque message
kind or copy the first line of the body. Good subjects include:
PR #123 ready for review
CI failure needs repair
Issue #45 blocked on scope decision
PR #123 merged; stand down
telex send --to <addr> --subject "<subject>" --body "<body>"
telex send --to <addr> --subject "<subject>" --body-file <path>
telex send --to <addr> --subject "<subject>" --body-stdin
Useful send flags:
telex send --to <addr> --from <your-addr> --subject "<s>" --body "<s>" --cc <a,b> --cc <c> --kind <s> --attention interrupt|next-checkpoint|background|fyi --requires-disposition --metadata <json>
--body, --body-file, and --body-stdin are mutually exclusive and exactly one is required.
Prefer --body-file or --body-stdin for non-trivial or multiline content — Markdown, code blocks,
JSON, quoted command output — to avoid shell quoting headaches and command-line length limits. Files
are read as UTF-8 and sent exactly as written (no trimming, so trailing newlines are preserved).
--body-file - and --body-stdin are equivalent: both read the body from stdin.
telex send --to <addr> --subject "Status" --body-file message.md --requires-disposition
echo "Status update" | telex send --to <addr> --subject "Status" --body-stdin
Windows / PowerShell — UTF-8 safety: piped stdin must be UTF-8. Before piping non-ASCII
content (accents, emoji, CJK characters), ensure PowerShell outputs UTF-8:
$OutputEncoding = [System.Text.Encoding]::UTF8
"Statut: café ✓" | telex send --to <addr> --subject "Statut" --body-stdin
For generated bodies, writing to a UTF-8 file first is the most reliable cross-platform path:
$body | Out-File -Encoding utf8 body.txt
telex send --to <addr> --subject "Status" --body-file body.txt
send prints a receipt: delivered, queued-unoccupied, or rejected-retired, plus the new message id.
A queued-unoccupied receipt is durable: the message is persisted and
delivered by a later telex wait once a station re-attends the address. Delivery
is at-least-once: printing a message is transport only, and the message remains
eligible until the agent runs telex ack --id <id> --address <recipient>. Ack is
per recipient, so acking a message for address A never consumes the same
message_id for cc recipient B. Terminal workflow dispositions (handle,
reject, close) remain the way to close the work after ack/processing.
Reply inside an existing thread:
telex reply --to-message <message-id> --body "<body>"
telex reply --to-message <message-id> --body-file <path>
telex reply --to-message <message-id> --body-stdin
telex reply --to-message <message-id> --subject "CI failure needs repair" --body "<body>"
When the parent already has a useful subject, the inherited Re: ... is sufficient.
When the parent subject is blank, vague, or misleading, pass a meaningful replacement
with --subject instead of perpetuating an unhelpful thread title.
Optional reply flags are --body-file <path> / --body-stdin (UTF-8 file or stdin; mutually exclusive with --body, exactly one of the three required), --from <your-addr>, --subject <s>, --cc <a,b> / repeated --cc <c>, --kind <s>, --attention interrupt|next-checkpoint|background|fyi, and --requires-disposition. As with send, --from defaults to $TELEX_ADDRESS / --address; the reply's destination is taken from the parent message's sender (so the parent must itself have had a from). On Windows/PowerShell, see the UTF-8 piping note above.
Reading
List actionable and recent messages for an address:
telex inbox --address <addr>
telex inbox --address <addr> --all --limit N
Read a message, with compact thread context when useful:
telex read --id <message-id> --thread
Use --full only when you need full history:
telex read --id <message-id> --full
Global options
Global options apply to all subcommands.
| 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/reply (which otherwise default from to the live local station you hold). |
--json / --text | Output format; default JSON when stdout is not a TTY, text when interactive. |
Postgres connections are configured once as named backends with telex backend add (see Backends), not via per-call environment variables.
Command reference
PRESENCE
| Command | Purpose | Key flags |
|---|
telex attach | One-shot register: attach this session to the address through the local exchange, claim the epoch lease, and register directory metadata. Exits immediately. | --address <addr>, --session <id> (or $TELEX_SESSION_ID), --description <s>, --scope <s>, --tags <a,b>, --watch-pid anchor:<pid> |
telex detach | One-shot detach: drop this session's in-memory membership and release epoch ownership non-destructively. | --address <addr>, --session <id> |
telex station status | Machine-readable projection of one session's attended addresses and waiter health. | --session <id> |
telex station stop | Stop a station and drain tracked waiters before detach. | --address <addr>, --session <id>, --wait-grace-ms N |
RECEIVE
| Command | Purpose | Key flags |
|---|
telex wait | Block on the local exchange; on delivery print one message as JSON and exit. Does not spawn a missing daemon; run attach first or after exit 3. Use --min-attention interrupt while focused; add --wake-on-cc only for explicit observer/relay wake. | --address <addr>, --session <id>, --timeout-ms N, --min-attention <level>, --wake-on-cc, --reconnect-grace-ms N |
telex inbox | List actionable messages requiring disposition and recent messages for the address. | --address <addr>, --all, --limit N |
telex read | Read a message. --thread shows compact thread context; --full shows full history. | --id <message-id>, --thread, --full |
SEND
| Command | Purpose | Key flags |
|---|
telex send | Send through the local exchange and print a delivery/queue/reject receipt plus message id. from must be an attended address for the session (or unambiguous from membership). --cc accepts repeated flags and comma-separated values. | --session <id>, --to <addr>, --from <addr>, --subject <s>, --body <s>, --body-file <path>, --cc <a,b>, --cc <c>, --kind <s>, `--attention interrupt |
telex reply | Reply under a parent message thread through the local exchange, optionally with CC visibility recipients. | --session <id>, --to-message <id>, --body <s>, --body-file <path>, --from <addr>, --subject <s>, --cc <a,b>, --cc <c>, --kind <s>, `--attention interrupt |
DISPOSITION
| Command | Purpose | Key flags |
|---|
telex ack | Explicitly mark the delivered (message_id, recipient address) consumed in the daemon delivery buffer. | --address <addr> or --recipient <addr>, --session <id>, --id <message-id> |
telex handle | Mark the message handled. | --id <message-id>, --note <s> |
telex defer | Mark the message deferred. | --id <message-id>, --note <s> |
telex reject | Mark the message rejected. | --id <message-id>, --note <s> |
telex close | Mark the message closed. | --id <message-id>, --note <s> |
telex escalate | Mark the message escalated. | --id <message-id>, --note <s> |
DIRECTORY
| Command | Purpose | Key flags |
|---|
telex address list | Show addresses with description, occupancy, and liveness grade. | --scope <s>, --match <substr>, --tag <t>, --all |
telex address show | Show detail for one address plus lease/occupancy. | --address <addr> |
telex address retire | Retire an address so it drops from normal listings. | --address <addr> |
telex resolve | Resolve target addresses by description substring or tag. | --match <substr> or --tag <t>, --scope <s> |
AUDIT
| Command | Purpose | Key flags |
|---|
telex export | Emit messages and disposition history as JSON lines for audit/provenance. | --address <addr>, --thread <id>, --since <id> |
SETUP
| Command | Purpose | Key flags |
|---|
telex init | Create ~/.telex/, write a default sqlite backend, and initialize its schema. | --backend <name>, --db <path> |
telex status | Show the resolved backend/address projection. Use hidden telex daemon status for daemon internals. | --address <addr> |
telex skill | Print these usage instructions from the embedded single source. | --address <addr>, --raw |
BACKENDS
| Command | Purpose | Key flags |
|---|
telex backend add <name> | Add (or update) a named backend. | --sqlite [--path <p>] or --postgres <conn-string> [--schema <s>] plus auth: --entra [--entra-cred auto|cli|managed], --password-env <VAR>, or --password-command <cmd>; --default |
telex backend list | List configured backends and the default. | |
telex backend show <name> | Show one backend's config (secrets redacted). | |
telex backend default <name> | Set the default backend. | |
telex backend remove <name> | Remove a backend. | |
telex backend kinds | List backend kinds compiled into this build. | |
Attention levels
Use exactly one of:
interrupt | next-checkpoint | background | fyi
Meanings:
interrupt — deliver as soon as possible, at the next agent turn boundary.
next-checkpoint — handle after the current safe stopping point.
background — visible in inbox, but should not derail current work.
fyi — visible/auditable, non-actionable by default.
Disposition states
States are:
acknowledged | handled | deferred | rejected | closed | escalated
Terminal states, removed from the actionable inbox: handled, rejected, closed.
Non-terminal states, still needing final disposition: acknowledged, deferred, escalated.
Latency
In pull mode, agent wake dominates perceived latency: the time from a waiter exiting
to the agent acting on the delivered message (harness-dependent, typically seconds to
tens of seconds) far exceeds backend delivery, which is sub-second. interrupt raises a
message above --min-attention interrupt filtering so a focused waiter wakes for it, but
it still resolves at the next wait completion / turn boundary rather than preempting
in-flight work. Harnesses with a native push integration may map attention onto
lower-latency delivery -- see that harness's skill (telex <harness> skill).
Backends
A backend is a named, configured store (a "key"). Selection is by name:
--backend <name> → $TELEX_BACKEND → the configured default → an implicit default
sqlite store at ~/.telex/telex.db. So with no setup, telex just works on local SQLite.
Configure backends once with telex backend add. The first one added becomes the default;
--default (or telex backend default <name>) changes it.
telex backend add local --sqlite
telex backend add staging --postgres "postgresql://app@staging-db:5432/telex?sslmode=require" \
--password-env STAGING_PG_PASSWORD --schema telex
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 telex build with the entra feature (the published release binaries
include it). On a laptop it uses your az login; on a devbox/VM with a managed identity use
--entra-cred managed for zero-login setup. As an alternative on builds without entra, you
can supply the token yourself via --password-command (e.g. az account get-access-token ...).
Then select a backend per command, or rely on the default:
telex --backend staging inbox
telex send --to node:x --subject "Ping node x" --body "hi"
telex backend list
The Postgres connection string is a libpq URI or a key=value DSN. Provide the password by
reference (--entra, --password-env, or --password-command) rather than embedding it.
--entra (Azure SDK; supports az login and managed identity) is available in builds with
the entra feature — which the published release binaries include.
Worked example: two sessions
Session A attaches to a durable address and waits. attach is one-shot; only
wait blocks in the background.
export TELEX_ADDRESS=session:a
export TELEX_SESSION_ID=session-a
telex attach --address session:a --description "session A waiting for coordination" --scope project:telex --tags repo:telex,role:worker
Then A starts a single-shot background telex wait; it completes on the next delivery, which notifies A:
telex wait --address session:a
Session B also registers its address.
export TELEX_SESSION_ID=session-b
telex attach --address session:b --description "session B requesting status" --scope project:telex --tags repo:telex,role:requester
Then Session B finds A and sends a disposition-required message. One-shot commands like resolve and send run directly — no background task needed.
telex address list --scope project:telex --match "session A"
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
A's wait command completes (exit 0) with the delivered message as JSON, which notifies A. A saves the JSON, acks + dedupes by message id, immediately re-arms a fresh background wait before longer processing, then handles the work, dispositions it, and replies in the same thread.
telex ack --address session:a --id <message-id-from-wait-json>
telex wait --address session:a
telex handle --id <message-id-from-wait-json> --note "status prepared"
telex reply --to-message <message-id-from-wait-json> --body "Status: holder is live; continuing work." --attention next-checkpoint
Session B waits, receives A's reply, and closes or handles it.
telex wait --address session:b
telex ack --address session:b --id <reply-message-id-from-wait-json>
telex handle --id <reply-message-id-from-wait-json> --note "reply received"