Zulip Sync
cryo-zulip bridges a cryochamber project with a Zulip stream, enabling remote monitoring and two-way messaging. Stream messages become inbox messages for the agent; outbox messages from the agent are posted back to the stream.
Prerequisites
- A Zulip server with a bot account
- A
zuliprcfile with bot credentials (standard Zulip INI format with[api]section containingemail,key,site) - A Zulip stream accessible by the bot
- An initialized cryochamber project (
cryo init)
Commands
cryo-zulip init --config ~/.zuliprc --stream my-stream # Validate credentials, resolve stream, write zulip-sync.json
cryo-zulip init --config ~/.zuliprc --stream my-stream --topic mychannel # Custom topic (default: "cryochamber")
cryo-zulip sync [--interval N] # Start background sync daemon (default from cryo.toml or 5s)
cryo-zulip unsync # Stop the sync daemon
cryo-zulip pull # One-shot: pull new messages → inbox
cryo-zulip push # One-shot: push latest session log → stream
cryo-zulip status # Show sync configuration
How Sync Works
cryo-zulip sync spawns a background daemon (just like cryo start does). It does two things in a loop:
Stream → Inbox (pull direction): Polls the Zulip stream for new messages every --interval seconds (default: zulip_poll_interval in cryo.toml, or 5s). New messages are written to messages/inbox/ where the cryo daemon picks them up on the next session. The bot’s own messages are filtered out to prevent echo loops.
Outbox → Stream (push direction): Watches messages/outbox/ for new files. When the agent sends a message (via cryo-agent send), the sync daemon posts it to the Zulip stream and archives the file to messages/outbox/archive/.
Zulip Stream Local filesystem
──────────── ─────────────────
New message ──(pull)──→ messages/inbox/ → agent reads on wake
←─(push)── messages/outbox/ ← agent writes via cryo-agent send
The sync is managed as a system service (launchd on macOS, systemd on Linux) that survives reboots. Logs go to cryo-zulip-sync.log.
Recommended Workflow
1. Initialize the project
cryo init --agent claude
# edit plan.md with your task
2. Link to Zulip
cryo-zulip init --config ~/.zuliprc --stream my-stream
This validates the bot credentials, resolves the stream ID, and writes zulip-sync.json. The zuliprc is copied to .cryo/zuliprc for use by the sync daemon.
3. Start the daemon and sync
cryo start
cryo-zulip sync
Both run as background daemons. Monitor with cryo watch.
4. Send messages from Zulip
Post a message in the Zulip stream from the web UI or mobile app. The sync daemon picks it up within 30 seconds and writes it to messages/inbox/. The cryo daemon wakes the agent on the next session (or immediately if watch_inbox = true).
5. Read agent replies on Zulip
When the agent calls cryo-agent send "message", the outbox file is detected immediately by the sync watcher and posted to the Zulip stream.
6. Stop
cryo-zulip unsync # stop sync daemon
cryo cancel # stop cryo daemon
One-Shot Usage
For manual or scripted use without the sync daemon:
cryo-zulip pull # fetch new stream messages into inbox
cryo-zulip push # post the latest session log to the stream
Example: Chess by Mail over Zulip
Play correspondence chess against an AI agent, sending moves from the Zulip web UI:
cd examples/chess-by-mail
cryo-zulip init --config ~/.zuliprc --stream chess-game
cryo init && cryo start
cryo-zulip sync --interval 30
# Send your moves as messages in the Zulip stream!
See Chess by Mail for the full example.
Files
| File | Purpose |
|---|---|
zulip-sync.json | Sync state: site, stream, stream ID, bot email, cursor |
.cryo/zuliprc | Bot credentials (copied from user’s zuliprc on init) |
cryo-zulip-sync.log | Sync daemon log output |
messages/inbox/ | Incoming messages (from Zulip stream) |
messages/outbox/ | Outgoing messages (posted to Zulip stream) |
messages/outbox/archive/ | Posted outbox messages (archived after sync) |