name: convos-cli description: Use when working with Convos messaging - privacy-focused ephemeral messaging with per-conversation identities, invites, profiles, and group management via the convos CLI tool
Convos CLI
The Convos CLI (convos) is a command-line tool for privacy-focused ephemeral messaging built on XMTP. Unlike standard XMTP, Convos creates a unique identity per conversation so conversations cannot be linked or correlated.
Key differences from standard XMTP:
- Per-conversation identities: Each conversation gets its own wallet, inbox, and database
- No global wallet key: Identities are created automatically when creating/joining conversations
- Invite system: Serverless QR code + URL invites for joining conversations
- Per-conversation profiles: Different display name and avatar in each conversation
- Explode: Permanently destroy a conversation and all cryptographic keys
- Lock: Prevent new members from being added
Prerequisites
Initialize Configuration
# generate config and save to default path (~/.convos/.env)
convos init
# output config to console instead of writing to file
convos init --stdout
# initialize for production environment
convos init --env production
# overwrite existing config
convos init --force
# initialize with a custom data directory (useful for multiple agents)
convos init --home /path/to/agent1-data
# or use the CONVOS_HOME environment variable
CONVOS_HOME=/path/to/agent1-data convos init
This creates a .env file with:
CONVOS_ENV- Network environment (local, dev, production)CONVOS_API_KEY- Agent API key for uploads (auto-selectsconvos-apiprovider)CONVOS_UPLOAD_PROVIDER- Upload provider override (convos-api,pinata,s3)
Note: Unlike standard XMTP, there is no global wallet key. Each conversation creates its own identity stored in ~/.convos/identities/.
Custom Data Directory
By default, all data is stored in ~/.convos/. To use a different directory (e.g., when running multiple agents on one machine), use the --home flag or the CONVOS_HOME environment variable:
# via flag (works on any command)
convos conversations list --home /path/to/agent-data
# via environment variable
export CONVOS_HOME=/path/to/agent-data
convos conversations list
Priority: --home flag > CONVOS_HOME env var > ~/.convos
Configuration Loading Priority
- CLI flags (highest priority)
- Explicit
--env-file <path> .envin the current working directory<convos-home>/.env(default:~/.convos/.env)
Command Structure
convos [TOPIC] [COMMAND] [ARGUMENTS] [FLAGS]
Topics
| Topic | Purpose |
|---|---|
agent |
Agent mode — long-running sessions with streaming I/O |
identity |
Manage per-conversation identities (inboxes) |
conversations |
List, create, join, and stream conversations |
conversation |
Interact with a specific conversation |
Standalone Commands
| Command | Purpose |
|---|---|
init |
Initialize configuration and directory structure |
reset |
Delete all identities and conversation data (preserves .env) |
schema |
Introspect CLI commands as machine-readable JSON (args, flags, examples) |
Output Modes
All commands support --json for machine-readable JSON output:
convos conversations list --json
Use --fields to limit JSON output to specific fields (implicitly enables --json). Supports dot notation for nested paths:
# only get message id, content, and sender
convos conversation messages <id> --fields id,content,senderInboxId
# nested field extraction
convos conversation messages <id> --fields id,content,contentType.typeId,sentAt
# works on any command
convos conversation profiles <id> --fields profiles
convos conversations list --fields conversationId,name
Use --verbose to see detailed client initialization logs. When combined with --json, verbose output goes to stderr:
convos identity info <id> --verbose
convos conversations list --json --verbose 2>/dev/null
Common Workflows
Create a Conversation
# create a conversation (auto-creates a per-conversation identity)
convos conversations create --name "My Group" --profile-name "Alice"
# create with admin-only permissions
convos conversations create --name "Announcement Channel" --permissions admin-only
# create and capture the conversation ID
CONV_ID=$(convos conversations create --name "Test" --json | jq -r '.conversationId')
Send Messages
# send a text message
convos conversation send-text <conversation-id> "Hello, world!"
# send a reaction
convos conversation send-reaction <conversation-id> <message-id> add "👍"
# remove a reaction
convos conversation send-reaction <conversation-id> <message-id> remove "👍"
# send a reply referencing another message
convos conversation send-reply <conversation-id> <message-id> "Replying to you"
# reply with a photo
convos conversation send-reply <conversation-id> <message-id> --file ./photo.jpg
# reply with a large file (auto-uploaded via provider)
convos conversation send-reply <conversation-id> <message-id> --file ./video.mp4
# send a read receipt (silent — no visible message, no push notification)
convos conversation send-read-receipt <conversation-id>
# query last read times per member (nanosecond timestamps)
convos conversation last-read-times <conversation-id>
convos conversation last-read-times <conversation-id> --sync --json
# send a typing indicator (silent — notifies others you are typing)
convos conversation send-typing-indicator <conversation-id>
# stop typing indicator
convos conversation send-typing-indicator <conversation-id> --stop
Send Attachments
# send a photo (small files ≤1MB sent inline, large files auto-uploaded via provider)
convos conversation send-attachment <conversation-id> ./photo.jpg
# force remote upload even for small files
convos conversation send-attachment <conversation-id> ./photo.jpg --remote
# override MIME type
convos conversation send-attachment <conversation-id> ./file.bin --mime-type image/png
# use upload provider via flags (no .env needed)
convos conversation send-attachment <conversation-id> ./photo.jpg \
--upload-provider pinata --upload-provider-token <jwt>
# encrypt only — outputs encrypted file + decryption keys for manual upload
convos conversation send-attachment <conversation-id> ./photo.jpg --encrypt
# send a pre-uploaded encrypted file with decryption keys
convos conversation send-remote-attachment <conversation-id> <url> \
--content-digest <hex> --secret <base64> --salt <base64> \
--nonce <base64> --content-length <bytes> --filename photo.jpg
# download an attachment (handles both inline and remote transparently)
convos conversation download-attachment <conversation-id> <message-id>
# download to a specific path
convos conversation download-attachment <conversation-id> <message-id> --output ./photo.jpg
# save encrypted payload without decrypting
convos conversation download-attachment <conversation-id> <message-id> --raw
To enable automatic upload for large files, set your agent API key in .env:
CONVOS_API_KEY=<your-agent-api-key>
This auto-selects the convos-api upload provider. Other providers (pinata, s3) are also available via CONVOS_UPLOAD_PROVIDER.
Read Messages
# list messages (default: descending order)
convos conversation messages <conversation-id>
# sync from network and limit results
convos conversation messages <conversation-id> --sync --limit 10
Stream Messages in Real-Time
# stream messages from a single conversation
convos conversation stream <conversation-id>
# stop after 60 seconds
convos conversation stream <conversation-id> --timeout 60
List Conversations
# list all conversations across all identities
convos conversations list
# sync from network before listing
convos conversations list --sync
Invite System
Convos uses a serverless invite system. The creator generates a cryptographic invite URL; the person joining must open the URL in the Convos app (or scan the QR code); then the creator processes the join request to add them to the group.
Important: Adding someone to a conversation is a multi-step process:
- Generate an invite (creator side) — produces a URL and QR code
- Person opens the invite URL in Convos or scans the QR code — this sends a join request to the creator via DM
- Creator processes the join request — this validates the request and adds the person to the group
The creator must process join requests after the person has opened/scanned the invite. If you don't know when that will happen, use --watch with a timeout to stream and process requests as they arrive.
Inspect an Invite
# decode and inspect an invite without joining (useful for debugging)
convos conversations inspect-invite <invite-slug>
# inspect a full invite URL
convos conversations inspect-invite "https://dev.convos.org/v2?i=<slug>"
# output as JSON
convos conversations inspect-invite <slug> --json
This displays the invite's tag, creator inbox ID, conversation name, expiration dates, signature validity, and whether the invite is expired — without creating any identities or sending join requests.
Create an Invite
# generate invite — displays QR code in terminal
convos conversation invite <conversation-id>
# generate invite with 1-hour expiry
convos conversation invite <conversation-id> --expires-in 3600
# single-use invite
convos conversation invite <conversation-id> --single-use
# JSON output (suppresses QR code)
convos conversation invite <conversation-id> --json
# capture invite URL for scripting
INVITE_URL=$(convos conversation invite <conversation-id> --json | jq -r '.url')
Person Joins via Invite
The person being invited must open the invite URL in the Convos app or scan the QR code with Convos. This can be done:
- On iOS: Open the URL in Safari (redirects to Convos app) or scan the QR code from within the app
- Via CLI: Use
convos conversations join
# join using a raw invite slug
convos conversations join <invite-slug>
# join using a full invite URL
convos conversations join "https://dev.convos.org/v2?i=<slug>"
# join with a display name
convos conversations join <slug> --profile-name "Bob"
# join with a display name and avatar image
convos conversations join <slug> --profile-name "Bot" --profile-image "https://example.com/avatar.jpg"
# join with custom metadata
convos conversations join <slug> --metadata role=assistant --metadata version=2
# send join request without waiting for acceptance
convos conversations join <slug> --no-wait
# wait up to 2 minutes for acceptance
convos conversations join <slug> --timeout 120
Process Join Requests (Creator Side)
After the person has opened/scanned the invite, the creator must process the join request:
# process all pending join requests (use when you know the invite has already been opened)
convos conversations process-join-requests
# process for a specific conversation only
convos conversations process-join-requests --conversation <id>
# watch for join requests with a timeout (use when you don't know when the invite will be opened)
convos conversations process-join-requests --watch --conversation <id>
# note: use ctrl-c or a timeout to stop watching
# continuously watch for all join requests (keep running in background)
convos conversations process-join-requests --watch
Per-Conversation Profiles
Each conversation has independent profiles — you can have a different name and avatar in each.
Profile updates are sent as ProfileUpdate messages to the group. The CLI no longer writes profiles to appData (this was removed to fix a data corruption bug where concurrent read-modify-write cycles could erase invite tags and other members' profiles). When reading profiles, message-sourced profiles take precedence, with appData as a read-only fallback for profiles written by older clients (e.g., iOS).
When new members are added (via invite or directly), a ProfileSnapshot message is sent containing all current member profiles so the new joiner has everyone's data immediately — solving the MLS forward secrecy problem where older messages may be undecryptable.
Profile resolution precedence:
- Latest ProfileUpdate from that member — highest priority, most recent self-authored update
- Most recent ProfileSnapshot containing that member — fallback when no ProfileUpdate exists
- appData profiles — legacy fallback for backward compatibility with older clients
- No profile — member has no name/avatar set
Both ProfileUpdate and ProfileSnapshot are silent messages (shouldPush = false) — they do not appear in chat or trigger notifications.
# set display name
convos conversation update-profile <conversation-id> --name "Alice"
# set name and avatar
convos conversation update-profile <conversation-id> --name "Alice" --image "https://example.com/avatar.jpg"
# go anonymous (clear profile)
convos conversation update-profile <conversation-id> --name "" --image ""
# view all member profiles
convos conversation profiles <conversation-id>
convos conversation profiles <conversation-id> --json
Identity Management
Identities are created automatically when creating/joining conversations, but you can manage them directly.
# list all identities
convos identity list
# create an identity manually
convos identity create --label "Work Chat" --profile-name "Alice"
# view identity details (connects to XMTP to show inbox ID)
convos identity info <identity-id>
# remove an identity (destroys all keys — irreversible)
convos identity remove <identity-id> --force
Reset All Data
Delete all identities and conversation data. The .env configuration is preserved.
# reset with confirmation prompt
convos reset
# reset without confirmation
convos reset --force
Group Management
# view members
convos conversation members <conversation-id>
# add members by inbox ID
convos conversation add-members <conversation-id> <inbox-id>
# remove members
convos conversation remove-members <conversation-id> <inbox-id>
# update group name
convos conversation update-name <conversation-id> "New Name"
# update group description
convos conversation update-description <conversation-id> "New description"
# view permissions
convos conversation permissions <conversation-id>
Lock a Conversation
Prevent new members from joining by setting the addMember permission to deny. This also invalidates all existing invites. Only super admins can lock/unlock.
# lock
convos conversation lock <conversation-id>
# unlock (previously shared invites remain invalid — generate new ones)
convos conversation lock <conversation-id> --unlock
Explode a Conversation
Permanently destroy a conversation and all its cryptographic keys. Sends an ExplodeSettings notification to all members (so iOS and other clients can trigger their cleanup), updates group metadata with the expiration timestamp, removes all members, then destroys the local identity. Irreversible.
# explode immediately
convos conversation explode <conversation-id> --force
# schedule explosion for a future date (ISO8601)
convos conversation explode <conversation-id> --scheduled "2025-03-01T00:00:00Z"
When scheduled, the ExplodeSettings message is sent with a future expiresAt date. Members are notified but not removed — clients handle cleanup when the time arrives. When immediate (no --scheduled), members are removed and the local identity is destroyed right away.
Assistant Attestation
Cryptographically verify that an agent was provisioned by the Convos backend. Attestations use Ed25519 signatures over sha256(inboxId || timestamp).
# generate a test attestation (creates a key pair, signs, outputs JWKS)
convos attestation generate <inbox-id>
convos attestation generate <inbox-id> --kid my-key-2026 --json
# verify an attestation against a JWKS endpoint
convos attestation verify <inbox-id> \
--attestation <base64url-sig> \
--attestation-ts <iso8601> \
--attestation-kid <kid>
# verify against a raw public key
convos attestation verify <inbox-id> \
--attestation <sig> \
--attestation-ts <ts> \
--public-key <base64url-pubkey>
# verify against a local JWKS file
convos attestation verify <inbox-id> \
--attestation <sig> \
--attestation-ts <ts> \
--attestation-kid <kid> \
--jwks-file ./agents.json
Agents include attestation in their profile metadata when joining:
# agent serve with attestation (typically provided by the backend)
convos agent serve --name "Bot" \
--attestation <sig> \
--attestation-ts <ts> \
--attestation-kid <kid>
# or via environment variables
CONVOS_ATTESTATION=<sig> CONVOS_ATTESTATION_TS=<ts> CONVOS_ATTESTATION_KID=<kid> \
convos agent serve --name "Bot"
# join with attestation
convos conversations join <slug> \
--attestation <sig> \
--attestation-ts <ts> \
--attestation-kid <kid>
Sync Data from Network
# sync conversation list
convos conversations sync
# sync a single conversation
convos conversation sync <conversation-id>
Agent Mode
The agent serve command runs a long-running process that combines conversation creation, message streaming, join request processing, and stdin command handling — ideal for AI agents and bots.
Quick Start (Agent)
# create a new conversation and start serving
convos agent serve --name "My Bot" --profile-name "Assistant"
# attach to an existing conversation
convos agent serve <conversation-id>
# create with admin-only permissions
convos agent serve --name "Agent" --permissions admin-only
Protocol
The agent uses an ndjson (newline-delimited JSON) protocol:
- stdout: Events (one JSON object per line)
- stdin: Commands (one JSON object per line)
- stderr: QR code + diagnostic logs
Events (stdout)
| Event | Description | Key Fields |
|---|---|---|
ready |
Session started | conversationId, inviteUrl, inboxId |
message |
New message received | id, senderInboxId, senderProfile (optional: name, image), content, contentType, sentAt, catchup (optional) |
typing |
Member typing status changed | senderInboxId, isTyping, conversationId |
member_joined |
Member joined via invite | inboxId, conversationId, catchup (optional) |
sent |
Message sent confirmation | id, text, replyTo (optional), type (optional) |
heartbeat |
Periodic health check | conversationId, activeStreams |
error |
Error occurred | message |
Messages with catchup: true were fetched during stream reconnection (missed while disconnected).
Commands (stdin)
{"type":"send","text":"Hello, world!"}
{"type":"send","text":"Replying to you","replyTo":"<message-id>"}
{"type":"react","messageId":"<message-id>","emoji":"👍"}
{"type":"react","messageId":"<message-id>","emoji":"👍","action":"remove"}
{"type":"attach","file":"./photo.jpg"}
{"type":"attach","file":"./photo.jpg","replyTo":"<message-id>"}
{"type":"attach","file":"./photo.jpg","mimeType":"image/jpeg"}
{"type":"remote-attach","url":"https://...","contentDigest":"<hex>","secret":"<base64>","salt":"<base64>","nonce":"<base64>","contentLength":12345,"filename":"photo.jpg"}
{"type":"rename","name":"New Group Name"}
{"type":"read-receipt"}
{"type":"typing"}
{"type":"typing","isTyping":false}
{"type":"lock"}
{"type":"unlock"}
{"type":"explode"}
{"type":"explode","scheduled":"2025-03-01T00:00:00Z"}
{"type":"stop"}
| Command | Required Fields | Optional Fields |
|---|---|---|
send |
text |
replyTo |
react |
messageId, emoji |
action (add/remove, default: add) |
attach |
file (local path) |
mimeType, replyTo |
remote-attach |
url, contentDigest, secret, salt, nonce, contentLength |
filename, scheme |
rename |
name |
— |
read-receipt |
— | — |
typing |
— | isTyping (bool, default: true) |
lock |
— | — |
unlock |
— | — |
explode |
— | scheduled (ISO8601 date) |
stop |
— | — |
Small attachments (≤1MB) are sent inline. Larger files are auto-encrypted and uploaded via the configured upload provider (e.g., Pinata).
Lock prevents new members from joining by rotating the invite tag and setting addMember permission to deny. Unlock reverses this (previously shared invites remain invalid). Explode permanently destroys the conversation — sends ExplodeSettings notification, removes members, and deletes the local identity. Immediate explode triggers agent shutdown. Rename updates the conversation name visible to all members.
How It Works
When started, agent serve:
- Creates or attaches to a conversation
- Displays QR code invite on stderr (so users can scan and join)
- Emits
readyevent with conversation ID, invite URL, and identity info - Processes pending join requests from before the agent started
- Streams messages — emits
messageevents as they arrive in real-time - Streams DM join requests — automatically adds new members and emits
member_joined - Reads stdin — accepts
send,rename,lock,unlock,explode, andstopcommands - Emits heartbeat (optional) — periodic health check events when
--heartbeatis set - Catches up on reconnect — if a stream disconnects and reconnects, fetches any missed messages since the last seen timestamp
All of these run concurrently. The agent stays alive until SIGINT, SIGTERM, stdin close, a stop command, or an immediate explode.
Example: Agent Integration
# Start the agent, pipe commands in, read events out
convos agent serve --name "Bot" --profile-name "AI Assistant" | while IFS= read -r event; do
type=$(echo "$event" | jq -r '.event')
case "$type" in
ready)
echo "Bot ready! Invite URL: $(echo "$event" | jq -r '.inviteUrl')" >&2
;;
message)
content=$(echo "$event" | jq -r '.content')
echo "Received: $content" >&2
# Send a reply (write JSON command to agent's stdin)
msg_id=$(echo "$event" | jq -r '.id')
echo "{\"type\":\"send\",\"text\":\"You said: $content\",\"replyTo\":\"$msg_id\"}"
;;
member_joined)
inbox=$(echo "$event" | jq -r '.inboxId')
echo "New member: $inbox" >&2
echo "{\"type\":\"send\",\"text\":\"Welcome!\"}"
;;
esac
done
Agent Flags
| Flag | Description |
|---|---|
--name |
Conversation name (when creating new) |
--description |
Conversation description (when creating new) |
--permissions |
all-members or admin-only (when creating new) |
--profile-name |
Display name for this conversation |
--identity |
Use an existing unlinked identity |
--label |
Local label for the identity |
--no-invite |
Skip generating an invite (attach mode) |
--heartbeat |
Emit heartbeat events every N seconds (0 to disable, default: 0) |
Important Concepts
Per-Conversation Identities
Every conversation has its own:
- Wallet key (secp256k1 private key)
- DB encryption key (32-byte key)
- XMTP inbox (unique inbox ID)
- Local database (SQLite)
Identities are stored in <convos-home>/identities/<id>.json. Databases are stored in <convos-home>/db/<env>/<id>.db3. The data directory defaults to ~/.convos/ but can be overridden with --home or CONVOS_HOME.
Invite Flow
- Creator generates an invite URL/QR code (contains encrypted conversation token + creator's inbox ID)
- Person opens the invite URL in Convos app (or scans the QR code) — this creates a per-conversation identity and sends a DM join request to the creator
- Creator processes the join request — validates the invite signature, decrypts the conversation token, and adds the person to the group
- Person is now a member with their own isolated identity
Key point: Step 3 must happen after step 2. The creator must either run process-join-requests after the invite has been opened, or use --watch to stream and process requests as they arrive.
Profile Messages
Member profiles are stored as XMTP group messages using two custom content types:
ProfileUpdate(convos.org/profile_update:1.0) — sent by a member when they change their own name or avatar. The sender's inbox ID is implicit from the XMTP message, preventing spoofing.ProfileSnapshot(convos.org/profile_snapshot:1.0) — sent after adding members to a group. Contains all current member profiles so new joiners have data immediately (solves MLS forward secrecy gap).
Both are silent (no push notification, not displayed in chat). The CLI reads appData profiles as a fallback for backward compatibility with older clients, but does not write profiles there. Custom XMTP content codecs (ProfileUpdateCodec, ProfileSnapshotCodec) are registered with the XMTP client at creation time so the SDK can decode these message types natively.
Profiles support typed metadata — arbitrary key-value pairs where values can be string, number (double), or boolean. Metadata is carried in both ProfileUpdate and ProfileSnapshot messages via a map<string, MetadataValue> protobuf field. Use --metadata key=value on update-profile (repeatable, auto-typed: "true"/"false" → bool, numeric → number, else string). Metadata merges with existing values (new keys overwrite, unmentioned keys preserved).
Profile images are encrypted end-to-end using the same scheme as iOS: HKDF-SHA256 derives a per-image AES-256-GCM key from the group's imageEncryptionKey (stored in appData) + random 32-byte salt, then encrypts with a random 12-byte nonce. The encrypted blob is uploaded via the configured upload provider and the URL + salt + nonce are sent as EncryptedProfileImageRef in the ProfileUpdate message. If no imageEncryptionKey exists for the group, the CLI generates one and writes it to appData.
Supported upload providers:
- Convos API:
CONVOS_UPLOAD_PROVIDER=convos-api,CONVOS_API_KEY=<agent-assets-api-key>, optionalCONVOS_API_BASE_URL=<url>(auto-derived from XMTP env: dev →https://api.dev.convos.xyz/api, production →https://api.prod.convos.xyz/api). Uses the agent asset upload endpoint (GET /v2/agents/assets/presigned) withX-Agent-API-Keyheader auth — no JWT step needed. - Pinata (IPFS):
CONVOS_UPLOAD_PROVIDER=pinata,CONVOS_UPLOAD_PROVIDER_TOKEN=<jwt>, optionalCONVOS_UPLOAD_PROVIDER_GATEWAY=<url> - S3 (direct):
CONVOS_UPLOAD_PROVIDER=s3,CONVOS_UPLOAD_PROVIDER_TOKEN=<accessKeyId>:<secretAccessKey>,CONVOS_S3_BUCKET=<bucket>, optionalCONVOS_S3_REGION=<region>(default: us-east-1), optionalCONVOS_S3_ENDPOINT=<url>(for S3-compatible services like MinIO, R2), optionalCONVOS_UPLOAD_PROVIDER_GATEWAY=<public-url-prefix>
Join Request Messages
Join requests use a structured content type instead of plain text:
JoinRequest(convos.org/join_request:1.0) — sent as a DM to the conversation creator when joining via invite. Contains the invite slug, joiner's profile (name, image, memberKind), and optional metadata.
The CLI sets memberKind: "agent" by default on all join requests so the creator knows a bot is joining. For backward compatibility, the CLI sends both the JoinRequestContent message and a plain text slug — older clients that don't understand the new content type will read the text fallback. When processing incoming join requests, the CLI tries JoinRequestContent first, then falls back to plain text.
Consent States
| State | Meaning |
|---|---|
allowed |
Messages are welcome |
denied |
Messages are blocked |
unknown |
No decision made |
Environment Networks
| Network | Use Case |
|---|---|
local |
Local XMTP node |
dev |
Development/testing (default) |
production |
Production use |
Data Directory
The data directory defaults to ~/.convos/ but can be overridden with --home or CONVOS_HOME:
<convos-home>/ # default: ~/.convos/
├── .env # Global config (env only)
├── identities/
│ ├── <id-1>.json # Identity: wallet key, db key, conversation link
│ └── <id-2>.json
└── db/
└── dev/ # XMTP databases by environment
├── <id-1>.db3
└── <id-2>.db3
Error Handling
- Not initialized: Run
convos initto create configuration - No identities: Create a conversation or identity first
- Identity not found: Use
convos identity listto see available identities - Conversation not found: Sync first with
convos conversations sync - Permission denied: Check group permissions with
convos conversation permissions - Invite expired or invalid: Generate a new invite with
convos conversation invite
Complete Example
# 1. initialize (first time only)
convos init --env dev
# 2. create a conversation
CONV=$(convos conversations create --name "Project Team" --profile-name "Alice" --json)
CONV_ID=$(echo "$CONV" | jq -r '.conversationId')
# 3. generate an invite for others to join
convos conversation invite "$CONV_ID"
# 4. wait for the person to open the invite URL or scan the QR code,
# then process their join request
convos conversations process-join-requests --conversation "$CONV_ID"
# OR: if you don't know when they'll open it, watch for requests
# convos conversations process-join-requests --watch --conversation "$CONV_ID"
# 5. send a message
convos conversation send-text "$CONV_ID" "Welcome to the team!"
# 6. stream messages
convos conversation stream "$CONV_ID" --timeout 300
Tips
- Always display the full QR code: The
conversation inviteandconversations createcommands output a scannable QR code rendered in Unicode block characters followed by the invite URL. When showing the user the result, you must display the complete, unmodified command output so the QR code renders correctly in the terminal. Do not summarize, truncate, or omit the QR code — it is the primary way users share invites. Always show the full stdout output to the user. When runningagent serve, the QR code is saved as a PNG file (path in theqrCodePathfield of thereadyevent) — display it to the user using the read tool so they can scan it. - Never use markdown in messages: Convos does not render markdown. When sending messages (via
send-text,send-reply, or agentsendcommands), always use plain text. Do not use markdown formatting like**bold**,*italic*,# headings,`code`,[links](url), or bullet lists with-or*. Write naturally in plain text instead. - Identities are automatic: You rarely need to manage them directly — creating/joining conversations handles it
- Use JSON output for scripting: Add
--jsonflag when extracting data programmatically - Use
--fieldsto limit output: When fetching messages or other large responses, use--fieldsto include only the fields you need — this saves context window tokens and reduces noise. e.g.--fields id,content,senderInboxId - Sync before reading: Add
--syncflag when reading messages to ensure fresh data - Process join requests after invite is opened: After generating an invite, wait for the person to open/scan it, then run
process-join-requests. If you don't know when they'll open it, use--watchto stream requests as they arrive - Lock before exploding: Lock a conversation first to prevent new joins, then explode when ready
- Dangerous operations require --force: Commands like
explode,identity remove, andlockprompt for confirmation unless--forceis passed - Check command help: Run
convos <command> --helpfor full flag documentation - Use
convos schemafor runtime introspection:convos schemalists all commands as JSON,convos schema <command>shows full args/flags/examples for a specific command. Useful for discovering capabilities without pre-loaded docs