pub struct DomainSession {Show 45 fields
pub key: SessionSlot,
pub session_id: Option<SessionId>,
pub conn: Option<Arc<AgentHandle>>,
pub pending_interactions: PendingInteractions,
pub spawned_force_new: bool,
pub pending_lead_resume_note: Option<String>,
pub spawn_wrote_row: bool,
pub runtime_state: Option<RuntimeSessionState>,
pub turn_pending: bool,
pub turn_commits: u64,
pub turns_served: u64,
pub turn_open: bool,
pub pending_cancel: bool,
pub failed_turn_at: Option<SystemTime>,
pub auto_continue: Option<PendingAutoContinue>,
pub auto_continue_queued: Option<(String, Option<SessionId>)>,
pub auto_continue_spent: bool,
pub background_work: bool,
pub background_tasks: Vec<BackgroundTask>,
pub(crate) live_tasks: Vec<BackgroundTask>,
pub prompt_queue: Vec<QueuedPrompt>,
pub lifecycle_frames: Option<bool>,
background_commands: HashMap<String, String>,
staged_commands: HashSet<String>,
task_tool_use: HashMap<String, String>,
pub awaiting_login: bool,
pub dictate_overrides: DictateOverrides,
pub available_commands: Vec<AvailableCommand>,
pub available_agents: Vec<AvailableAgent>,
pub agents_emitted_this_turn: bool,
pub observed_permission_mode: Option<PermissionMode>,
pub observed_effort: Option<EffortLevel>,
pub configured_effort: EffortLevel,
pub configured_permission_mode: Option<PermissionMode>,
pub mcp_servers: Option<McpServers>,
pub context_usage: Option<ContextUsage>,
pub handoff: HandoffState,
pub current_model: Option<CurrentModel>,
pub available_models: Vec<AvailableModel>,
pub monitors: Vec<MonitorRecord>,
pub process_snapshot: Option<ProcessSnapshot>,
pub work_snapshot: Option<WorkSnapshot>,
pub card_tracker: CardTracker,
pub cards_snapshot: Vec<SubagentCard>,
pub file_index: Option<HeldFileIndex>,
}Fields§
§key: SessionSlot§session_id: Option<SessionId>Claude-issued session UUID. None until the first Connected
event from this session’s bridge. Workspace consults this when
dispatching AgentHandle calls that route by session id.
conn: Option<Arc<AgentHandle>>Agent connection handle bound to this session at spawn time.
None for pre-spawn / pre-Connect domains (the cron and gotify
delivery paths register a placeholder handle-less domain before
the spawn, which fills the handle in later).
pending_interactions: PendingInteractionsPending permission/question/elicitation oneshots indexed by the
wire tool_id / elicitation_id, oldest first. SessionTask pops
on Respond* commands; bridge inserts on every *Request event.
spawned_force_new: bool--new boot-wave flag, stamped at spawn time from
SessionLaunchSettings.force_new. For a project lead it makes
the Connected-time respawn skip the store lookup
(resume_existing = None for every worker), so they come
up fresh alongside their fresh lead. false for every non-boot
spawn.
pending_lead_resume_note: Option<String>A boot spawn’s lead restart note, waiting for the Connected hook
to deliver it as the lead’s first turn. None for every other
spawn, and cleared by the hook so a reconnect repeats nothing.
spawn_wrote_row: boolWhether the spawn that opened this session wrote the worker’s
durable row rather than adopting one that was already there.
Stamped at spawn time from the spawn’s role, and read by the
tag-write rollback to decide whether the row goes with the spawn
it is discarding. false for every lead and every non-worker.
runtime_state: Option<RuntimeSessionState>Latest runtime liveness mirrored from the session’s
session_state_changed wire messages. Operational turn state
otherwise lives on the view’s own session record; this one signal is
duplicated here so the workspace’s worker-liveness and
prompt-interception guards can tell an in-flight turn from an
idle one (a view’s own gate alone can race a just-delivered
peer / cron / gotify / slack prompt). None until the first
state message.
turn_pending: boolTurn committed at Command::Prompt routing, ahead of the
wire-lagged runtime_state; the guards OR it in.
Derived from the commit count, not a bare flag. A Result ends ONE
turn, and a prompt committed before that Result landed belongs to the
next one - so clearing a flag at the Result would erase the next
turn’s stamp and make a running seat read idle. commit_turn and
serve_turn are the only writers.
turn_commits: u64Prompts committed as turns on this occupant.
turns_served: u64Results (or transport errors) that closed one of them.
turn_open: boolFrame-derived liveness: the model’s own output proves a turn is
running, and a Result/Error ends it.
The two mirrors above miss a turn’s opening. turn_pending covers
only a routed Command::Prompt, and one Result clears it; and
runtime_state no longer arrives (“may never arrive”, above). So a
turn whose opening is thinking and prose reads NOT in flight on the
wire until its first tool call, and a client drawing from the header
shows no running row for that stretch - measured at 41 seconds on a
resumed worker (2026-10-09), where the terminal showed it running and
the client did not.
pending_cancel: boolWhether the reader asked to stop the in-flight turn. Stamped
where Command::Cancel routes and spent when that turn’s
Result lands: a cancelled turn ends with the same failed
Result a genuine error does, so failed_turn_at below cannot
tell the two apart without it.
failed_turn_at: Option<SystemTime>When the slot’s newest turn ended in failure, for the rail’s mark. Cleared when a new turn is committed, dropped with the occupant, and never set for a turn the reader cancelled.
auto_continue: Option<PendingAutoContinue>The continuation the seat’s newest failed turn left: what the model will be told, taken by the fire as soon as that failure is folded. Cleared by the fire, a new turn, a success, or the occupant changing.
auto_continue_queued: Option<(String, Option<SessionId>)>The continuation forge has sent for that failure, until the prompt
runs: its id, and the occupant the failure belonged to. A /new or
/resume the queue drains first leaves a different occupant under
it, and the words describe a history the replacement does not carry.
Not cleared by Connected - the replacement is the case it exists
for.
auto_continue_spent: boolWhether a continuation has already gone out for the seat’s current failure episode. Held until a turn completes or a view shows the seat, so a failure that persists is continued once rather than in a prompt loop.
background_work: boolWhether the CLI last reported live background work here - the
background_tasks_changed snapshot, which carries the whole set
each change, so an empty one clears. Held on the session rather
than folded per view: two views always agree about it, and a
connection failure clears it because the CLI sends no terminal
snapshot for a session that died.
background_tasks: Vec<BackgroundTask>The CLI’s background-task registry as it last reported it, each entry carrying the command its own card named where both have been seen.
The bool above says that something is running; this is what a view
draws a row from, and it is held for the same reason - the set arrives
whole on each background_tasks_changed, and a view that attached
afterwards would otherwise see the flag and not one row.
live_tasks: Vec<BackgroundTask>The CLI’s long-running calls it announced outside the registry, by
task id: a foreground call that outlives a beat reaches task_started
and its own task_notification, and never background_tasks_changed.
Held apart from the registry because that snapshot replaces itself
whole and would wipe them; merged where a view reads
(Self::task_rows), so the row says what the seat is running
whether the model backgrounded the call or not. Each keeps the CLI’s
own is_backgrounded, so a consumer can tell a foreground hold from a
registry row; the turn’s end settles the foreground ones even when no
ending frame came, and a backgrounded hold lasts only until the next
snapshot - that set is the background world, complete on every change.
prompt_queue: Vec<QueuedPrompt>Prompts in the CLI’s queue for this occupant: what a view draws as waiting, and what the pile read answers with.
Appended where the prompt is sent - the only place that knows its source and words - and advanced by the CLI’s own lifecycle frames, which are the only writer of a prompt’s state. Belongs to the occupant, so it is dropped with the background registry.
lifecycle_frames: Option<bool>Whether this occupant’s CLI advertises msg_lifecycle_v1, from its
init frame. None until one has been read.
The pile is drawn FOR it: without the frames nothing would ever settle a row, so a session that does not advertise them records no rows at all and falls back to drawing the prompt the way it was drawn before the pile existed. Unknown reads as present - the first prompt can precede the first init - and the pinned CLI always advertises it.
background_commands: HashMap<String, String>The command a tool call’s card carried, by tool-use id.
Held rather than resolved on the spot because the card arrives BEFORE
the task_started that links it to a task, so the command has to be
kept until a link names it. Cleared with the registry: it belongs to
the occupant that made the calls.
staged_commands: HashSet<String>The ids whose cards arrived since the last registry change.
What keeps the map above bounded: a command is kept while a live task
links to it, or for one registry change after its card arrived - which
is exactly the window in which the task_started naming it can land.
task_tool_use: HashMap<String, String>The tool call that began a task, by task id, from task_started.
awaiting_login: boolWhether this session is waiting on /login before it can run.
Set from the signals the CLI actually sends: an assistant message
whose error is AuthenticationFailed, or a retry frame naming the
same class. A TurnError envelope whose text reads like an auth
failure sets it too, as a fallback - that envelope’s producers are
forge’s own failed write and cancel rather than CLI output. Cleared
by a Connected and by a turn that finished, since either proves
the credential works. A fact about the session, so the lifecycle
derivation reads it rather than each view inventing the state from
the event it saw.
dictate_overrides: DictateOverridesThe /dictate overlay’s per-session normalizer-axis overrides.
Set by Command::SetDictateOverride / ::ResetDictateOverrides
and consumed when a capture finishes into
NormalizeOptions. Session-scoped and volatile: dies with the
session, never persisted.
available_commands: Vec<AvailableCommand>The slash commands the CLI last advertised for this session, from
the system/init frame’s slash_commands and from
commands_changed. Held here so a view that arrived after the
turn started reads the list through the view surface, rather than
waiting a whole turn for the next frame.
available_agents: Vec<AvailableAgent>The subagents the CLI last advertised, from the same init frame’s
agents, on the same terms as Self::available_commands.
agents_emitted_this_turn: boolWhether this turn’s system/init has already been read for the
agent catalogue. The first init of a turn carries it and a
re-fire inside the same turn repeats it, so the read arms once per
turn - the rule this fold applies.
observed_permission_mode: Option<PermissionMode>Hook-observed permission mode: higher fidelity than the
system/status mode, because
the CLI can change mode without re-emitting status.
observed_effort: Option<EffortLevel>Hook-observed effort level, on the same terms as
Self::observed_permission_mode.
configured_effort: EffortLevelThe effort level forge stamped into this session’s launch
settings, read back so a session whose hook has not fired yet
still answers with the level it is running at rather than none.
Max when the launch asked for nothing, which is forge’s default.
configured_permission_mode: Option<PermissionMode>The permission mode forge stamped into the same launch settings,
on the same terms: the answer until a hook reports one. None
when the launch pinned none.
mcp_servers: Option<McpServers>The MCP servers this session last saw, from its own bridge: MCP is configured per session, so this is not an account-wide fact.
context_usage: Option<ContextUsage>How full the context window was at the last poll.
handoff: HandoffStateThe handoff this occupant is running, if any. Belongs to the occupant: a swap that completes resets it, and a request that was parked for a seat whose occupant changed describes a session that is gone.
current_model: Option<CurrentModel>The model the session resolved to, from its connect and from every later init frame that names a different one.
available_models: Vec<AvailableModel>The model catalogue the connect resolved against, kept so a frame naming another model can be resolved the same way the connect’s own name was rather than falling back to the raw id.
monitors: Vec<MonitorRecord>The monitors this session has running, and the ones that settled
while it did, folded from the wire: the Monitor tool call, the
task id the CLI assigns it, and the transition that settles it.
The set drains once every entry in it is terminal, so a session
that ran a monitor and stopped drawing one holds none.
process_snapshot: Option<ProcessSnapshot>The last OS-level walk of the session’s process tree. The scan runs on a view’s tick rather than here, so this is the most recent one whoever asked last produced.
work_snapshot: Option<WorkSnapshot>The last scan of the session’s working tree, and when it was taken.
Written by the seat’s watch loop while a view is showing it, so a
read answers a value rather than paying for a git subprocess.
card_tracker: CardTrackerThe session’s sub-agent instances, folded a frame at a time as they arrive. Seeded from the history a connect carries, then driven by each frame.
cards_snapshot: Vec<SubagentCard>The instance list the last announcement carried, which a push is gated on: an announcement is a frame about a change, so the frame that moved nothing announces nothing.
file_index: Option<HeldFileIndex>The seat’s walked file index, and when it was taken. Written by the
seat’s own loop while a view is showing it, so the composer’s @
list reads a value rather than paying for a walk of the tree.
Implementations§
Source§impl DomainSession
impl DomainSession
Sourcepub fn awaits_permission(&self, tool_id: &str) -> bool
pub fn awaits_permission(&self, tool_id: &str) -> bool
Whether a permission prompt is still waiting on tool_id.
The session task asks the same question as it pops the slot, and this asks it where a refusal can still reach a caller: by the time the task sees the answer, the click has already happened.
Sourcepub fn awaits_question(&self, tool_id: &str) -> bool
pub fn awaits_question(&self, tool_id: &str) -> bool
The same for a question. A prompt answered already, or one that asked something else, is a dock that is gone.
Sourcepub(crate) fn replace_background_tasks(&mut self, tasks: Vec<BackgroundTask>)
pub(crate) fn replace_background_tasks(&mut self, tasks: Vec<BackgroundTask>)
Replace the registry with the CLI’s last snapshot, which carries the whole set, and fill in whatever commands are already resolvable.
A backgrounded hold is redundant by definition, because this
snapshot is the whole background set; keeping one past a later snapshot
that drops its task is exactly how a TaskStop’s row - which sends no
ending and no registry change - resurfaces. So every backgrounded hold
is dropped here. A foreground hold is dropped only when the snapshot
names its task or call: then the registry owns the call.
Also the place the held commands are pruned after a snapshot: a task
that has left both sets takes its link with it, and the staging window
for a card whose task_started has not landed closes.
Sourcepub(crate) fn note_live_task(&mut self, task: BackgroundTask)
pub(crate) fn note_live_task(&mut self, task: BackgroundTask)
Record a task the CLI announced on task_started, which is the only
signal a long foreground call leaves: the registry names backgrounded
calls alone, and a foreground one is announced here and ended by its
own task_notification or the turn it ran in.
Idempotent by task id AND by call: the CLI can announce one call under two task ids, and a second row for it would double the seat’s count. A backgrounded task its registry already names is not held at all: the snapshot is the whole background set, and the mirror would be the row that survives the snapshot dropping it.
Sourcepub(crate) fn settle_live_task(
&mut self,
task_id: &str,
tool_use_id: Option<&str>,
)
pub(crate) fn settle_live_task( &mut self, task_id: &str, tool_use_id: Option<&str>, )
Withdraw a task its own ending frame settled.
Matched by the task id or the call the frame names: one call can be announced under two task ids, and the ending may name either.
Sourcepub(crate) fn settle_foreground_holds(&mut self)
pub(crate) fn settle_foreground_holds(&mut self)
Withdraw what the end of a turn settles: its foreground holds.
A foreground call cannot outlive the turn that ran it. Its own
task_notification normally ends it, but the TaskStop tool path
sends no ending frame, and a cancelled turn can bury the one it would
have sent, so the turn’s terminal frame settles the rest. A background
hold stays: a backgrounded call outlives its turn, and the CLI’s
registry is its own source.
The turn boundary also closes the staging window: every card the turn
drew has been linked by its task_started or never named a task.
Sourcepub fn task_rows(&self) -> Vec<BackgroundTask>
pub fn task_rows(&self) -> Vec<BackgroundTask>
The seat’s live tasks as a view reads them: the CLI’s background registry first, then the announced foreground calls still running.
Deduped by task id and by call, so a hold the registry names never draws beside it before the next snapshot prunes it.
Sourcefn prune_task_maps(&mut self)
fn prune_task_maps(&mut self)
Drop the link of every task that is no longer live, and the command of every call no live task names.
A card whose command arrived since the last prune stays while it is
staged, because its task_started may still be on its way.
Sourcepub fn seed_live_tasks(&mut self, tasks: Vec<BackgroundTask>)
pub fn seed_live_tasks(&mut self, tasks: Vec<BackgroundTask>)
Seed the announced holds a fixture cannot drive through a session actor: the view surface’s read test has no wire to produce them.
Sourcepub(crate) fn hold_background_command(
&mut self,
tool_use_id: String,
command: String,
)
pub(crate) fn hold_background_command( &mut self, tool_use_id: String, command: String, )
Record the command a tool call’s card carried.
Sourcepub(crate) fn hold_task_tool_use(
&mut self,
task_id: String,
tool_use_id: String,
)
pub(crate) fn hold_task_tool_use( &mut self, task_id: String, tool_use_id: String, )
Record which tool call began a task, which is the link that names the command, and fill in that task’s command if the card is already held.
Sourcepub(crate) fn drop_background_tasks(&mut self) -> Vec<QueuedPrompt>
pub(crate) fn drop_background_tasks(&mut self) -> Vec<QueuedPrompt>
Let go of the registry and everything that resolves a command in it: a new occupant’s calls are its own, and the CLI re-sends the set only when it changes.
The queued prompts are handed back rather than dropped: they died with the process that held them, so the caller owns the row each one still deserves.
Sourcepub(crate) fn record_queued_prompt(
&mut self,
uuid: &str,
source: PromptSource,
text: &str,
attachments: Vec<ImageAttachment>,
)
pub(crate) fn record_queued_prompt( &mut self, uuid: &str, source: PromptSource, text: &str, attachments: Vec<ImageAttachment>, )
Record a prompt as waiting, at the dispatch site.
Sourcepub(crate) fn queued_prompt(&self, uuid: &str) -> Option<&QueuedPrompt>
pub(crate) fn queued_prompt(&self, uuid: &str) -> Option<&QueuedPrompt>
The entry a lifecycle frame names, while it is still waiting.
Read before the frame advances the queue, because started is a
settled state: the entry leaves the pile as the row it draws arrives.
Sourcepub(crate) fn take_queued_prompts(&mut self) -> Vec<QueuedPrompt>
pub(crate) fn take_queued_prompts(&mut self) -> Vec<QueuedPrompt>
Take every entry still waiting, leaving the pile empty.
The capability arrives after the first prompt. msg_lifecycle_v1
is read from the CLI’s init frame, and its first user message lands
before that - so a prompt recorded while the flag was None can never
be settled by a lifecycle frame. The task takes the entries and draws
them itself, the only carrier they will get.
Sourcepub(crate) fn advance_queued_prompt(&mut self, uuid: &str, state: &str) -> bool
pub(crate) fn advance_queued_prompt(&mut self, uuid: &str, state: &str) -> bool
Advance a prompt’s state from one of the CLI’s lifecycle frames.
Returns whether the id was known. A state the CLI has SETTLED drops the row, so the pile holds only prompts still waiting; a state this build cannot name leaves it standing, because a word the CLI adds later must not empty a pile nobody can see being wrong. An unknown id is not an error - a prompt dispatched before this build, or a frame for a prompt another cohort minted, simply is not in the pile.
Sourcepub(crate) fn drop_queued_prompt(&mut self, uuid: &str)
pub(crate) fn drop_queued_prompt(&mut self, uuid: &str)
Drop an entry outright - a cancel that the CLI confirmed.
Sourcefn link_background_calls(&mut self)
fn link_background_calls(&mut self)
Fill in the call that began each entry, and the command its card carried, wherever both halves are known. Called after either half lands, so the order the CLI sends them in does not matter.
Sourcepub fn new(key: SessionSlot, conn: Option<Arc<AgentHandle>>) -> Self
pub fn new(key: SessionSlot, conn: Option<Arc<AgentHandle>>) -> Self
Construct a fresh DomainSession bound to key with the
given conn. Pre-spawn / pre-Connect callers pass None to
register a placeholder domain whose handle slot fills in once
the spawn handler runs.
Sourcepub fn turn_in_flight(&self) -> bool
pub fn turn_in_flight(&self) -> bool
Whether a turn is in flight. turn_pending is the primary
signal: it is stamped synchronously when a Command::Prompt is
routed, so it covers the window before any wire echo lands.
runtime_state is OR’d in rather than trusted on its own -
session_state_changed appears in no wire-conformance baseline
and in none of the captured session JSONL, so it may never
arrive.
Shared by the prompt-interception guards and the worker activity derivation so the two cannot drift apart.
Sourcepub fn commit_turn(&mut self) -> bool
pub fn commit_turn(&mut self) -> bool
A prompt was committed as a turn.
A prompt queued into a turn already running joins that turn. The
CLI runs it inside the same turn and emits one Result for both, so
counting it as another turn would leave the outstanding count
permanently ahead and close the handoff’s idle gate for the life of
the seat. A prompt sent while the task still reads a turn in flight
but the CLI has started a new one is held busy by prompt_queue
until its own Result serves the boundary.
Sourcepub fn uncommit_turn(&mut self)
pub fn uncommit_turn(&mut self)
Undo a commit whose prompt never reached its task: the send failed, so no Result will ever serve it. Without this the seat reads a turn in flight for the life of the session, and the header’s spinner with it.
Sourcepub fn serve_turn(&mut self)
pub fn serve_turn(&mut self)
One Result (or transport error) closed the oldest outstanding turn.
One, not all of them. A prompt committed before this Result landed belongs to the next turn, so the flag is derived from what is still outstanding rather than cleared outright.
Sourcepub fn turns_outstanding(&self) -> bool
pub fn turns_outstanding(&self) -> bool
Whether a committed turn is still waiting for its Result.
Sourcepub fn reset_turns(&mut self)
pub fn reset_turns(&mut self)
Drop the turn accounting with the occupant it described.
Sourcepub fn is_idle_for_handoff(&self) -> bool
pub fn is_idle_for_handoff(&self) -> bool
Whether a handoff may start here: nothing running, nothing waiting in the CLI’s queue, and nothing parked on an answer.
All three, not just the turn. A prompt already queued is a turn the handoff’s own prompt would be folded into, and a parked interaction is a turn that has not ended; the document names both as the “otherwise idle” condition.
Sourcepub fn note_liveness(&mut self, said: TurnLiveness)
pub fn note_liveness(&mut self, said: TurnLiveness)
Fold one frame’s own claim about the turn into the liveness mirror.
The model’s output is the proof a turn is running and its Result is the
proof it ended - which is what a view derives from the same
frames. Nothing else is read: a system report, a task fact or a hook
line says nothing about whether a turn is open, so it leaves the mirror
where it stands.
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for DomainSession
impl !UnwindSafe for DomainSession
impl Freeze for DomainSession
impl Send for DomainSession
impl Sync for DomainSession
impl Unpin for DomainSession
impl UnsafeUnpin for DomainSession
Blanket Implementations§
§impl<T> AnySync for T
impl<T> AnySync for T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more