Expand description
L3 — the turn engine, M1: actor + turn tasks (commit-protocol.md).
One session actor per session is the sole committer to the log and the
owner of the projection ([actor]); turn tasks read actor-granted
snapshots at sample boundaries and propose entries ([turn]). Steers
admitted mid-turn are woven into the next sample (the conflict table’s
rebase row); interrupts travel out-of-band via a shared token; permission
asks are events carrying a oneshot reply.
Re-exports§
pub use hooks::NotificationKind;
Modules§
- hooks
- Extension hooks (M5). The engine consults a
Hooksimpl at two points in the tool phase — before a call runs and after it returns — scoped to the events actually used, not a 35-event schema (Delivery #5).
Structs§
- Commit
Ack - What the writer acked with, as a proposer sees it — the shipped
hotl_store::Ack, renamed at this boundary to match commit-protocol.md §Vocabulary. - Commit
Ticket - Handed back the moment the actor forwards a proposal to the writer in
AckMode::Pipelined(commit-protocol.md §Vocabulary).idandseqare carried eagerly — the actor mints the ulid and assignsseqat validation, before the write — so a proposer knows its own identity and its own commit order without waiting for durability. Only durability waits, andackis the one place a commit failure is ever reported. - Engine
Config - Ledger
Summary - Re-exported so
hotl_engine::LedgerSummaryresolves alongsideEngineEvent::LedgerReport— the loop-overhead instrument (§S1) lives in its own module ([ledger]) since it is self-contained (no dependency on the rest of the engine’s types) and independently unit-tested. What gets flushed atTurnFinished(as ancrate::EngineEvent::LedgerReport, never the canon log) and what a CI regression gate consumes. - Phase
Delta Summary - Re-exported so
hotl_engine::LedgerSummaryresolves alongsideEngineEvent::LedgerReport— the loop-overhead instrument (§S1) lives in its own module ([ledger]) since it is self-contained (no dependency on the rest of the engine’s types) and independently unit-tested. One attributable stage’s timing across every sample in a flush. - Prepared
Entry - One entry a turn task proposes to the actor, already serialized and
masked (commit-protocol.md §Proposal payloads): the actor’s disk-write
path (
hotl_store::SessionLog::append_prepared) never touchesEntryPayloadagain.itemcarries the typed value for entries that also live in the model-visible projection — the actor still needs it to updateSessionCmd::Snapshot’s answer, and keeping it here is not a second serialization: it is the exact value the turn already built in memory to producepayload, never re-parsed frompayload’s bytes (that would just move T3-16’s per-entry cost back onto the actor rather than delete it).Nonefor entries that never enter the projection (Usage,PendingAsk/AskResolved). - Projection
Head - Re-exported so
hotl_engine::ProjectionHeadnames whatSessionHandle::headhands out — the epoch-fenced published projection (commit-protocol.md §Read invariant). It lives in [actor], next to the only thing that may publish it. The projection head the actor publishes (commit-protocol.md §Read invariant, amendment 2): epoch-fenced, published only post-ack, and read by turn tasks through awatch::Receiverthey cannot write. The actor is the sole publisher — thewatch::Sendernever leaves [Head], which only [run] owns. - Session
Deps - Session
Handle - Snapshot
- Re-exported alongside
ProjectionHead: what a read of it yields, split into the durable projection and the ephemeral per-sample tail. Out-of-crate readers (fork’s history seed) name it to say which half they take. One read of the projection head, split by durability. - Spec
Digest - A compaction digest computed speculatively during the turn, overlapping the summarize call with the turn’s own samples. Indices refer to the projection the digest was planned against; the projection only appends between folds, so they stay valid until the fold that consumes them.
- Turn
Continuation - The per-turn state a compaction respawn must NOT reset (T2-2). A fold is
“no new user item, same logical turn” — so every counter that bounds that
turn has to cross it. Reconstructing a
TurnfromDefaulthere is what letmax_turnsbe defeated by the very scenario it exists for. INVARIANT: every per-turn safety counter survives a compaction respawn. Enforced bymax_turns_is_enforced_across_a_compactionandthree_folds_with_progress_do_not_exhaust_the_streak.
Enums§
- AckMode
- Whether the proposer waits for durability (commit-protocol.md
§Vocabulary). Orthogonal to
hotl_store::AckTier, which is how durable the write must be before the writer acks: canon isDurablein both modes, and only who waits changes. - AskReply
- A human’s answer to a permission ask. Widened from a
bare
boolso a denial can carry the reason to the model as tool-result feedback — a steer fused with a “no”. §2b (M4) extends this withAllowEdited/Respond; callers should treat it as non-exhaustive. - Commit
Failed - Why a pipelined commit will never be durable. Exactly two variants (commit-protocol.md §Vocabulary).
- Engine
Event - Everything the surface renders.
Askcarries the reply channel — the surface (or an allow-rule upstream) is the human on the loop. - Entry
Proposal - A batch of entries a turn task asks the actor to commit
(commit-protocol.md §Vocabulary), in the two shapes the protocol names.
Both answer with exactly one
CommitTicketinAckMode::Pipelined; they differ in what reaches the writer. - Outcome
- Phase
- Re-exported so
hotl_engine::LedgerSummaryresolves alongsideEngineEvent::LedgerReport— the loop-overhead instrument (§S1) lives in its own module ([ledger]) since it is self-contained (no dependency on the rest of the engine’s types) and independently unit-tested. The ten boundary events a turn’s sample passes through. Declaration order is the critical-path order the design doc’s per-phase deltas price (SnapshotReady−BoundaryStartprices the snapshot transport, etc.) — see [DELTA_PAIRS]. A doom-loop abort or a failed sample can legitimately skip phases past the point of failure; see [LoopLedger::stamp]. - Propose
Reply - What
SessionCmd::ProposePreparedanswers with — plainboolhas no room for the rules-epoch guard’s distinction (commit-protocol.md §Proposal payloads): a stale epoch means “rebuild under the current rules and resend”, a genuinely different repair than “the log is sealed, stop trying”. - Question
Answer - Re-exported so
hotl_engine::QuestionAnswerresolves alongsideEngineEvent::Question— the type physically lives in hotl-types (shared with hotl-tools’sQuestionSink) to avoid a hotl-tools → hotl-engine dependency cycle; seequestion_sink’s doc comment. A human’s answer to anask_userquestion (tier-1 gap #4). Lives here (not hotl-engine, where the plan first sketched it) so both hotl-tools’sQuestionSinkand hotl-engine’sEngineEvent::Questioncan share one definition without either crate depending on the other — the same cycle-avoidance the plan flagged as open, resolved the wayQuestionitself already is.NoHumanis the documented default when no reply arrives (headless,DontAsk, a dropped reply channel): the model must always get an answer, never a hang. - Sample
Stage - Whether the sample that produced a proposal had closed by the time the proposal was made — declared by the proposer, because only the turn knows. The actor never stores it as state and never routes on it; it exists so the held-steer release can check the argument it rests on instead of resting on a comment (commit-protocol.md §Read invariant, and the 72a6f1b held-steer rule).
- Session
Cmd - TurnEnd
- How a turn task ended: with a user-facing outcome, or asking the actor to compact and respawn a continuation (M2 mid-turn = terminate → compact → respawn, per commit-protocol).
Traits§
- Snapshotter
- Workspace snapshots around mutating tool batches (M3b shadow-git). Implementations run the actual snapshot off-thread; a slow or absent snapshotter must never wedge the turn.
Functions§
- event_
channel - A fresh, not-yet-consumed event channel for a session that doesn’t exist
yet — the events-side twin of
session_channel. Split out so a caller can build a tool (ask_user) whose sink already holds a live sender to this session’s own events stream before the actor exists, the same chicken-and-eggsession_channelsolves forSessionCmd. - needs_
continuation - Whether a projection ends on the model’s turn to speak (M4/#8): the last item is a user prompt or a batch of tool results the model never answered — i.e. an interrupted turn worth continuing on resume. A projection ending in an assistant item (or holding only instructions) is complete.
- question_
sink - The production
hotl_tools::ask::QuestionSinkforask_user(tier-1 gap #4): mirrorsTurn::askalmost line-for-line, but runs from inside a tool rather thanTurnitself, so it reaches the actor through channels instead ofself.propose/self.eventsdirectly. Durably commitsPendingQuestionbefore surfacing (so a process that dies mid-question leaves a dangling record replay can warn about, exactly likePendingAsk), emitsEngineEvent::Questioncarrying a fresh reply channel, races the human’s reply against the call’s own cancellation token (the same tokenTurn::askraces — an in-flightask_usermust never outlive a turn the user already cancelled), then commitsQuestionResolved. - session_
channel - A fresh, not-yet-consumed command channel for a session that doesn’t
exist yet. Split out from
spawn_sessionso a caller can build a tool (todo_write) whose sink already holds a live sender to this session’s actor before the actor exists — the registry (and the deps built from it) has to be assembled beforespawn_sessionruns, which is otherwise a chicken-and-egg with a command channelspawn_sessioncreates internally. - spawn_
session - spawn_
session_ with - Spawn against a pre-created command channel (see
session_channel); builds its own event channel. - spawn_
session_ with_ channels - Spawn against pre-created command and event channels (see
session_channel/event_channel) — what a caller needs when a session-scoped tool’s sink (ask_user) must hold live senders to both before the actor exists.