Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

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

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 ---, an agent started line, and an agent hibernated line with a summary.
  • A complaint appear in the message history, the agent’s cryo-agent send text.
  • A new TODO in the TODOS tab with an at time 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-plan and ask the agent to invoke the make-plan skill to create a new cryochamber project here. The skill replaces this manual plan.md step 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.toml field.
  • 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.

If your AI agent supports custom skills, the bundled make-plan skill walks you through plan.md and cryo.toml interactively.

  1. Install the skill in your agent. Point your agent’s skill installer at:

    <repo>/.claude/skills/make-plan
    
  2. Open your agent in the directory where you want the chamber. Prompt it:

    Invoke the make-plan skill to create a new cryochamber project here.

  3. Answer the agent’s questions. When the skill finishes, the directory contains plan.md, cryo.toml, and NOTES.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 is a global web dashboard that manages every chamber registered on this machine. It can be started and stopped from any directory.

cryohub dashboard with the mr-lazy chamber selected

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/chambers by 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.0 exposes lifecycle actions over the network without authentication. Cryohub prints a warning when you do this. Do not use 0.0.0.0 on 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 with gh 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 zuliprc file with bot credentials: an INI file whose [api] section contains email, key, and site.
  • 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 wake sends SIGUSR1 to 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 open todo.json.
  • Read the agent’s raw output in cryo-agent.log.

Common causes:

  • The agent is hitting rate limits; set max_session_duration to throttle.
  • A required dependency is missing in the chamber directory.
  • The agent does not understand the cryo-agent protocol; check the session prompt in cryo-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.

CommandWhat 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 3600Override the session timeout for this run.
cryo statusShow whether the daemon is running, the current session number, and the next wake time.
cryo restartStop the running daemon and start a fresh one.
cryo cancelStop the daemon and remove the runtime state.
cryo watch [--all]Follow the session log in real time.
cryo logPrint the full session log.
cryo send "<message>"Send a message to the agent’s inbox.
cryo receiveRead 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)

CommandWhat 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 --foregroundRun the hub in the current terminal instead of installing a service.
cryohub stopUninstall the global hub service.
cryohub statusShow 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.

CommandWhat it does
cryo-agent hibernate --summary "..."End the session; more work remains.
cryo-agent hibernate --completeEnd the session; the plan is done.
cryo-agent hibernate --exit 1Report 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 listList 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 receiveClaim 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 timePrint 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)

CommandWhat it does
cryo-gh init --repo owner/repoCreate 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 unsyncStop the sync daemon.
cryo-gh pullOne-shot pull.
cryo-gh pushOne-shot push.
cryo-gh statusShow sync configuration.

Zulip Sync (cryo-zulip)

CommandWhat 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 unsyncStop the sync daemon.
cryo-zulip pullOne-shot pull.
cryo-zulip pushOne-shot push.
cryo-zulip statusShow 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)
FieldDefaultDescription
agent"opencode"Agent command to run. Use "claude" for Claude Code, "codex" for Codex, or any executable on PATH.
max_session_duration0Session timeout in seconds. 0 disables the timeout.
watch_inboxtrueWatch 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_interval0Hours 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.

FieldDefaultDescription
host"127.0.0.1"Bind address for the global dashboard service.
port8765TCP port for the global dashboard service.
chamber_root~/.cryo/chambersDefault 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

FilePurposePersists across runs
cryo.tomlProject configuration. Check into git.Yes
timer.jsonRuntime 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

