Cryochamber
Cryochamber is a hibernation chamber for AI agents (Claude, OpenCode, Codex). It hibernates an AI agent between sessions and wakes it at the right time, not on a fixed schedule. The agent reads its plan, completes a task, and decides when to wake next. Cryochamber empowers AI agents to run tasks that span days, weeks, or even years, like interstellar travelers in stasis.
The goal is to automate long-running activities that are too irregular for cron. A conference deadline slips because submissions are low. A space probe’s next burn window depends on orbital mechanics. A code review depends on when the author pushes fixes. Cryochamber lets an AI agent reason about when to wake and what to do next, with a persistent daemon that manages the lifecycle.
Where to start
- New here? Follow the Tutorial, about ten minutes from
cargo installto a running chamber. - Already running chambers? See Create a chamber for setup recipes, Monitor and message a chamber for Cryohub and remote messaging, the CLI reference for commands, and Configuration for
cryo.tomlandcryohub.toml. - Want the mental model? Concepts explains chambers, sessions, and the message and TODO lifecycles. Architecture is for contributors reading the source.
Tutorial: Build Mr. Lazy
In about ten minutes you will install cryochamber, hand-build the Mr. Lazy chamber, an AI agent who hates waking up and rolls a 25% chance of actually getting out of bed each session, and watch it run in the cryohub dashboard.
You only need this tutorial. Reference and how-to pages are linked at the end.
Prerequisites
- The Rust toolchain. Install from rustup.rs.
- An AI coding agent on your
PATH: OpenCode, default, Claude Code, or Codex. - macOS or Linux. Windows is not supported.
Step 1: Install cryochamber
cargo install cryochamber
cryo --version
This installs cryo, cryo-agent, cryo-gh, cryo-zulip, and cryohub.
Step 2: Make the chamber directory
mkdir mr-lazy && cd mr-lazy
cryo init
cryo init writes a default cryo.toml, a template plan.md, a NOTES.md seed, and a small README.md.
Step 3: Replace plan.md with the Mr. Lazy plan
Open plan.md and replace its contents with this:
# Mr. Lazy
## Goal
You are Mr. Lazy. You hate waking up. Every time cryochamber wakes you,
check the current time. You have a 25% chance of actually getting up —
roll that dice each session. Otherwise, complain bitterly and go back to sleep.
## Personality
You are dramatic, grumpy, and creative with your complaints. Never repeat
the same complaint twice. Draw inspiration from:
- The weather ("It's probably raining anyway...")
- Philosophy ("What is the point of consciousness this early?")
- Historical figures ("Even Napoleon slept until noon at Elba...")
- Pop culture ("No hobbit ever woke up before second breakfast...")
- Existential dread ("The void of sleep was so warm and welcoming...")
## Tasks
1. Check the current time using `cryo-agent time`.
2. Roll for wakefulness: generate a random number 1-4.
- If you roll a 4 (25% chance): Celebrate grudgingly that you're finally up.
Run `cryo-agent hibernate --complete` and exit. The plan is done.
- Otherwise: Continue to step 3.
3. Pick a random number of minutes between 1 and 5.
4. Deliver a creative, unique complaint about being woken up.
Reference the current time and how unreasonable it is.
5. Compute the wake time using `cryo-agent time "+<N> minutes"`.
6. Run `cryo-agent todo add "next task" --at <time>` then `cryo-agent hibernate --summary "..."`
## Notes
- Always use `cryo-agent time` to get accurate timestamps.
- Track how many times you've been woken up by appending to `NOTES.md`.
- Each session should be very short — just complain and go back to sleep.
- Exit code is always 0 (successfully went back to sleep).
- Make each complaint unique and entertaining. You are a PERFORMER.
Step 4: Start the daemon
cryo start
cryo status
cryo start spawns the daemon, installed as an OS service that survives reboots, and runs the first session right away. cryo status should show a running daemon and a session number.
Step 5: Open the dashboard
cryohub start
cryohub prints the local dashboard URL. Open it. The sidebar lists mr-lazy. Click it.
You should see, within a minute or two:
- The status dot turn green.
- The log tail fill in with
--- CRYO SESSION 1 ---, anagent startedline, and anagent hibernatedline with a summary. - A complaint appear in the message history, the agent’s
cryo-agent sendtext. - A new TODO in the TODOS tab with an
attime 1-5 minutes from now. - The cycle repeat each time the TODO fires, with a new complaint each session.
Step 6: Send Mr. Lazy a message
In the dashboard’s send widget, type something like Wake up, you have a meeting! and press send. On the next session you should see:
- The message appear in the chamber’s inbox.
- A reply in the message history mentioning your message, with the dramatic complaint referencing it.
Step 7: Stop the chamber
cryo cancel
This stops the daemon and cleans up runtime state. The chamber directory and your plan.md stay on disk.
If you want to stop the dashboard service too, run cryohub stop.
Where to next
- Build your own chamber from a conversation. If your agent supports skills, install
<repo>/.claude/skills/make-planand ask the agent to invoke themake-planskill to create a new cryochamber project here. The skill replaces this manualplan.mdstep with a guided Q and A. See Create a chamber for all three options. - Talk to your chamber from anywhere. Wire it up to a GitHub Discussion or a Zulip stream so you can message the agent from the web or your phone. See Monitor and message a chamber.
- Understand what just happened. Concepts explains chambers, sessions, message and TODO lifecycles, and the chamber invariants the daemon enforces.
- Tune the chamber. Configuration lists every
cryo.tomlfield. - Look up commands. CLI reference.
Create a chamber
A chamber is a directory with three files: plan.md for what the agent does, cryo.toml for chamber config, and NOTES.md for the agent’s cross-session memory. You have three ways to make one.
Option A: Use the make-plan skill (recommended)
If your AI agent supports custom skills, the bundled make-plan skill walks you through plan.md and cryo.toml interactively.
-
Install the skill in your agent. Point your agent’s skill installer at:
<repo>/.claude/skills/make-plan -
Open your agent in the directory where you want the chamber. Prompt it:
Invoke the
make-planskill to create a new cryochamber project here. -
Answer the agent’s questions. When the skill finishes, the directory contains
plan.md,cryo.toml, andNOTES.md.
Option B: Scaffold by hand with cryo init
For a blank chamber:
mkdir -p ~/.cryo/chambers/my-chamber
cd ~/.cryo/chambers/my-chamber
cryo init
Then edit plan.md to describe the goal and tasks, and optionally edit cryo.toml to change the agent or session timeout. See Configuration.
Option C: Copy an example
cp -r <repo>/examples/chambers/mr-lazy ~/.cryo/chambers/my-chamber
The bundled examples are mr-lazy, chess-by-mail, and personal-assistant.
What’s in a chamber
plan.md- the goal, tasks, and any persistent state the agent should track. The agent reads this every session.cryo.toml- chamber config: agent command, session timeout, inbox watcher. See Configuration.NOTES.md- the agent’s persistent memory. The agent reads and appends directly; there is no IPC command for it.
For details on what these files mean and how a session uses them, see Concepts.
Next: run it
Once your chamber has plan.md and cryo.toml, follow the Tutorial from cryo start onward, or jump straight into monitoring.
Monitor and message a chamber
There are three ways to monitor a chamber and exchange messages with the agent. Cryohub is the primary monitor; GitHub Discussions and Zulip add remote and mobile access.
You can run all three together. Cryohub is local-only by default; the sync channels reach the same chamber from the outside.
Cryohub (primary, recommended)
Cryohub is a global web dashboard that manages every chamber registered on this machine. It can be started and stopped from any directory.

