pub(crate) struct SessionTask {
pub(crate) key: SessionSlot,
pub(crate) handle: Arc<AgentHandle>,
pub(crate) command_rx: UnboundedReceiver<Command>,
pub(crate) domain: Arc<Mutex<DomainSession>>,
pub(crate) update_tx: UpdateFanout,
pub(crate) connected_once: bool,
pub(crate) workspace: Weak<Workspace>,
pub(crate) compaction_count: u32,
pub(crate) successor: Option<SuccessorWatch>,
pub(crate) teardown_done: Option<Sender<bool>>,
}Fields§
§key: SessionSlot§handle: Arc<AgentHandle>§command_rx: UnboundedReceiver<Command>§domain: Arc<Mutex<DomainSession>>§update_tx: UpdateFanout§connected_once: boolTracks whether the first Connected has been emitted. The
second-and-beyond Connected on the same task drives
SessionUpdate::SessionReplaced instead (covers /new,
login, logout flows).
workspace: Weak<Workspace>Weak reference to the parent crate::Workspace. Used inside
Self::translate_event’s Connected arm to call back into
Workspace::record_connected_session
so the project catalog stays current as freshly-spawned
sessions reach Connected. Weak avoids the Workspace<->Task
reference cycle (Workspace holds Task’s command_tx; Task
holds Workspace).
compaction_count: u32Compactions this run has crossed, for the handoff policy’s
after_compactions backstop.
successor: Option<SuccessorWatch>The handoff this session arrived by, while its first ten turns are
measured. None for a session that arrived another way - the counts
exist to tune the trigger, and a session nobody handed to has
nothing to compare against.
teardown_done: Option<Sender<bool>>Fires as this task drops - after run’s exit path has disconnected
the subprocess (waited it out up to the close-wait budget, then
SIGKILLed it) - so a despawn can hold its worktree removal until
the session’s own child is reaped. Held for every exit, early
returns and panics included, so a waiter never hangs on a task that
ended some other way.
Implementations§
Source§impl SessionTask
impl SessionTask
pub(crate) async fn run(self)
Sourcefn declared_models_for_session(&self, event: AgentEvent) -> AgentEvent
fn declared_models_for_session(&self, event: AgentEvent) -> AgentEvent
Replace a Connected event’s CLI-advertised available_models
with the session’s org’s declared models before translate_event
sees it (covering both the Connected and SessionReplaced
emits). Synchronous - the rows are read from config, not fetched.
Sourcepub(crate) fn translate_event(&mut self, event: AgentEvent) -> bool
pub(crate) fn translate_event(&mut self, event: AgentEvent) -> bool
Translate one AgentEvent into the matching SessionUpdate.
Updates DomainSession in-place before each emit.
Returns true to keep the run loop running; false when the
event is terminal for this task and the loop must exit (so the
exit path releases the session and disconnects the subprocess).
Sourcefn record_queued(
&self,
uuid: &str,
source: PromptSource,
text: &str,
attachments: &[ImageAttachment],
) -> bool
fn record_queued( &self, uuid: &str, source: PromptSource, text: &str, attachments: &[ImageAttachment], ) -> bool
Record a prompt as waiting and announce it: the row’s words and sender
ride SessionUpdate::PromptQueued, where the lifecycle frames carry
only the id and the state.
Returns false for a session whose CLI has said it does not carry
those frames: a row nothing can settle is worse than no row, and the
caller draws the prompt at dispatch instead, the way it did before the
pile existed.
Sourcefn emit_prompt_row(
&self,
text: &str,
attachments: &[ImageAttachment],
uuid: &str,
source: PromptSource,
)
fn emit_prompt_row( &self, text: &str, attachments: &[ImageAttachment], uuid: &str, source: PromptSource, )
The row a person’s prompt draws.
Emitted at started wherever the CLI can follow a queue (#2816):
the CLI takes a prompt sent while a turn runs at its own start - at
that turn’s end, or absorbed into the running turn - and emits
command_lifecycle: started then, so a row forged at dispatch drew
above the answer still being written. The held copy needs it here for
the other half: the CLI never echoes a stdin prompt on the wire, so
this frame is the page’s only carrier of the reader’s own words.
Two fallbacks draw at dispatch, because no started is coming: a
CLI that cannot follow a queue, and a dispatch that fails after the
prompt was recorded. An occupant that dies or is replaced draws the
queued rows on the connect or the failure frame instead.
Sourcefn settle_lost_queued(&self, prompts: Vec<QueuedPrompt>)
fn settle_lost_queued(&self, prompts: Vec<QueuedPrompt>)
Settle the prompts still in the queue when the occupant went.
No row is forged for one that never started. A live row would have no read carrier: the transcript keeps only the enqueue attachment and no start, so a reload would drop it and the two paths would disagree. The pile’s ending line carries the words instead, the same carrier a refused or discarded prompt uses, and the card settles with it.
fn execute_command(&mut self, cmd: Command)
Sourcefn continuation_lost_its_occupant(&self, uuid: &str) -> bool
fn continuation_lost_its_occupant(&self, uuid: &str) -> bool
Whether uuid is the continuation forge sent for this seat and the
occupant it was sent for is no longer the one here. Taken either way:
the prompt is running now, so the matching is spent.
Sourcefn session_id_string(&self) -> Option<String>
fn session_id_string(&self) -> Option<String>
Snapshot the session id stamped on DomainSession, formatted
as a String. Most agent commands need the wire session_id;
returns None when the bridge hasn’t emitted its first
Connected yet.
Sourcefn emit(&self, update: SessionUpdate)
fn emit(&self, update: SessionUpdate)
Send update to the workspace fan-out; log when no subscriber
took it, so a regression there leaves a trail.
Sourcefn drain_review_activity_for(&self, caller: &SessionSlot)
fn drain_review_activity_for(&self, caller: &SessionSlot)
Flush caller’s buffered review activity into its batched
notices. Every path a turn can end on calls this, so it has to be
idempotent: drain_review_activity removes the buffer, and a
caller with nothing buffered yields no notices. A turn that ended
normally therefore leaves the teardown drains with nothing to do.
caller is explicit because the replacement path drains a key
that DomainSession.key is about to move off, and the buffer
entry becomes unreachable once it has.
Sourcefn fresh_session_id(&self) -> Option<String>
fn fresh_session_id(&self) -> Option<String>
The id a /new starts under: a fresh one, recorded against the row
this session belongs to, so a restart re-enters the new session
rather than the one /new replaced. None when the session has no
row to record against, which leaves the CLI to pick its own id.
Sourcefn deliver_pending_brief(&mut self, key: &SessionSlot, occupant: &str)
fn deliver_pending_brief(&mut self, key: &SessionSlot, occupant: &str)
Give a successor the brief its swap wrote, as its first turn. The brief forges its own row, which the client draws as the handoff procedure.
Sourcefn complete_handoff(&mut self, note: String)
fn complete_handoff(&mut self, note: String)
The skill’s last act: the note the successor’s brief carries.
Only while this seat is already handing over. The entry is
/handoff or the automatic trigger; a call with no handoff in flight has no
turn to belong to and is stated as refused, never turned into a start.
Sourcefn request_handoff(&mut self, reason: HandoffReason, fill: Option<FillReading>)
fn request_handoff(&mut self, reason: HandoffReason, fill: Option<FillReading>)
A handoff was asked for. The seat must be otherwise idle - no turn, no queued prompt, nothing parked on an answer - or the request waits for the next turn end: a handoff is never injected into a turn, and its prompt must run as a turn of its own.
Sourcefn begin_handoff(&mut self)
fn begin_handoff(&mut self)
Start the handoff’s own turn.
The swap’s gate opens HERE, at the decision, not when the brief is built: everything the seat is sent from this point waits for the successor, and only the judgment prompt is let through.
Sourcefn attribute_dispatch(&self, msg: Message) -> Message
fn attribute_dispatch(&self, msg: Message) -> Message
The same frame, naming the dispatch that owns it.
A sub-agent’s frames carry the dispatch their parent fired, and only
the outermost dispatch is a card, so a deeper frame names no expansion;
a permission_denied frame names the agent and no dispatch at all.
Both walk the tracker’s parent links to the owning card, and a frame
whose chain reaches none is left alone.
Sourcefn capture_judgment(&self, message: &Message)
fn capture_judgment(&self, message: &Message)
Hold the assistant text of a running judgment turn.
Sourcefn after_turn_ended(&mut self)
fn after_turn_ended(&mut self)
Act on a turn that ended normally: finish a running handoff, or start one that arrived while the turn was running.
Sourcefn abort_cancelled_handoff(&mut self)
fn abort_cancelled_handoff(&mut self)
A cancelled judgment turn ends the handoff rather than finishing it: the reader stopped the prompt the brief was coming from, so the seat keeps its session and what waited during the swap goes back to it.
Sourcefn abort_handoff(&mut self, why: &str)
fn abort_handoff(&mut self, why: &str)
End a handoff that will not finish: clear its state, close its gate, hand back what the gate held, and tell the seat why.
Every way the judgment prompt can settle without running lands here. A prompt the reader cancels, or the CLI refuses or discards, never produces the turn whose Result would finish the handoff - and left alone it would hold the gate open and drop every later handoff for the life of the seat.
Sourcefn abort_handoff_state(
workspace: &Arc<Workspace>,
domain: &Arc<Mutex<DomainSession>>,
update_tx: &UpdateFanout,
key: &SessionSlot,
why: &str,
)
fn abort_handoff_state( workspace: &Arc<Workspace>, domain: &Arc<Mutex<DomainSession>>, update_tx: &UpdateFanout, key: &SessionSlot, why: &str, )
End a running handoff from wherever can reach the seat’s state: the task itself for a cancel or a settlement, and the deadline task when the judgment prompt never starts.
Sourcefn count_successor_prompt(&mut self)
fn count_successor_prompt(&mut self)
Count the successor’s prompts, and mark the tenth for measurement.
Prompts, not turn boundaries. successor_counts reads the
transcript’s user prompts, so counting boundaries here would compare
ten endings against ten prompts that included forge’s own brief turn.
The brief and every other forge delivery carry PromptSource::Forge,
so only a person’s send counts.
Sourcefn measure_successor_at_boundary(&mut self)
fn measure_successor_at_boundary(&mut self)
Measure the successor once its tenth prompt’s turn has ended, when the transcript holds all ten. A successor that leaves before then goes unmeasured rather than writing half a comparison.
Sourcefn maybe_trigger_handoff(&mut self)
fn maybe_trigger_handoff(&mut self)
The automatic trigger: at a turn boundary with no handoff running, ask the CLI for this seat’s own fill reading.
Asked at every boundary, page or no page. A view refreshes the reading, but an unattended worker has no view, so a trigger that read only what a page had asked for would never fire on exactly the sessions that run longest. A failed ask skips this boundary and says so at debug; the denominator is never guessed.
Sourcefn evaluate_handoff_trigger(
&mut self,
reading_for: &str,
reported: Option<u8>,
total_tokens: Option<u64>,
effective_max_tokens: Option<u64>,
)
fn evaluate_handoff_trigger( &mut self, reading_for: &str, reported: Option<u8>, total_tokens: Option<u64>, effective_max_tokens: Option<u64>, )
Judge a reading the trigger asked for: a fill at or over the configured share, or a compaction the fill trigger did not stop.
Sourcefn swap(&mut self, reason: HandoffReason, judgment: Vec<HandoffSection>)
fn swap(&mut self, reason: HandoffReason, judgment: Vec<HandoffSection>)
Replace this seat’s occupant with a new session that continues from
the brief, on the same order /new uses.
Sourcefn drain_parked(&self)
fn drain_parked(&self)
Drain everything parked for this session’s slot on its first
Connected, in arrival order. The take is the session’s own
(org, project, label) bucket, so a payload parked for a team
worker never lands on the project’s lead.
Sourcefn deliver_parked_peers(
&self,
workspace: &Arc<Workspace>,
pending: Vec<ParkedPeer>,
)
fn deliver_parked_peers( &self, workspace: &Arc<Workspace>, pending: Vec<ParkedPeer>, )
Deliver the peer prompts parked for this session’s slot. Each is
re-dispatched as a normal Command::Prompt against self.key.
The existing prompt-delivery path handles it identically to a
user-typed prompt - the only difference is the prose body
carries the [Message id=m-…] wrapper
that forge_server::envelope::detect_inbound matches, which
the chat renders as a styled peer block.
Sourcefn deliver_parked_crons(
&self,
workspace: &Arc<Workspace>,
pending: Vec<PendingCron>,
)
fn deliver_parked_crons( &self, workspace: &Arc<Workspace>, pending: Vec<PendingCron>, )
Deliver the cron prompts parked for this session’s slot - the
bucket a due cron filled while the slot was asleep. Each is
echoed as a cron block (missed-marked when overdue) and
re-dispatched as a plain Command::Prompt.
Sourcefn deliver_parked_gotify(
&self,
workspace: &Arc<Workspace>,
pending: Vec<GotifyNotification>,
)
fn deliver_parked_gotify( &self, workspace: &Arc<Workspace>, pending: Vec<GotifyNotification>, )
Deliver the Gotify notifications parked for this session’s slot.
Each is echoed into chat as a notification block and re-dispatched
as a plain Command::Prompt, landing as an ordinary user turn.
Mirrors Self::deliver_parked_crons, which echoes its own
block before dispatching.
Sourcefn deliver_parked_slack(
&self,
workspace: &Arc<Workspace>,
pending: Vec<SlackMessage>,
)
fn deliver_parked_slack( &self, workspace: &Arc<Workspace>, pending: Vec<SlackMessage>, )
Deliver the Slack messages parked for this session’s slot. Each
conversation’s is echoed into chat as one notification block and
re-dispatched as a plain Command::Prompt, landing as an ordinary
user turn. Mirrors Self::deliver_parked_gotify.
Trait Implementations§
Source§impl Drop for SessionTask
Drop hook: on SessionTask exit (any reason - graceful close,
crash, panic), drop whatever is parked for this session’s slot - a
message waiting on a session that no longer exists is acknowledged
back to its sender rather than delivered to nobody.
impl Drop for SessionTask
Drop hook: on SessionTask exit (any reason - graceful close, crash, panic), drop whatever is parked for this session’s slot - a message waiting on a session that no longer exists is acknowledged back to its sender rather than delivered to nobody.
Uses the stored Weak<Workspace> reference so a Workspace drop
before the task drops doesn’t double-fire or panic.
Auto Trait Implementations§
impl !RefUnwindSafe for SessionTask
impl !UnwindSafe for SessionTask
impl Freeze for SessionTask
impl Send for SessionTask
impl Sync for SessionTask
impl Unpin for SessionTask
impl UnsafeUnpin for SessionTask
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