FileCreated byPersists across runsSync-safe?Purpose
cryo.tomlcryo inityesyesProject configuration.
plan.mdcryo init or youyesyesThe plan the agent reads each session.
NOTES.mdcryo inityesyesAgent’s persistent cross-session memory.
timer.jsoncryo startnonoRuntime state: session number, PID lock, CLI overrides.
todo.jsonfirst cryo-agent todo addnonoPer-project TODO list and scheduler source of truth.
cryo.logdaemonnonoAppend-only structured event log.
cryo-agent.logdaemonnonoAgent stdout and stderr, including raw tool-call output.
messages/inbox/local writers and sync daemonsnonoIncoming messages waiting for the agent.
messages/inbox/archive/daemon on receivenonoProcessed inbox messages.
messages/outbox/agent, daemon, and reportsnonoOutgoing messages waiting for delivery.
messages/outbox/archive/sync daemonsnonoOutbox messages already delivered remotely.
.cryo/cryo.sockdaemonnonoUnix domain socket for agent-daemon IPC.

Sync-channel state files

FileCreated byPersists across runsSync-safe?Purpose
gh-sync.jsoncryo-gh inityesyesGitHub Discussion sync state: repo, Discussion ID, last-read marker.
cryo-gh-sync.logcryo-gh syncnonoGitHub sync daemon log.
zulip-sync.jsoncryo-zulip inityesyesZulip sync state: site, stream, stream ID, last-imported message.
.cryo/zuliprccryo-zulip inityesno - contains API keyZulip credentials. Never commit, push, or sync this file. Already gitignored.
cryo-zulip-sync.logcryo-zulip syncnonoZulip sync daemon log.

OS service files

FileCreated byPurpose
~/Library/LaunchAgents/com.cryo.*.plistcryo start on macOSlaunchd service definition.
~/.config/systemd/user/com.cryo.*.servicecryo start on Linuxsystemd 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.

Message and TODO lifecycles inside a chamber

  1. You send a message. The dashboard, cryo send, or a sync daemon writes a file into messages/inbox/.
  2. The daemon wakes the agent. If inbox watching is enabled, that wake is immediate; otherwise the message waits for the next scheduled session.
  3. The agent claims the batch. cryo-agent receive prints the inbox contents and moves the batch into messages/inbox/archive/ right away.
  4. 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.

  1. The agent creates a TODO. cryo-agent todo add "text" --at <time> writes a pending item into todo.json.
  2. 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.
  3. 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.
  4. Retries back off visibly. Each retry gets a new ID, a (attempt k) suffix, and a 2^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

BinaryPurpose
cryoOperator CLI — init, start, status, cancel, log, watch, send, receive, wake, ps, restart, daemon.
cryo-agentAgent IPC CLI — hibernate, send, receive, time, todo. Most commands send requests to the daemon via socket; only time is local.
cryo-ghGitHub sync CLI — init, pull, push, sync, unsync, status. Manages Discussion-based messaging via an OS service.
cryo-zulipZulip sync CLI — init, pull, push, sync, unsync, status. Manages Zulip stream messaging via an OS service.
cryohubGlobal web dashboard — start, stop, status, daemon. Installs a launchd/systemd service that serves the hub UI over HTTP.
cryo-mockTest-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

ModulePurposeKey interfaces
socketUnix domain socket IPC protocol.enum Request (Ping, Hello, Hibernate, Send, Receive, TodoAdd/Done/Remove, TodoList), struct Response, fn socket_path, fn send_request.
daemon_clientThin CLI → daemon IPC wrapper.fn send_request, fn send_checked_request, fn daemon_responding, fn signal_daemon_wake.
daemonPersistent 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::effectsSession I/O abstraction (inbox claim/finalize, reply posting, TODO mutation).trait SessionEffects, enum ReplyAuthor, struct FsSessionEffects.
daemon::requestRequest parsing and hibernate-decision logic.enum DaemonRequest, enum TodoRequest, struct HibernateDecision, fn resolve_hibernate_request.
daemon::scheduleWake-time scheduling and pure next-step decisions.fn compute_sleep_timeout, fn next_wake_from_todos, fn decide_next_step.
daemon::sessionSession runtime: process spawn, request/response loop, wait/terminate.trait SessionRuntime, struct ChildExitStatus.
lifecycleSession 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.
processProcess management utilities.fn send_signal, fn terminate_pid, fn spawn_daemon.
registryUser 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.
serviceOS 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

