Session and turn
One user message is one turn:
- Snapshot the transcript and current tool definitions.
- Stream one chat request. Publish each delta as an event as it arrives. If that
round fails with a retryable provider error (HTTP 429, 404, 408,
5xx, timeout, network, or an incomplete stream), wait with
exponential backoff and repeat the same round. The
retrypolicy inconfig.jsonsets the limit and timing (defaults: five retries, 2s base, factor 2, 25% jitter, 30s cap when Retry-After is absent); Retry-After or retry-after-ms is honored when present. The engine publishesRetryScheduled; the terminal progress row showsRetrying In Ns (Attempt K)and counts the remaining wait down, and the transcript printsretrying model requestplus the operator message. Live stream text from the failed attempt is discarded. HTTP 401, 403, quota,invalid_prompt, and context overflow are not retried. A connection that closes while the request body is still being written is retried once, so a stale connection can be replaced, and then reported with the transport cause instead of using the rest of the budget. User cancel still interrupts immediately. - Select candidate zero.
- Stop before tools when the finish reason is not a normal stop or a tool
request.
lengthkeeps the assistant text, drops tool calls, and stops asoutput_truncated. A content filter keeps the text and stops ascontent_filtered. Any other reason stops asfailedand publishesModel stopped with finish reason '<value>'.These stops do not compact. - If the candidate has tool calls, execute the full batch through
ToolExecutor, or the multimodal bridge for an image-capable turn (approval runs first in either case). A batch that does not fit the remaining tool-call budget stops astool_call_limit_reached, keeps the assistant text and reasoning, and leaves those calls out of the transcript. - Append exact
ToolResultvalues. - Repeat until the candidate has no tool calls, a configured limit stops
the turn, or the user cancels. Before each model round, compact if the
estimated transcript or the last model-round usage is over budget. One
failed compact while still over budget stops the turn
(
context_overflow). A provider failure that survives retry stops the turn asfailed: the error is published, the progress row clears, and the transcript, including that user message, is saved.
Text and image-capable turns share executionBudget from config.json.
The default per-turn limits are 1024 model calls, 8192 tool calls, and 7 days.
The setting may be "unlimited" for all three, or an object with optional
maximumModelCalls, maximumToolCalls, and maximumDurationSeconds fields.
Omitted fields keep their defaults; null removes that field’s limit.
Model calls and duration must be positive when finite. Tool calls may be zero
to prevent tool execution. Duration must fit the runtime timer. A turn with
unlimited limits still ends on model completion, user cancellation, or context
compaction exhaustion. This host owns the live turn budget; it follows the
nullable-limit semantics of Crystal’s AgentRunLimits without using Agent
for streaming UI turns.
Built-in bash has its own per-command timer, bashTimeoutSeconds in
config.json. Omitting it keeps 600 seconds. null or "unlimited" disables
that timer. A positive integer up to the runtime timer limit replaces it.
User cancellation and the turn budget still stop the command. External tool
sets keep their own timeoutSeconds, which defaults to 120 seconds and does
not follow bashTimeoutSeconds.
7. After a completed turn, consider compaction from that last round’s
reported usage and the estimated model context. /compact (alias
/summarize) summarizes earlier turns immediately, without waiting for
that threshold. Compaction runs
inside an engine call the front end awaits; the front end keeps its
own surface live meanwhile (the terminal host pumps the frame so the
spinner, resize, and composer stay live).
The session keeps two transcripts. The archive appends user text, assistant
text, reasoning, tool calls, and tool results as they are committed, and
compaction does not change it. The model context is the list sent to the
model. Compaction replaces that list with the live system prompt, a summary,
and a recent tail. Usage, approval review, and later model calls read the
model context. Resume, fork replay, and export read the archive. /stats
tool share reads the archive, and a session file with no archive counts tools
from its saved items. A session
file written before the archive existed loads its saved items as the archive.
Images stay when either transcript still mentions them. /clear drops both.
The composer stays open while a turn runs. Enter with text enqueues a
follow-up (FIFO). Queued items stay in a panel above the composer until
they are sent. The queue is sent when the current tool batch finishes or
when the turn (thinking or conversation) ends. Empty Enter while a
follow-up is queued interrupts the turn, or compaction if that is still
running, so the queue is sent now. Empty Enter with nothing queued does
not stop the turn. /quit, /clear, /resume, and /fork stop a busy
turn and then run, including a command submitted while other session work
is still on the stack. /clear, /resume, and /fork stay in the queue
and run in the order typed. /clear, /resume, and /fork drop queued
follow-up text; the panel updates and does not keep it. During compaction
those three except /quit are refused, as are other commands that change
the session, workspace, or model.
/status, /help, /todos, and other commands that only report state or
adjust the display run immediately. /quit cancels compaction and exits.
Ctrl+C interrupts and does not drop queued text.
At an idle prompt, Ctrl+C clears the composer.
Two Ctrl+C presses on an empty composer exit.
/btw (alias /side) asks one side question from the archive
and the newest 20 side exchanges in this process. The request uses the work
model, the current system message, an empty tool list, and its own cancellation.
When that request does not fit the window, the host compacts a copy and does
not write the summary back. If the copy still does not fit, the side question
fails.
Text still streaming in the current round is not included. A tool call is not
executed. Text that arrived with it is kept; a tool call with no text fails,
and the question stays in the panel so it can be asked again. The question,
the answer, and the token usage stay out of the queue, the transcript, the
usage ledger, compaction, and the saved session. The terminal draws the
answer in a panel above the composer and leaves the status bar, transcript,
and progress row in place. Until reasoning starts, that panel shows
Waiting for the model with the same one-cell spinner as the progress row.
Once reasoning starts and before answer text, it shows Thinking with that
spinner, and the frame keeps advancing while the panel is open. Esc, Enter,
or Space closes the panel. Left and Right step through earlier answers. x
clears the in-memory thread. While that panel is open, Ctrl+C cancels an
in-flight side question, closes the panel, and leaves the main turn running.
/clear, /resume, and
/fork drop the thread. crystal run rejects the command with every other
slash command.