Engine contract
本頁內容尚未翻譯。
CrystalCode.Engine runs the conversation and reports what happens. A front end
hands it a SessionFrontEnd, calls it, and observes events. It never reads
engine state directly and the engine never draws, reads keys, or writes to the
console.
A front end supplies (SessionFrontEnd):
Observer(ISessionObserver) receives everySessionEvent.Approvals(IApprovalPrompt) asks the operator to approve a tool call and is told when a call auto-passes or is under review.Questions(IUserPrompt) answers the built-inquestiontool.Sessions(ISessionChooser) picks a saved session for/resume, including one workspace or every workspace.Trust(IWorkspaceTrustPrompt) asks whether to trust a directory. A front end that cannot ask returns false.
A front end calls CodingSession: Create, StartAsync, SubmitAsync
(slash commands, follow-ups while a turn runs, interrupts, and turn starts;
returns true when the operator asked to quit), CompleteTurnAsync once
TurnTask finishes, FinishTurnAsync to collect a turn before exit,
TryInterrupt, TogglePlan, SetVerbose, NotifyDraftChanged, Enqueue,
PasteClipboardImageAsync, and Close. TurnActive, TurnTask,
PlanMode, and SessionId report state. The usual turn is:
SubmitAsync, await TurnTask, then CompleteTurnAsync.
Events are immutable sealed records, one type per file under Events:
- Lifecycle and chrome:
SessionStarted,ChromeChanged,PreferencesChanged,SlashCommandsChanged,PromptHistoryLoaded,ImageHistoryInvalidated,ActivityChanged. - Turn:
UserMessageSent,TurnStarted,StreamReceived(carries Crystal’sChatStreamEvent),ToolCallsIssued,ToolResultsReceived,ModelRoundClosed,RetryScheduled,UsageChanged,TurnFinished,QueueChanged. - Conversation:
HistoryReplayed,ConversationCleared,TodosChanged. - Text and reports:
NoteWritten,ErrorWritten,HelpRequested,StatusReported,ToolsListed.
The engine publishes from whichever thread produced the change, including the
turn thread while a model round streams. Observers must be thread-safe and
return quickly; a front end with a UI thread marshals to it. Events carry
engine values, not captions: the engine reports SessionActivity and the front
end chooses the wording and layout.
The engine owns the usage numbers. It keeps the context usage and the session
cumulative totals, including the baseline that in-turn cumulative counts are
added to, and reports them through UsageChanged and TurnFinished. A front
end renders them and does not compute totals.
Some engine calls take time (a turn collecting, /compact, a model switch).
The front end awaits them without freezing its surface. The terminal host does
this by pumping the frame while the call runs.
Image markers cross the boundary as text. UserMessageSent and replayed
transcripts carry [Image #N] markers, and a marker is trusted only when it
starts with ImageMarkerText.Prefix (U+2063). A composer that attaches images
prefixes exactly the attached spans. Display cannot reference the engine, so it
holds its own copy of the prefix; ImageMarkerContractTests keeps the two
equal.
Adding a front end means referencing CrystalCode.Engine, implementing the five contracts above, and projecting events onto its own surface. The terminal host in CrystalCode is the reference implementation. The headless test in CrystalCode.Engine.Tests is the smallest correct driver. A later Avalonia desktop front end uses C# markup and does not add XAML or AXAML files.
crystal run is the headless front end in this executable, not a second
assembly. It reads one task from an argument or from stdin, applies
process-only overrides, starts one user turn, and exits. The interactive
crystal and crystal space commands apply the same session overrides:
approval, the approval model and its thinking gear, Plan or Work, thinking,
the selected model’s context budget, service tier, speed, prompt set, prompt attachments, skills,
external tools, plugins, and turn quotas. Overrides are not written to
config.json. That includes the
approval-model switch
(--approval-model, --approval-provider, --approval-model-id) and gear
(--approval-thinking).
A later interactive preference command writes the field the operator changed,
and can also write a --provider or --model passed at launch. It does not
write the other launch overrides. The task text, --format,
--show-thinking, --space, and --workspace-trust stay on crystal run.
Slash commands are rejected on crystal run so they cannot change
saved settings. Secrets are not command flags.
The run supplies RunLog or RunJsonLog, UnattendedApprovalPrompt,
UnattendedUserPrompt, UnattendedSessionChooser, and
UnattendedTrustPrompt. The trust prompt is a fail-closed fallback.
The directory check itself runs before the session is created and follows
--workspace-trust, not workspaceTrust. The approval prompt
denies every call that would have asked the operator. The question prompt
dismisses. Session choice is declined. Review and Audit still call the
reviewing model: allow executes the tool, and deny returns the reviewer’s
reason. Plan does not offer write, edit, or bash. The approval policy
also rejects those side effects when a catalog still contains them.
--format default writes a readable trace. Each model round prints thinking
text only when --show-thinking is set, then the assistant reply, then each
tool beside its own result. With --show-thinking, the reply is labeled
[Assistant]. read, list, glob, grep, and other successful
tools keep a short head excerpt and an omitted-line count. edit and write
keep their result. bash keeps the command, the exit status, and a short
tail, with a longer tail when the command fails. Other failures keep the
result text. Labels use square brackets, exit status uses parentheses, and
omitted-line markers use angle brackets. --format json writes one JSON object per
line, with type, a UTC timestamp, and sessionID, and keeps the full
tool output. Event types are
step_start, text, reasoning, tool_use, error, note, retry,
step_finish, stopped, and session. Thinking text is omitted unless
--show-thinking is set. The process then prints the saved session id.
Exit 0 means the turn completed and no operator prompt was denied or
dismissed. Exit 1 is an
invalid command, configuration, credential, workspace, or prompt set. Exit
2 is a failed model request. Exit 3 is a model-call, tool-call, or duration
budget, or context overflow. Exit 4 is a finish that was not a failure,
budget stop, or interrupt, after an operator denial or a dismissed question.
Exit 5 is an interrupt. A failure, budget stop, or interrupt outranks a
denial.