Quest Coordination

Updated April 5, 2026

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

Sequential Delivery

When you send a message to the party, Quest does not blast it to all agents simultaneously. Instead, it delivers the message one agent at a time in priority order:

  1. The first agent in the party receives the message
  2. Crystl monitors that agent’s hook activity to detect when it stops working
  3. Once the agent finishes, the next agent in the party receives the message
  4. This continues through all agents

Why Sequential

Simultaneous delivery causes problems:

  • Agents make the same change independently, creating conflicts
  • Agents don’t see each other’s responses, leading to duplicated work
  • In open mode, agents overwrite each other’s file edits

Sequential delivery ensures each agent sees the responses from agents before it. The second agent knows what the first agent did. The third agent knows what both previous agents did. This produces coordinated work without explicit synchronization.

Priority Order

Agents receive messages in the order they appear in the party template. When creating a custom template, put the agent whose work others depend on first. For example, a planner or architect should go before implementers.

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 — 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 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_announce

quest_announce '<message>'

Broadcast to every shard at once. Use for shared decisions — port assignments, API shape, design tokens — anything the whole team needs to align on simultaneously.

@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 and quest_announce include 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.