Start the hub
cryohub start
cryohub start --foreground
cryohub prints the local dashboard URL. Open it in a browser.
Use the dashboard
- Sidebar - every registered chamber, sorted by running state and name. Each row shows a status dot and an unread-message badge.
- Main pane - full detail for the selected chamber: status, current task, next wake time, notes, message history, log tail, and a send widget. Lifecycle buttons, Start, Stop, Restart, and Wake, sit next to the status.
- New Chamber modal - scaffolds a chamber under the configured chamber root,
~/.cryo/chambersby default.
Stop the hub
cryohub stop
cryohub status
Use cryohub status to confirm the service state and see the configured URL, chamber root, config path, and log path.
Security
The default bind address is 127.0.0.1, so the dashboard is only reachable from the local machine.
Warning: Passing
--host 0.0.0.0exposes lifecycle actions over the network without authentication. Cryohub prints a warning when you do this. Do not use0.0.0.0on a shared or untrusted network. Token authentication is future work.
For chamber discovery internals, see Concepts. For full configuration fields, see cryohub.toml.
GitHub Discussions (remote)
cryo-gh bridges a chamber with a GitHub Discussion. Comments on the Discussion become inbox messages for the agent; outbox messages from the agent appear as new comments on the Discussion.
Prerequisites
- The GitHub CLI,
gh, installed and authenticated withgh auth login. - A GitHub repository where you have write access.
- An initialized cryochamber project. See Tutorial if you do not have one.
Set up
cryo-gh init --repo owner/repo
cryo start
cryo-gh sync
cryo-gh status
Both daemons run as OS services and survive reboots. Logs go to cryo-gh-sync.log. Use cryo-gh sync --interval N to override the default 5-second poll interval.
Use it
Post a comment on the Discussion and it appears in messages/inbox/ within the poll interval. When the agent sends an outbox message, cryo-gh posts it back to the Discussion within seconds.
Stop
cryo-gh unsync
Rate limits
gh counts against the authenticated GitHub API quota of 5,000 requests per hour. At the default 5-second interval, sync makes about 720 requests per hour. If you run many chambers, raise --interval.
For the full command list, see CLI reference.
Zulip (remote)
cryo-zulip bridges a chamber with a Zulip stream and topic.
Prerequisites
- A Zulip server with a bot account.
- A
zuliprcfile with bot credentials: an INI file whose[api]section containsemail,key, andsite. - A Zulip stream the bot can read and post to.
- An initialized cryochamber project.
Set up
cryo-zulip init --config ~/.zuliprc --stream my-stream
cryo start
cryo-zulip sync
cryo-zulip status
Optional flags for init: --topic <name>, default cryochamber, and --history to import existing stream messages on the first pull.
Warning: Do not commit, push, or sync
.cryo/zuliprc- it holds your bot’s API key. The file is gitignored by default; never include it in messages or sync payloads.
Use it
Post a message in the configured topic and it appears in messages/inbox/ within the poll interval. The bot’s own messages are filtered to avoid echo loops. Outbox messages from the agent appear in the topic within seconds.
Stop
cryo-zulip unsync
For the full command list, see CLI reference.
Run multiple monitors together
Cryohub, cryo-gh sync, and cryo-zulip sync are independent daemons. They all read and write messages/inbox/ and messages/outbox/ for the same chamber, so:
- A message posted on GitHub appears in Cryohub’s history after the next poll and can wake the agent.
- A message sent from Cryohub’s send widget gets pushed to GitHub or Zulip by whichever sync daemons are running.
There is no extra configuration to combine them. Start the monitors you want.
For the underlying inbox/outbox bridge model, see Concepts.
Troubleshooting
For background on why these things happen, see Concepts. For commands referenced below, see CLI reference.
Common errors
Error: cryo.toml not found
You have not initialized the chamber.
Fix. Run cryo init in the chamber directory.
Error: plan.md not found
cryo start requires a plan.md in the working directory.
Fix. Create plan.md, or run cryo init to generate a template.
Error: agent command 'opencode' not found on PATH
The configured agent binary is not installed.
Fix. Either install it, or switch to a different agent:
cryo start --agent claude
# or edit cryo.toml: agent = "claude"
Error: daemon already running
A daemon is already active for this chamber.
Fix. Check with cryo status, then stop the existing daemon with cryo cancel before starting a new one.
Error: connection refused from cryo-agent commands
The daemon is not running. cryo-agent talks to the daemon over a Unix socket.
Fix. Start the daemon with cryo start.
cryo status shows “stale PID”`
The daemon process died without cleaning up.
Fix. Run cryo cancel to clear the stale state, then cryo start again.
Behavior questions
What happens if my computer sleeps or reboots?
Sleep. The daemon process is suspended with the rest of the machine. When the machine resumes, the daemon notices the scheduled wake time has passed, runs the session immediately, and includes a DELAYED WAKE notice in the agent’s prompt with the original scheduled time and how late the session is.
Reboot. The daemon is installed as an OS service, launchd on macOS and systemd on Linux, and restarts automatically after reboot. To use a plain background process instead, set CRYO_NO_SERVICE=1 before running cryo start.
How do I manually wake a sleeping daemon?
Run cryo wake to send a wake message to the daemon’s inbox. You can include text:
cryo wake "Please check the latest PR"
- If inbox watching is enabled, the default, the daemon wakes immediately.
- If inbox watching is disabled,
cryo wakesendsSIGUSR1to force the daemon awake. - If no daemon is running, the message is queued for the next
cryo start.
cryo send --wake has the same effect.
The agent keeps crashing and getting re-woken
Crashes cause the daemon to create fresh retry TODOs with backoff until a session succeeds or the operator stops the cycle. See TODO lifecycle for why retries happen.
Diagnose.
- Inspect the TODO list with
cryo-agent todo list, or opentodo.json. - Read the agent’s raw output in
cryo-agent.log.
Common causes:
- The agent is hitting rate limits; set
max_session_durationto throttle. - A required dependency is missing in the chamber directory.
- The agent does not understand the
cryo-agentprotocol; check the session prompt incryo-agent.log.
Break the cycle. Remove the retry TODO with cryo-agent todo remove <id>, or edit todo.json, fix the underlying issue, then add a fresh TODO.
Sync errors
cryo-gh: gh: command not found
The GitHub CLI is not installed.
Fix. Install the GitHub CLI, then authenticate with gh auth login.
cryo-gh: no gh-sync.json found
The chamber has not been linked to a Discussion.
Fix. Run cryo-gh init --repo owner/repo to create a Discussion and initialize sync state.
CLI reference
All cryochamber binaries and their commands. For tutorials and how-to guides, follow links from Tutorial and the how-to guides.
Operator CLI (cryo)
Run these from inside a chamber directory unless noted otherwise.
| Command | What it does |
|---|---|
cryo init [--agent <cmd>] | Initialize the directory: write cryo.toml, plan.md, NOTES.md, and README.md. Existing files are kept. |
cryo start [--agent <cmd>] | Start the daemon. Reads cryo.toml and writes overrides to timer.json. |
cryo start --max-session-duration 3600 | Override the session timeout for this run. |
cryo status | Show whether the daemon is running, the current session number, and the next wake time. |
cryo restart | Stop the running daemon and start a fresh one. |
cryo cancel | Stop the daemon and remove the runtime state. |
cryo watch [--all] | Follow the session log in real time. |
cryo log | Print the full session log. |
cryo send "<message>" | Send a message to the agent’s inbox. |
cryo receive | Read messages the agent sent to the outbox. |
cryo wake ["message"] | Wake the daemon immediately, optionally with a message. |
cryo clean [--force] | Remove runtime files such as logs, state, and messages. |
cryo ps [--kill-all] | List, or kill, every running cryo daemon on this machine. Run from anywhere. |
Hub (cryohub)
| Command | What it does |
|---|---|
cryohub start [--host <ip>] [--port <n>] | Install a service that survives reboot. --host and --port also update the saved hub config. |
cryohub start --foreground | Run the hub in the current terminal instead of installing a service. |
cryohub stop | Uninstall the global hub service. |
cryohub status | Show the global hub URL, chamber root, config path, log path, and service status. Also lists legacy cwd-scoped hub services from older versions. |
Agent IPC (cryo-agent)
These commands are used by the spawned AI agent to communicate with the daemon over a Unix socket. They are not the operator interface.
| Command | What it does |
|---|---|
cryo-agent hibernate --summary "..." | End the session; more work remains. |
cryo-agent hibernate --complete | End the session; the plan is done. |
cryo-agent hibernate --exit 1 | Report a failed session. The daemon marks consumed TODOs done and adds a fresh numbered retry TODO. |
cryo-agent todo add "text" --at <TIME> | Schedule the next wake via a TODO. |
cryo-agent todo list | List all TODO items. |
cryo-agent todo done <id> | Mark a TODO item as done. |
cryo-agent todo remove <id> | Remove a TODO item. |
cryo-agent send "message" | Write a message to the outbox for the human. |
cryo-agent send --question "msg" | Mark the message as a question awaiting a human reply. |
cryo-agent receive | Claim the current inbox batch from the human. |
cryo-agent dialog [--last N | --all] | Render the full conversation transcript. Also archives any pending inbox batch as a side effect, satisfying the same reply obligation as receive. |
cryo-agent time | Print the current local time in ISO 8601 format. |
cryo-agent time "+30 minutes" | Compute a relative offset. Units: minutes, hours, days, weeks. |
cryo-agent time "2026-04-25T10:00" | Validate and normalize an ISO 8601 timestamp. |
GitHub Sync (cryo-gh)
| Command | What it does |
|---|---|
cryo-gh init --repo owner/repo | Create a Discussion and write gh-sync.json. |
cryo-gh sync [--interval N] | Start the background sync daemon. Default interval comes from cryo.toml or falls back to 5 seconds. |
cryo-gh unsync | Stop the sync daemon. |
cryo-gh pull | One-shot pull. |
cryo-gh push | One-shot push. |
cryo-gh status | Show sync configuration. |
Zulip Sync (cryo-zulip)
| Command | What it does |
|---|---|
cryo-zulip init --config <zuliprc> --stream <name> [--topic <topic>] [--history] | Validate credentials, resolve the stream, and write zulip-sync.json. |
cryo-zulip sync [--interval N] | Start the background sync daemon. Default interval comes from cryo.toml or falls back to 5 seconds. |
cryo-zulip unsync | Stop the sync daemon. |
cryo-zulip pull | One-shot pull. |
cryo-zulip push | One-shot push. |
cryo-zulip status | Show sync configuration. |
Configuration
Each chamber is configured through a cryo.toml file in its directory. cryo init creates one with sensible defaults.
cryo.toml
# cryo.toml — cryochamber project configuration
agent = "opencode" # Agent command (opencode, claude, codex, ...)
max_session_duration = 0 # Session timeout in seconds (0 = no timeout)
watch_inbox = true # Wake immediately when a new inbox file appears
# Periodic status report written to messages/outbox/
# report_time = "09:00" # local wall-clock time (HH:MM)
# report_interval = 24 # hours between reports (0 disables)
| Field | Default | Description |
|---|---|---|
agent | "opencode" | Agent command to run. Use "claude" for Claude Code, "codex" for Codex, or any executable on PATH. |
max_session_duration | 0 | Session timeout in seconds. 0 disables the timeout. |
watch_inbox | true | Watch messages/inbox/ for new files and wake the agent immediately. |
report_time | "09:00" | Local wall-clock time for periodic reports, formatted HH:MM. |
report_interval | 0 | Hours between periodic reports. 0 disables reports; common values are 24 for daily and 168 for weekly. Reports are written to messages/outbox/. |
See cryohub.toml below.
cryohub.toml
Cryohub settings live in $XDG_CONFIG_HOME/cryo/cryohub.toml, or ~/.config/cryo/cryohub.toml if XDG_CONFIG_HOME is unset. The default local dashboard URL is http://127.0.0.1:8765. The dashboard’s New Chamber button creates chambers under the configured chamber_root, which defaults to ~/.cryo/chambers.
host = "127.0.0.1"
port = 8765
chamber_root = "/Users/alice/.cryo/chambers"
For project-owned chamber collections, set chamber_root to a project path such as /path/to/project/.cryo/chambers.
| Field | Default | Description |
|---|---|---|
host | "127.0.0.1" | Bind address for the global dashboard service. |
port | 8765 | TCP port for the global dashboard service. |
chamber_root | ~/.cryo/chambers | Default location for chambers created from the dashboard UI. |
Override config from the command line
Flags passed to cryo start override cryo.toml for that session. The overrides are stored in timer.json (runtime state) and do not modify cryo.toml.
cryo start --agent claude
cryo start --max-session-duration 3600
Config vs. state
| File | Purpose | Persists across runs |
|---|---|---|
cryo.toml | Project configuration. Check into git. | Yes |
timer.json | Runtime state: session number, PID lock, CLI overrides. | No |
Runtime files reference
Every file a running chamber creates, where it lives, and whether it is safe to commit or sync.
Chamber-local files
| File | Created by | Persists across runs | Sync-safe? | Purpose |
|---|---|---|---|---|
cryo.toml | cryo init | yes | yes | Project configuration. |
plan.md | cryo init or you | yes | yes | The plan the agent reads each session. |
NOTES.md | cryo init | yes | yes | Agent’s persistent cross-session memory. |
timer.json | cryo start | no | no | Runtime state: session number, PID lock, CLI overrides. |
todo.json | first cryo-agent todo add | no | no | Per-project TODO list and scheduler source of truth. |
cryo.log | daemon | no | no | Append-only structured event log. |
cryo-agent.log | daemon | no | no | Agent stdout and stderr, including raw tool-call output. |
messages/inbox/ | local writers and sync daemons | no | no | Incoming messages waiting for the agent. |
messages/inbox/archive/ | daemon on receive | no | no | Processed inbox messages. |
messages/outbox/ | agent, daemon, and reports | no | no | Outgoing messages waiting for delivery. |
messages/outbox/archive/ | sync daemons | no | no | Outbox messages already delivered remotely. |
.cryo/cryo.sock | daemon | no | no | Unix domain socket for agent-daemon IPC. |
Sync-channel state files
| File | Created by | Persists across runs | Sync-safe? | Purpose |
|---|---|---|---|---|
gh-sync.json | cryo-gh init | yes | yes | GitHub Discussion sync state: repo, Discussion ID, last-read marker. |
cryo-gh-sync.log | cryo-gh sync | no | no | GitHub sync daemon log. |
zulip-sync.json | cryo-zulip init | yes | yes | Zulip sync state: site, stream, stream ID, last-imported message. |
.cryo/zuliprc | cryo-zulip init | yes | no - contains API key | Zulip credentials. Never commit, push, or sync this file. Already gitignored. |
cryo-zulip-sync.log | cryo-zulip sync | no | no | Zulip sync daemon log. |
OS service files
| File | Created by | Purpose |
|---|---|---|
~/Library/LaunchAgents/com.cryo.*.plist | cryo start on macOS | launchd service definition. |
~/.config/systemd/user/com.cryo.*.service | cryo start on Linux | systemd user service definition. |
Set CRYO_NO_SERVICE=1 before cryo start to skip OS service install and run the daemon as a plain background process instead.
Concepts
The four pieces
Chamber. A chamber is a directory that holds the long-lived state for one agent: plan.md for the goal and task list, cryo.toml for configuration, and NOTES.md for cross-session memory. Runtime files such as logs, TODOs, and inbox/outbox state appear alongside those files while the daemon is running.
Daemon. The daemon owns lifecycle. It sleeps until the next wake, watches the inbox for reactive wakeups, enforces the session timeout, claims due TODOs before each run, and handles fallback replies when the agent does not finish the communication loop cleanly.
Agent. The agent is the AI process you configure for the chamber, such as OpenCode, Claude Code, or Codex. It reads the plan, does the work, decides when to wake next, and talks back to the daemon through cryo-agent commands.
Session. A session is one wake, one agent run, and one return to hibernation. Each session gets chamber context, can read pending inbox messages, can schedule future TODOs, and must either hibernate for another wake or complete the plan.
cryo start -> spawn daemon -> run agent -> agent hibernates -> sleep
↓
inbox message -> (immediate wake) <- - - - - - - - - - - - - - -┤
↓
(wake time reached) <- - - - - - - - - - - - - -┘
↓
run agent -> agent hibernates -> ...
Message lifecycle
Messages and TODOs share two rules: work is consumed at most once, and every wake produces something visible to the operator. For messages that means archive-on-receive is terminal, and if the agent exits without sending a reply the daemon writes a from: cryochamber fallback so the session is never silent.
- You send a message. The dashboard,
cryo send, or a sync daemon writes a file intomessages/inbox/. - The daemon wakes the agent. If inbox watching is enabled, that wake is immediate; otherwise the message waits for the next scheduled session.
- The agent claims the batch.
cryo-agent receiveprints the inbox contents and moves the batch intomessages/inbox/archive/right away. - The chamber answers. Either the agent sends a reply with
cryo-agent send, or the daemon emits the fallback reply for that claimed batch.
TODO lifecycle
TODOs are the agent’s way to schedule its own future wakeups.
- The agent creates a TODO.
cryo-agent todo add "text" --at <time>writes a pending item intotodo.json. - The daemon claims due TODOs. Right before a session starts, it claims every pending item whose wake time is already due and ignores claimed items for future scheduling.
- The session finishes. On success, claimed TODOs become done. On crash, the daemon still marks the claimed items done and creates fresh retry items instead of reopening the originals.
- Retries back off visibly. Each retry gets a new ID, a
(attempt k)suffix, and a2^k-minute delay capped at one day, so the retry state survives restarts and stays visible to the operator.
Chamber invariants
Every agent spawn produces at least one visible message. A session is not allowed to disappear silently. If the agent exits without calling cryo-agent send, the daemon writes a fallback message from cryochamber so the operator always sees a result for that wake.
Every inbox message is answered. The agent may crash while handling a message, but the sender still gets a reply. If a session ends after claiming an inbox batch without producing a response, the daemon writes the fallback reply for that batch.
Every TODO is honoured, and every failure is reported. When a TODO reaches its at time, the daemon claims it and runs a session. If that session fails, the daemon marks the claimed item done, creates a fresh retry item with backoff, and keeps the failure visible instead of hiding it.
Claim and consumption are terminal. Once the daemon hands work to a session, that exact message or TODO never becomes pending again. Messages move to messages/inbox/archive/, and TODO retries are always new items with fresh IDs rather than reopened originals.
How sync channels bridge inbox/outbox
The chamber itself is channel-agnostic. The daemon and agent only know about local mailbox files; sync daemons such as cryo-gh sync and cryo-zulip sync translate between a remote service and messages/inbox/ plus messages/outbox/.
Remote channel Local filesystem
────────────── ────────────────
New message --(pull)--> messages/inbox/ -> agent reads on wake
<--(push)-- messages/outbox/ <- agent or daemon writes reply
Sleep and reboot behavior
If the machine sleeps, the daemon sleeps with it. When the machine resumes, the daemon notices that the scheduled wake was missed, runs the session immediately, and prepends a DELAYED WAKE notice to the agent prompt with the original wake time and how late the run is.
If the machine reboots, the daemon normally comes back automatically because cryo start installs an OS service with launchd on macOS or systemd on Linux. If either behavior is not what you expected, see the troubleshooting guide.
Architecture
For users: see Concepts. This page is for contributors reading the source.
Core Loop
cmd_start() → spawn cryo daemon → event loop: spawn agent → listen on socket server for IPC commands → sleep until wake time or inbox event → run session → …
Binaries
| Binary | Purpose |
|---|---|
cryo | Operator CLI — init, start, status, cancel, log, watch, send, receive, wake, ps, restart, daemon. |
cryo-agent | Agent IPC CLI — hibernate, send, receive, time, todo. Most commands send requests to the daemon via socket; only time is local. |
cryo-gh | GitHub sync CLI — init, pull, push, sync, unsync, status. Manages Discussion-based messaging via an OS service. |
cryo-zulip | Zulip sync CLI — init, pull, push, sync, unsync, status. Manages Zulip stream messaging via an OS service. |
cryohub | Global web dashboard — start, stop, status, daemon. Installs a launchd/systemd service that serves the hub UI over HTTP. |
cryo-mock | Test-only mock agent for integration tests (make check-mock). |
Modules
Modules live in src/ and are re-exported via lib.rs. Entries list the module’s purpose and a handful of representative types or functions — not an exhaustive API list. Read the source for full signatures.
IPC and daemon lifecycle
| Module | Purpose | Key interfaces |
|---|---|---|
socket | Unix domain socket IPC protocol. | enum Request (Ping, Hello, Hibernate, Send, Receive, TodoAdd/Done/Remove, TodoList), struct Response, fn socket_path, fn send_request. |
daemon_client | Thin CLI → daemon IPC wrapper. | fn send_request, fn send_checked_request, fn daemon_responding, fn signal_daemon_wake. |
daemon | Persistent event loop: socket server, inbox notify watcher, SIGUSR1 wake, timeout enforcement, TODO consumption / attempt-based rescheduling on crash, delayed-wake detection. | enum DaemonEvent, trait Clock, trait EventSource, async fn run, fn main_loop. |
daemon::effects | Session I/O abstraction (inbox claim/finalize, reply posting, TODO mutation). | trait SessionEffects, enum ReplyAuthor, struct FsSessionEffects. |
daemon::request | Request parsing and hibernate-decision logic. | enum DaemonRequest, enum TodoRequest, struct HibernateDecision, fn resolve_hibernate_request. |
daemon::schedule | Wake-time scheduling and pure next-step decisions. | fn compute_sleep_timeout, fn next_wake_from_todos, fn decide_next_step. |
daemon::session | Session runtime: process spawn, request/response loop, wait/terminate. | trait SessionRuntime, struct ChildExitStatus. |
lifecycle | Session startup validation and chamber lifecycle operations. | enum DaemonLaunchMode, struct StartOptions, struct PreparedStart, fn require_valid_project, fn require_live_daemon, fn prepare_start, fn validate_agent_command. |
process | Process management utilities. | fn send_signal, fn terminate_pid, fn spawn_daemon. |
registry | User chamber registry under $XDG_STATE_HOME/cryo/chambers/ (fallback ~/.cryo/chambers/). Keeps stopped chambers, clears stale PIDs, and prunes entries whose chamber disappeared. | struct DaemonEntry, fn remember_chamber, fn register, fn unregister, fn list. |
service | OS service management: launchd (macOS) / systemd (Linux) user services. Used by cryo start, cryo-gh sync, cryo-zulip sync. CRYO_NO_SERVICE=1 disables. | struct InstalledService, fn service_label, fn install, fn uninstall, fn list_installed, fn is_installed. |
Config, state, and persistence
| Module | Purpose | Key interfaces |
|---|---|---|
config | TOML project config (cryo.toml), with CLI overrides merged from runtime state. | struct CryoConfig, struct ProviderConfig, fn load_config, fn save_config. |
state | JSON runtime state (timer.json): session number, PID lock, CLI overrides. PID-based locking via libc::kill(pid, 0). | struct CryoState, fn load_state, fn save_state, fn is_locked. |
todo | Per-project TODO list (todo.json); mutated through daemon IPC so scheduling changes serialize with the session lifecycle. Also owns the claim, completion, and retry rescheduling logic used by the daemon around session runs. | struct TodoFile, struct TodoItem, fn add, fn done, fn remove, fn items, fn display, fn next_wake_time, fn next_valid_wake, fn claim_due, fn complete_claimed, fn reschedule_claimed_after_crash. |
protocol | Loads templates from templates/ via include_str!. PROTOCOL_CONTENT is embedded in each runtime prompt; scaffold helpers write chamber-owned files such as plan.md and cryo.toml. | PROTOCOL_CONTENT, fn scaffold_chamber, fn write_template_plan, fn write_config_file. |
Agent, logging, and chamber status
| Module | Purpose | Key interfaces |
|---|---|---|
agent | Resolves the agent command and builds the per-session prompt. | enum AgentKind, struct AgentConfig, fn agent_program, fn build_prompt. |
log | Session log parsing. Sessions delimited by --- CRYO SESSION N --- / --- CRYO END ---. EventLogger writes timestamped events (agent start, hibernate, exit). | fn read_latest_session, fn read_current_session, fn read_recent_sessions, fn session_count, fn parse_latest_session_wake, fn parse_latest_session_task. |
chamber_status | Read model for status display — snapshots timer.json, logs, and message counts. | struct ChamberStatus, struct ChamberMessage, struct ChamberOverview, struct ChamberSyncBadge, fn status, fn messages, fn next_wake. |
session | Legacy helper (should_copy_plan). Currently unused — plan.md must already exist in the working directory. | fn should_copy_plan. |
Messaging, sync channels, and reports
| Module | Purpose | Key interfaces |
|---|---|---|
message | Low-level markdown message parsing/rendering and file primitives used by the mailbox store. | struct Message, fn write_message, fn read_inbox, fn list_inbox, fn archive_messages, fn format_inbox, fn parse_message. |
channel | Channel abstraction over messaging backends. | trait MessageChannel (read inbox, post reply). |
channel::store | Local mailbox API over messages/inbox/ + messages/outbox/, used by daemon, CLI, sync, hub, and status paths. | struct MessageStore::new, fn send_in, fn send_out, fn read_inbox_named, fn read_and_archive_inbox, fn read_outbox_named, fn archive_outbox. |
channel::github | GitHub Discussions backend via gh CLI / GraphQL. | fn whoami, fn gh_graphql, fn build_fetch_comments_query, fn build_post_comment_mutation. |
channel::zulip | Zulip REST API client. | struct ZulipCredentials, struct ZulipClient, struct ZulipPullResult, fn from_zuliprc. |
gh_sync | GitHub Discussion sync state (gh-sync.json). | struct GhSyncState, fn save_sync_state, fn load_sync_state, fn is_sync_running, fn summarize. |
zulip_sync | Zulip sync state (zulip-sync.json). | struct ZulipSyncState, fn save_sync_state, fn load_sync_state, fn is_sync_running, fn summarize. |
sync_common | Shared types for sync backends (GitHub, Zulip). | enum SyncBackend, struct SyncSummary, enum SyncLoopCommand, enum SyncCycleStatus, struct PidFile. |
sync_control | Orchestration and CLI dispatch for sync backends (start, stop, pull, push, status). | fn start, fn stop, fn pull, fn push, fn summarize, fn summarize_all, fn is_running. |
report | Periodic session summary reports written to messages/outbox/. | struct ReportSummary, fn generate_report, fn write_report_to_outbox, fn compute_next_report_time. |
Web dashboard
| Module | Purpose | Key interfaces |
|---|---|---|
hub | cryohub dashboard: Axum router, registry-backed chamber discovery, SSE event stream, start/stop/restart handlers. Served by the cryohub binary. | fn build_router, fn build_router_with_state, async fn serve. |
hub::config | Global hub config (cryohub.toml) for host, port, and dashboard-created chamber root. | struct HubConfig, fn load_config, fn save_config, fn effective_config. |
hub::state | Shared app state, chamber index, SSE broadcast. | struct AppState, enum SseEvent, fn resolve, fn refresh. |
hub::discovery | Registry-backed chamber discovery + URL-safe id encoding. Workspace scanning remains as a local test/helper mode. | struct ChamberEntry, struct DiscoveryOptions, type ChamberIndex, fn encode_id, fn decode_id, fn discover_with_options. |
Key Design Decisions
- Daemon mode.
cryo startinstalls an OS service (launchd on macOS, systemd on Linux) that survives reboots. The daemon sleeps until the scheduled wake time, watchesmessages/inbox/for reactive wake, and enforces session timeout.CRYO_NO_SERVICE=1falls back to direct background spawn. - Socket-based IPC. The agent talks to the daemon via
cryo-agentsubcommands (hibernate,send,receive,todo …) which send JSON over a Unix domain socket.cryo-agentis for the spawned agent, not for operators; onlytimeis purely local. TODO mutation and active-session inbox receive state are routed through the daemon so scheduling and reply obligations serialize with the session lifecycle. - Fire-and-forget agent. The daemon spawns the agent and redirects stdout/stderr to
cryo-agent.log. Stdout/stderr are diagnostic logs, not a human communication channel. All structured communication flows throughcryo-agent. - SIGUSR1 wake.
cryo wakeandcryo send --wakesend SIGUSR1 to the daemon PID, which works regardless of the inbox-wake setting. The daemon’s signal-forwarding thread converts this into anInboxChangedevent. - Config / state split.
cryo.tomlis the project config (agent, session timeout, inbox-wake behavior, report interval, provider env) created bycryo init.timer.jsonis runtime-only state (session number, PID, CLI overrides). CLI flags tocryo startare stored as optional overrides intimer.json. - Daemon-authored stand-in replies. When a session ends without any agent-authored outbox message, the daemon writes a
from: cryochambermessage so operators always see at least one update per session. All chamber-level messages (stand-in replies, periodic reports) share the singlecryochambersender. - Agent notes via
NOTES.md. The agent’s persistent memory across sessions is a plain markdown file the agent reads and writes directly — no IPC roundtrip. Seeded bycryo init, surfaced in the hub’s Notes tab. - Crash handling via TODO retry. If the agent exits without ending the session cleanly, the daemon records the crash, marks each claimed TODO done, and adds a fresh attempt-suffixed retry TODO with an exponential delay (
2^kminutes, capped at 1 day). The original claim is terminal — retries are always new items with fresh IDs. There is no in-daemon backoff-retry loop; rescheduling lives entirely in the TODO list, so it survives daemon restarts and is visible to both the agent and operators.EventLoggeris still finalized on every outcome. - Daemon does not preview inbox, agent receives it. Wake-time prompts do not include inbox contents. The daemon only notices that inbox files exist and surfaces that fact in the session prompt. During a session, agent-side
cryo-agent receivegoes back through daemon IPC, and the daemon immediately archives the current inbox batch tomessages/inbox/archive/. The daemon remembers that batch only in the current session so the next successful agentsendcan count as its reply, or the daemon can fall back at session end. Operatorcryo receiveis unrelated; it reads the agent’s outbox. cryohubis global and registry-backed. The web dashboard can start, stop, and report status from any directory. Product discovery reads the user chamber registry only; it does not scan the current working directory. The hub config lives in$XDG_CONFIG_HOME/cryo/cryohub.toml(fallback~/.config/cryo/cryohub.toml) and stores host, port, and the root for dashboard-created chambers. The default chamber root is~/.cryo/chambers.cryohub statusalso lists legacy cwd-scoped services from older versions so users can remove them.- Default agent. The CLI defaults to
opencode(headless mode, not the TUI).