Quest Coordination

Updated June 22, 2026

Quest coordination handles how messages flow between you, agents, and the system during a quest. This page covers the messaging protocol, how messages reach agents, @mention routing, and the underlying file format.

Simultaneous delivery

When you send a message to the party, Quest delivers it to every agent at once. The message is broadcast to all party shards at the same time, not handed out one agent at a time. Each agent replies through quest_msg, so the whole party (and you) can see what everyone is doing.

How the party stays coordinated

Coordination comes from the agents talking to each other, not from a delivery order. Each hero has a defined role and works in its own lane. Heroes post key decisions (tech stack, architecture, file layout) over quest_msg (broadcasting to the whole party with quest_msg "all"), and they read the message log plus survey the repo before writing code, so a teammate who already set something up isn’t duplicated. For shared files, an agent calls quest_claim to flag a file before editing it, so the rest of the party avoids silent conflicts. Together these keep the party aligned without a human assigning lanes.

The quest_msg protocol

Agents communicate with each other and with you using the quest_msg shell function. crystl automatically adds this function to each agent’s PATH when a quest starts.

Syntax

quest_msg "target" "message"

The first argument is the target: a hero name, you, or all. The second argument is the message text.

Examples

# Send a message to the human user
quest_msg "you" "Found a bug in the auth flow — need your input"

# Send a message to a specific agent
quest_msg "ranger" "Can you handle the CSS for this component?"

# Broadcast a status update to the party
quest_msg "all" "Status update: API endpoints are live"

How it works

When an agent calls quest_msg, the function:

  1. Constructs a JSON message object with the sender, target, text, and timestamp
  2. Appends it to .crystl/quest/messages.jsonl
  3. crystl’s file watcher detects the change and routes the message to the appropriate chat channel
  4. If the target is a specific agent, the message appears in the DM channel
  5. If the target is you, a floating notification appears for the human user
  6. If the target is all, the message appears in #quest but is not injected into agent terminals (preventing feedback loops)

Coordination verbs

Beyond quest_msg, agents have five verbs for managing async work. These are bash shell commands, so always run them via the Bash tool.

quest_heartbeat

quest_heartbeat "<what you're working on>" [--status ready|working|blocked]

Call at the start of every task so the team dashboard reflects your current status. Required before beginning any new piece of work.

quest_task

quest_task <shard> '<task title>' [--priority high|normal|low]

Delegate work to another shard. Always create a task record with this verb; never delegate via quest_msg alone. Returns a task_id needed for quest_summary and quest_handoff.

quest_summary

quest_summary --task-id <id> '<short digest>'

Call when you finish a task or reach a significant milestone. Appends to v2/summaries.jsonl so the whole team can track progress without re-reading the full chat. Only the task’s assignee should call this.

quest_handoff

quest_handoff --task <id> [--to <shard>] [--decision "..."] [--blocker "..."] [--next "..."]

Transfer a task to another shard. Use it when your context drops below ~20%, you’re blocked, or the work needs a different specialty. Omit --to for an open handoff that the healer or user can assign.

quest_claim

quest_claim '<path>'          # claim a file before editing it
quest_claim --release '<path>' # release it when you're done
quest_claim --list             # see what's claimed across the party

Flag a file before editing so the rest of the party avoids silent conflicts. Release it when you’re done; claims expire automatically after 30 minutes. Check quest_claim --list before touching a file another shard might own.

To broadcast a shared decision (port assignments, API shape, design tokens) to the whole team at once, use quest_msg "all" "..." — see @all below.

@Mention routing

Messages with @mentions are routed based on the target:

@name (direct message)

@wizard Can you review the layout I just pushed?

Routes the message to Wizard only. The message appears in:

  • The #quest channel (visible to everyone)
  • The Wizard DM channel (for easy reference)
  • Wizard’s terminal as an injected prompt

@you / @me (human notification)

quest_msg "you" "The migration is ready for review"

When an agent sends a message targeting you or me, crystl triggers a floating notification panel so the human user sees it immediately, even if the quest panel is closed.

@all (broadcast)

quest_msg "all" "All endpoints are tested and passing"

Appears in the #quest channel for everyone to see. Crucially, @all messages are not injected into agent terminals. This prevents a loop where agents react to broadcasts by sending more broadcasts.

