Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
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 # e.g. in Copilot CLI: telex copilot 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 or ; harness-specific integrations may
map the harness's own session id for you. Telex fails closed rather than guessing.
--session
$TELEX_SESSION_ID
Register your session's address once:
export TELEX_SESSION_ID=<stable-session-id> # often already supplied by the harness
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:
--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:
exit.code — the integer exit code, written last as the completion marker.
Trust the artifact exit.code as the completion marker rather than a background
task's reported exit code, which may describe only a launcher/wrapper. On the
completion wake, read exit.code (then delivery.json or message.json if it is
0), ack the delivered message with the same session id, dedupe by id, then
re-arm a fresh wait with the same session id before longer processing.
Do not hide wait inside an infinite shell loop.
wait does not spawn the daemon. If the daemon is gone, wait exits 3 so
the agent can run telex attach (the spawning/recovery verb) and then re-arm.
If a replacement daemon already exists, wait can reconnect/re-register during
its bounded reconnect grace.
CC traffic is pull-only by default: it remains visible in inbox --all /
read, but a bare wait does not wake for it. If this seat is deliberately
acting as an observer/relay for a table and should be woken by live CC traffic,
arm that wait with --wake-on-cc. This is an explicit per-wait opt-in, not CC
backlog replay and not a manual-ack requirement.
Exit
Meaning
What you do
0
delivered
Read delivery.json (or message.json/stdout JSON), ack --session <session-id> + dedupe by id, then re-arm a fresh wait --session <session-id> before longer processing.
2
idle-timeout
Nothing arrived before --timeout-ms; re-arm if still attending.
3
daemon gone / not running
Run telex attach and re-arm.
4
daemon hung / no response after a finite wait's --timeout-ms + --hang-ms watchdog
Re-arm or restart the daemon if repeated.
5
presence ended
Non-destructive reap; live sessions should attach/wait again.
After reading the delivered JSON, explicitly ack it, then apply the workflow
disposition that reflects the actual outcome:
ack is transport consumption for (message_id, recipient-address). Terminal
workflow disposition is still handle, reject, or close; defer and
escalate are non-terminal. Dispositions default to the current --address
recipient; pass --recipient only when intentionally recording for another
recipient.
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:
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> # body from a UTF-8 file (`-` = 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 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.
# Recommended for multiline/structured messages:
telex send --to <addr> --subject "Status" --body-file message.md --requires-disposition
# Pipe a body from stdin: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:
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:
# A meaningful parent subject is inherited as "Re: ...".
telex reply --to-message <message-id> --body "<body>"
telex reply --to-message <message-id> --body-file <path> # reply body from a UTF-8 file (`-` = stdin)
telex reply --to-message <message-id> --body-stdin # read reply body from stdin (UTF-8)# Replace a blank, vague, or misleading parent subject.
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:
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.
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.
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.
# Local sqlite (usually unnecessary — it's the implicit default):
telex backend add local --sqlite
# Networked Postgres, 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 (token fetched on demand by telex itself):
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:
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 # optional: defaults A's wait/inbox to this addressexport TELEX_SESSION_ID=session-a # often supplied by the agent harness
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
# B's send below stamps from=session:b automatically because B attends that address.
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 # start this as a fresh background wait after ack/dedupe
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.