pub struct ClientSession {Show 25 fields
pub client_id: String,
pub runtime: Arc<Runtime>,
pub channel: Arc<WsChannel>,
pub host: Arc<HostState>,
pub memgine: Arc<Mutex<MemgineEngine>>,
pub browser: BrowserSessionSlot,
pub authenticated: Arc<AtomicBool>,
pub negotiated_protocol_version: AtomicU32,
pub negotiated_capabilities: Arc<RwLock<BTreeSet<String>>>,
pub inference_control: Arc<InferenceRegistry>,
pub is_host: AtomicBool,
pub agent_id: Arc<Mutex<Option<String>>>,
pub agent_method_allowlist: Arc<RwLock<Option<BTreeSet<String>>>>,
pub callback_tool_schema_digests: Arc<RwLock<HashMap<String, String>>>,
pub memory_namespace: Mutex<Option<String>>,
pub bound_memgine: Mutex<Option<Arc<Mutex<MemgineEngine>>>>,
pub current_run_id: Mutex<Option<String>>,
pub run_lifecycle_guard: Arc<Mutex<()>>,
pub permission_gate: Arc<RwLock<PermissionGate>>,
pub halted: Arc<AtomicBool>,
pub evolution_guard: CycleGuard,
pub last_chat_turn: Mutex<Option<LastChatTurn>>,
pub chat_inflight: AtomicUsize,
pub tenant: Mutex<Option<String>>,
pub tool_stream_subscribed: Arc<AtomicBool>,
}Expand description
Per-client session.
Fields§
§client_id: String§runtime: Arc<Runtime>§channel: Arc<WsChannel>§host: Arc<HostState>§memgine: Arc<Mutex<MemgineEngine>>Memgine handle. Wrapped in tokio::sync::Mutex so dispatcher
handlers can hold the lock across .await points without
risking poisoning. Migrated from std::sync::Mutex in the
car-server-core extraction (U1) per the “one-wrapper rule”.
browser: BrowserSessionSlotLazy browser session — first browser.run call launches Chromium,
subsequent calls reuse it so element IDs resolve across invocations
within the same WebSocket connection.
authenticated: Arc<AtomicBool>Per-connection auth state. Starts false; flips to true
after a successful session.auth handshake. Always considered
authenticated when ServerState::auth_token is unset (auth
disabled). Closes Parslee-ai/car-releases#32.
negotiated_protocol_version: AtomicU32Negotiated daemon JSON-RPC protocol for this WebSocket connection.
Starts at 0 (unnegotiated) and is set to
car_proto::PROTOCOL_VERSION only after an exact-version
server.handshake. The state is connection-scoped by construction, so
a reconnect always has to negotiate again before using handshake-gated
host/auth methods.
negotiated_capabilities: Arc<RwLock<BTreeSet<String>>>Capability names negotiated by this connection’s authenticated
server.handshake. Connection-scoped for the same reason as the wire
version: a reconnect cannot inherit another socket’s proof.
inference_control: Arc<InferenceRegistry>Bounded lifecycle state for inferences started on this exact socket. Keeping it per-session prevents an authenticated peer from probing IDs owned by another connection.
is_host: AtomicBoolHost-management role (Parslee-ai/car#254). Starts false; flips
to true only when the connection presents the per-launch host
token via session.auth { host_token } (validated against
ServerState::host_token). authorize_run_access requires this
for cross-agent run-trace reads — being merely host.subscribed
is no longer sufficient, which is what closes the self-elevation
hole. Cleared implicitly when the connection drops.
agent_id: Arc<Mutex<Option<String>>>Bound agent identity (#169). Some(id) once a lifecycle-agent
child has called session.auth { token, agent_id } and the
supervisor confirmed agent_id is supervised + token matches.
Used by agents.list to surface which managed agents have
actually attached vs. just being marked Running at the
process level. Cleared at disconnect by remove_session.
agent_method_allowlist: Arc<RwLock<Option<BTreeSet<String>>>>Optional daemon-method allowlist bound by successful supervised-agent
authentication. None is the backward-compatible unrestricted state;
Some(empty) denies every application method. The dispatcher reads it
once before any method-specific handler or notification interceptor.
callback_tool_schema_digests: Arc<RwLock<HashMap<String, String>>>Canonical schema digests for the narrow reverse-callback tools this client registered. Server-owned registry entries never land here.
memory_namespace: Mutex<Option<String>>Bound memory namespace (session.auth { memory_namespace }, #79/#80).
The namespace identity has to survive the handshake, not just the
lookup that binds bound_memgine. Without it the daemon knows which
graph a session is using but not what to call it, so nothing can
write that graph back to
~/.car/memory/memory-namespaces/<encoded-ns>.json — which is
why namespace memory was durable only until the next restart
(car-releases#82). Paired with bound_memgine exactly as agent_id is.
bound_memgine: Mutex<Option<Arc<Mutex<MemgineEngine>>>>Bound persistent memgine (#170). Some after session.auth
successfully attaches the connection to a daemon-owned
per-agent memgine (paired with agent_id). Memory handlers
route through ClientSession::effective_memgine which
returns this when set, falling back to the ephemeral
memgine field for browser/host/CLI connections.
current_run_id: Mutex<Option<String>>The run currently bracketed on this connection (agent run
tracing, U1). Set by runs.start before that handler
responds, so the per-turn recorder (U2) always reads the
run_id the bracket established — no race across the
concurrently-spawned dispatch tasks (KTD3). Cleared by
runs.complete. On disconnect with a still-set current run and
no recorded terminal, the daemon marks it Incomplete (R5).
run_lifecycle_guard: Arc<Mutex<()>>Serializes the authenticated run bracket on one WebSocket: start, proposal lifecycle, complete, and disconnect terminalization cannot interleave and produce ambiguous journal ordering.
permission_gate: Arc<RwLock<PermissionGate>>Per-session permission-tier gate (survey §3.4.3/§5.2.5). Backs the
permission.* JSON-RPC methods: the session holds the granted standing
tier + risk classifier. Defaults to SandboxEdit, the tier
docs/websocket-protocol.md publishes as the session default.
Arc, not a bare lock, because this is also the gate
crate::permission_gate::PermissionAdmissionGate enforces at
proposal admission (Parslee-ai/car#890). ONE gate object behind both:
a second instance would let the advisory permission.evaluate and the
enforcement point disagree about the same action, which is precisely
the bug class the admission gate was added to close.
NOTE (kernel review C1): approval records do NOT live here. The
gate’s embedded ledger is deliberately unused in the daemon — every
fingerprint-keyed HITL read/write goes through the SHARED
ServerState::approval_ledger (journal-backed), so an approval
recorded on one connection is visible to every other and survives
restart. Tier state stays per-session; the approval store is
daemon-wide.
halted: Arc<AtomicBool>Connection-local fail-stop latch. A typed terminal tool callback sets it after its proposal has aborted and rolled back; the admission gate sharing this exact atomic rejects every later proposal on this socket. It is deliberately not durable and is discarded on reconnect.
evolution_guard: CycleGuardNon-overlap guard for evolution.run on this session (kernel review
S3): the dispatcher spawns requests concurrently even on one
connection, and two interleaved cycles would double-dispatch the
evolution mechanisms. A second evolution.run while one is in flight
errs instead of overlapping.
last_chat_turn: Mutex<Option<LastChatTurn>>Last assistant turn produced on this session’s chat path, so the next
user turn can be scored as a conversation outcome (Part B). None until
the first chat reply; only set for declared-chat inference.
chat_inflight: AtomicUsizeIn-flight chat-infer count. Concurrent infers on one session have no well-defined turn adjacency (the dispatcher spawns a task per frame), so the outcome scorer only records a turn that began while the session was idle — otherwise it would score one infer’s trace against an unrelated concurrent user turn (an active mislabel of routing stats). neo #1.
tenant: Mutex<Option<String>>Tenant identity bound at session.auth { tenant_id } (linus
review C-4). Once bound, every tenant-scoped handler uses THIS
identity: a per-request tenant_id param may restate it but a
mismatch is rejected — an authenticated connection cannot hop
into another tenant’s namespace by editing request params.
None (unbound) preserves the legacy per-request behavior.
tool_stream_subscribed: Arc<AtomicBool>Whether this connection has a live tools.stream.event forwarder
task (C2). Set by tools.stream.subscribe; a concurrent second
subscribe is a no-op instead of spawning a duplicate forwarder
(which would double every notification). Arc so the forwarder
task can RESET it on exit (write timeout on a stalled-but-
recovering socket, broadcast closed) — a later re-subscribe then
spawns a fresh forwarder rather than silently no-op’ing (Q2).
Implementations§
Source§impl ClientSession
impl ClientSession
Sourcepub async fn effective_memgine(&self) -> Arc<Mutex<MemgineEngine>> ⓘ
pub async fn effective_memgine(&self) -> Arc<Mutex<MemgineEngine>> ⓘ
Returns the memgine handle the memory.* handlers should use:
the bound per-agent memgine when this session attached via
session.auth { agent_id } (#169 + #170), otherwise the
ephemeral per-WS memgine. Cheap (one async lock + Arc clone).
pub async fn bind_run_journal(&self, run_id: &str) -> Result<(), String>
pub async fn require_run_journal_binding( &self, run_id: &str, ) -> Result<(), String>
pub async fn clear_run_journal_binding( &self, run_id: &str, ) -> Result<(), String>
pub async fn append_run_terminal_event( &self, ended: &RunEnded, ) -> Result<(), String>
pub async fn append_run_cancellation_requested_event( &self, requested: &RunCancellationRequested, ) -> Result<(), String>
pub async fn append_run_cancellation_result_event( &self, result: &RunCancelResponse, ) -> Result<(), String>
pub async fn append_run_started_event( &self, started: &RunStarted, ) -> Result<(), String>
Auto Trait Implementations§
impl !Freeze for ClientSession
impl !RefUnwindSafe for ClientSession
impl !UnwindSafe for ClientSession
impl Send for ClientSession
impl Sync for ClientSession
impl Unpin for ClientSession
impl UnsafeUnpin for ClientSession
Blanket Implementations§
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<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> 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