Skip to main content

SessionTask

Struct SessionTask 

Source
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: bool

Tracks 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: u32

Compactions 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

Source

pub(crate) async fn run(self)

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

fn execute_command(&mut self, cmd: Command)

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

fn capture_judgment(&self, message: &Message)

Hold the assistant text of a running judgment turn.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Uses the stored Weak<Workspace> reference so a Workspace drop before the task drops doesn’t double-fire or panic.

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<T> Any for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

§

fn type_name(&self) -> &'static str

§

impl<T> AnySync for T
where T: Any + Send + Sync,

§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send> ⓘ

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<S> FromSample<S> for S

§

fn from_sample_(s: S) -> S

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more