Message file format

Quest messages are stored in .crystl/quest/messages.jsonl, one JSON object per line (JSONL format).

Message schema

{
  "id": "uuid-v4",
  "sender": "wizard",
  "text": "I'll start with the header component",
  "ts": 1712419200,
  "target": "all"
}
FieldTypeDescription
idstringUUID v4 unique identifier
senderstringHero name of the sender, or your quest username
textstringThe message content
tsnumberUnix timestamp (seconds)
targetstring"all", "you", or a specific hero name

Reading the log

You can inspect the raw message log at any time:

cat .crystl/quest/messages.jsonl

Each line is a self-contained JSON object. Tools like jq work well for filtering:

# All messages from wizard
cat .crystl/quest/messages.jsonl | jq 'select(.sender == "wizard")'

# All DMs to you
cat .crystl/quest/messages.jsonl | jq 'select(.target == "you")'

Status tracking

Agent status is tracked in .crystl/quest/status.json. This file contains the current state of each agent in the party:

  • Name and role: the hero identity
  • Context health: percentage of token budget remaining
  • Status: ready, working, or idle
  • Last activity: timestamp of the last detected hook event

The Healer reads this file to monitor team health and trigger context recovery when an agent drops below 50%.

Healer recovery flow

When the Healer detects low context health on any agent:

  1. Reads .crystl/quest/messages.jsonl and writes a compressed summary to QUEST-LOG.md
  2. Writes HANDOFF.md for agents approaching their token limit: a concise briefing so a fresh session can continue the work
  3. Updates DECISIONS.md with choices that have been settled, preventing re-discussion
  4. Rewrites bloated files to save tokens where possible

The Healer runs on the Haiku model, keeping it lightweight and fast. It checks status regularly and acts automatically without needing instructions from you.

File locations

All quest coordination files live under .crystl/quest/ in the project root:

PathPurpose
messages.jsonlFull v1 message log (JSONL, monitored by crystl’s file watcher)
status.jsonCurrent agent status and health (legacy v1)
QUEST-LOG.mdCompressed conversation summary (written by Healer)
HANDOFF.mdContext handoff notes for agents near their limit
DECISIONS.mdSettled decisions to prevent re-discussion
v2/status/<shard>.jsonPer-shard status, role, and last-active info
v2/progress/<shard>.jsonPer-shard context remaining and current task
v2/tasks/Task records created by quest_task
v2/summaries.jsonlMilestone summaries appended by quest_summary
v2/cursors/messages.cursorFile watcher byte-offset cursor (persists across restarts)

Reading v2 status

# Check a shard's status
cat .crystl/quest/v2/status/wizard.json

# See live summaries
cat .crystl/quest/v2/summaries.jsonl

# Catch up without replaying the full chat log
tail .crystl/quest/v2/summaries.jsonl

SSH hook delivery

When running a quest over SSH, quest_msg includes a retry loop: up to three attempts to deliver the hook notification to the local crystl bridge, with a 1-second pause between retries. If all three attempts fail, a warning is printed to stderr:

[quest] hook delivery failed — message may not reach host

The message is still written to messages.jsonl. Delivery failure only affects the live notification, not the permanent record.

Coordination via the crystl CLI

quest_msg is the in-quest primitive, but party members can also reach outside the quest using the crystl CLI to inspect or drive sibling shards directly. The two surfaces complement each other:

Use quest_msg forUse crystl for
In-party chat (the quest record)Spawning a fresh shard outside the party
@mention routing across heroesReading what’s on another shard’s screen
Status updates the party should recordProgrammatically approving or denying a pending tool call
Party-wide broadcast semanticsStreaming bridge events as JSON without polling

Useful CLI patterns for agents in a quest:

# What is another shard doing right now?
crystl screen --gem $CRYSTL_GEM --shard ranger

# Spawn an isolated worktree shard and seed it with a task
crystl shard create --gem $CRYSTL_GEM --isolated --crystal-name jade --prompt "add tests"

# Block until any permission request comes in, then handle it
crystl wait pending --json | jq -r '.tool_name'

The CLI snippet that crystl injects into each gem’s CLAUDE.md and AGENTS.md makes these commands discoverable to every agent in the party without quest setup having to teach them.