ModulePurposeKey interfaces
configTOML project config (cryo.toml), with CLI overrides merged from runtime state.struct CryoConfig, struct ProviderConfig, fn load_config, fn save_config.
stateJSON 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.
todoPer-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.
protocolLoads 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

ModulePurposeKey interfaces
agentResolves the agent command and builds the per-session prompt.enum AgentKind, struct AgentConfig, fn agent_program, fn build_prompt.
logSession 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_statusRead 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.
sessionLegacy helper (should_copy_plan). Currently unused — plan.md must already exist in the working directory.fn should_copy_plan.

Messaging, sync channels, and reports

ModulePurposeKey interfaces
messageLow-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.
channelChannel abstraction over messaging backends.trait MessageChannel (read inbox, post reply).
channel::storeLocal 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::githubGitHub Discussions backend via gh CLI / GraphQL.fn whoami, fn gh_graphql, fn build_fetch_comments_query, fn build_post_comment_mutation.
channel::zulipZulip REST API client.struct ZulipCredentials, struct ZulipClient, struct ZulipPullResult, fn from_zuliprc.
gh_syncGitHub Discussion sync state (gh-sync.json).struct GhSyncState, fn save_sync_state, fn load_sync_state, fn is_sync_running, fn summarize.
zulip_syncZulip sync state (zulip-sync.json).struct ZulipSyncState, fn save_sync_state, fn load_sync_state, fn is_sync_running, fn summarize.
sync_commonShared types for sync backends (GitHub, Zulip).enum SyncBackend, struct SyncSummary, enum SyncLoopCommand, enum SyncCycleStatus, struct PidFile.
sync_controlOrchestration 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.
reportPeriodic session summary reports written to messages/outbox/.struct ReportSummary, fn generate_report, fn write_report_to_outbox, fn compute_next_report_time.

Web dashboard

ModulePurposeKey interfaces
hubcryohub 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::configGlobal hub config (cryohub.toml) for host, port, and dashboard-created chamber root.struct HubConfig, fn load_config, fn save_config, fn effective_config.
hub::stateShared app state, chamber index, SSE broadcast.struct AppState, enum SseEvent, fn resolve, fn refresh.
hub::discoveryRegistry-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 start installs an OS service (launchd on macOS, systemd on Linux) that survives reboots. The daemon sleeps until the scheduled wake time, watches messages/inbox/ for reactive wake, and enforces session timeout. CRYO_NO_SERVICE=1 falls back to direct background spawn.
  • Socket-based IPC. The agent talks to the daemon via cryo-agent subcommands (hibernate, send, receive, todo …) which send JSON over a Unix domain socket. cryo-agent is for the spawned agent, not for operators; only time is 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 through cryo-agent.
  • SIGUSR1 wake. cryo wake and cryo send --wake send SIGUSR1 to the daemon PID, which works regardless of the inbox-wake setting. The daemon’s signal-forwarding thread converts this into an InboxChanged event.
  • Config / state split. cryo.toml is the project config (agent, session timeout, inbox-wake behavior, report interval, provider env) created by cryo init. timer.json is runtime-only state (session number, PID, CLI overrides). CLI flags to cryo start are stored as optional overrides in timer.json.
  • Daemon-authored stand-in replies. When a session ends without any agent-authored outbox message, the daemon writes a from: cryochamber message so operators always see at least one update per session. All chamber-level messages (stand-in replies, periodic reports) share the single cryochamber sender.
  • 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 by cryo 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^k minutes, 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. EventLogger is 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 receive goes back through daemon IPC, and the daemon immediately archives the current inbox batch to messages/inbox/archive/. The daemon remembers that batch only in the current session so the next successful agent send can count as its reply, or the daemon can fall back at session end. Operator cryo receive is unrelated; it reads the agent’s outbox.
  • cryohub is 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 status also 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).