# Kennedy logical sessions
This library runs one Kennedy logical session. It owns lifecycle and mode authorization, context recovery, provider and subagent hosting, checkpoints, Kweb write staging and final commit, session-launch authorization and intent recovery, and service compatibility.
Deterministic rendering is delegated to `kcode-kennedy-session-presentation`; strict existing-Ktool decoding and media/model/document validation to `kcode-kennedy-session-tool-contracts`; Kweb plan ownership to `kcode-kennedy-kweb-plan`; object resolution and delivery construction to `kcode-kennedy-session-objects`; durable input staging and transcript reconstruction to `kcode-kennedy-session-ingress`; and durable yield-bound admission identity to `kcode-kennedy-turn-admission`.
## Public API
```rust
pub use kcode_kennedy_session_objects::ResolvedObject;
pub use kcode_kennedy_turn_admission::PendingTurnAdmission;
pub use kcode_telegram_session_coordinator::validate_file_name
as validate_delivery_file_name;
#[derive(Clone)]
pub struct Service { /* private shared capability handles */ }
pub struct Capabilities {
pub load_fixed_connections: bool,
pub kmap: kcode_kweb_manager::KwebManager,
pub intelligence: kcode_intelligence_router::Intelligence,
pub history: kcode_session_history::SessionHistory,
pub speech_classifier: Arc<kcode_speaker_system::SpeechClassifier>,
pub dev_tools: kcode_dev_tools::Service,
pub agents: kcode_agent_runtime::AgentRuntime,
pub telegram: kcode_telegram_session_coordinator::Service,
}
impl Service {
pub fn new(capabilities: Capabilities) -> Self;
}
pub struct RuntimeModel {
pub model: String,
pub reasoning_effort: String,
pub context_window_tokens: u64,
}
impl RuntimeModel {
pub fn from_intelligence(
runtime: kcode_intelligence_router::RuntimeModel,
) -> Self;
}
pub enum AgentMode {
Conversation,
FreeTime,
Wakeup,
Ingress { record_id: Option<String> },
}
pub enum TurnDeadlineKind { Telegram, SelfTimeHardStop }
pub struct TurnDeadline {
pub kind: TurnDeadlineKind,
pub at: chrono::DateTime<chrono::Utc>,
}
pub struct SessionOptions {
pub session_type: String,
pub root_node_ids: Vec<String>,
pub reference_root_node_ids: Vec<String>,
pub channel: serde_json::Value,
pub free_time: serde_json::Value,
pub orchestration: serde_json::Value,
pub provenance_id: Option<String>,
pub mode: AgentMode,
pub source_session_type: Option<String>,
pub group_context: serde_json::Value,
pub rust_lib_session_id: Option<String>,
}
impl SessionOptions {
pub fn conversation(
session_type: impl Into<String>,
roots: Vec<String>,
) -> Self;
}
pub struct ResolvedObject {
pub object_id: String,
pub bytes: Vec<u8>,
pub file_name: String,
pub media_type: String,
pub transport_kind: Option<String>,
}
pub struct SessionTurn { /* opaque */ }
pub struct PendingSessionInference { /* opaque */ }
pub struct SessionInferenceWake { /* opaque */ }
pub enum TurnBoundary {
Await(PendingSessionInference),
Yield(SessionTurn),
Complete(Option<String>),
}
impl PendingSessionInference {
pub async fn wait(self) -> SessionInferenceWake;
}
pub struct Session {
pub session_type: String,
pub channel: serde_json::Value,
pub free_time: serde_json::Value,
pub orchestration: serde_json::Value,
pub provenance_id: Option<String>,
pub rust_lib_session_id: String,
pub root_node_ids: Vec<String>,
pub reference_root_node_ids: Vec<String>,
pub started_at: String,
pub transcript: Vec<serde_json::Value>,
pub pending_turn: bool,
pub pending_external_event_id: Option<String>,
pub completed: bool,
pub rounds_used: u64,
}
impl Session {
pub async fn new(
service: Service,
system_prompt: String,
subagent_codex_prompt: String,
runtime: RuntimeModel,
started_at: String,
options: SessionOptions,
restored: Option<&serde_json::Value>,
) -> anyhow::Result<Self>;
pub fn append_final_user_message(
&mut self,
text: &str,
metadata: &serde_json::Value,
) -> bool;
pub fn stage_source_message(
&mut self,
kennedy: bool,
text: &str,
metadata: serde_json::Value,
) -> anyhow::Result<()>;
pub fn answer_for_external_event(
&self,
id: &str,
) -> Option<&serde_json::Value>;
pub fn responses_for_external_event(
&self,
id: &str,
) -> Vec<&serde_json::Value>;
pub fn resolve_object(
&mut self,
object_id: &str,
) -> anyhow::Result<ResolvedObject>;
pub fn requires_history_ingress(&self) -> bool;
pub fn stage_free_time_opening(&mut self) -> bool;
pub fn stage_wakeup_opening(&mut self) -> anyhow::Result<bool>;
pub fn begin_user_turn(
&mut self,
text: &str,
metadata: &serde_json::Value,
) -> bool;
pub fn reset_exhausted_turn_rounds_for_retry(&mut self);
pub fn interrupt_current_turn(&mut self) -> anyhow::Result<()>;
pub fn begin_pending_turn(
&mut self,
operation_id: uuid::Uuid,
turn_deadline: Option<TurnDeadline>,
) -> anyhow::Result<Option<SessionTurn>>;
pub async fn admit_pending_turn<C, F>(
&mut self,
turn: &mut SessionTurn,
admission: PendingTurnAdmission,
recorded_at: &str,
checkpoint: &mut C,
) -> anyhow::Result<bool>
where
C: FnMut(serde_json::Value) -> F + Send,
F: Future<Output = anyhow::Result<()>> + Send;
pub async fn advance_pending_turn<C, F>(
&mut self,
turn: SessionTurn,
checkpoint: &mut C,
) -> anyhow::Result<TurnBoundary>
where
C: FnMut(serde_json::Value) -> F + Send,
F: Future<Output = anyhow::Result<()>> + Send;
pub async fn apply_inference_wake<C, F>(
&mut self,
wake: SessionInferenceWake,
checkpoint: &mut C,
) -> anyhow::Result<TurnBoundary>
where
C: FnMut(serde_json::Value) -> F + Send,
F: Future<Output = anyhow::Result<()>> + Send;
pub async fn run_pending_turn<C, F>(
&mut self,
operation_id: uuid::Uuid,
turn_deadline: Option<TurnDeadline>,
checkpoint: C,
) -> anyhow::Result<Option<String>>
where
C: FnMut(serde_json::Value) -> F + Send,
F: Future<Output = anyhow::Result<()>> + Send;
pub fn refresh_telegram_group_context(
&mut self,
group_context: &serde_json::Value,
current_message_id: Option<&str>,
) -> anyhow::Result<()>;
pub fn finalize_free_time(&mut self, reason: &str) -> anyhow::Result<()>;
pub fn commit_current_write_session(&mut self) -> anyhow::Result<()>;
pub fn snapshot(&self) -> anyhow::Result<serde_json::Value>;
pub async fn release_managed_sources(&self);
}
pub fn is_ingress_time_expired(error: &anyhow::Error) -> bool;
impl Session {
pub fn mark_previous_ingress_attempt_timed_out(&mut self);
}
```
`SessionOptions::conversation` creates an idle conversation with ordered writable roots. Reference roots are sorted, deduplicated, and removed when also writable. The application supplies composed prompts, selected runtime facts, roots, mode, and transport metadata. The fixed-loading flag is applied consistently to primary and box-free child Kweb contexts.
## Stepped primary turns
The owned stepped primary-turn interface uses opaque `SessionTurn`, `PendingSessionInference`, and `SessionInferenceWake` values plus `TurnBoundary::{Await, Yield, Complete}`.
Serial preparation and wake application occur while the caller holds `&mut Session`. `PendingSessionInference::wait` owns its provider action and does not retain a Session, Chatend, Kweb plan, `KennedySessionHost`, checkpoint callback, or borrowed data. Provider startup, provider-event waiting, and native tool-result response can therefore wait independently of mutable Session ownership. The resulting owned wake is later applied serially to the same logical turn.
`Await` requests one owned provider wait. `Yield` means the completed provider transition, receipts, semantic state, and required checkpoints have been applied, while no later inference has been rendered, prepared, or started. `Complete` returns the same terminal `Option<String>` outcome used by `run_pending_turn`.
`admit_pending_turn` is callable only with the current live `SessionTurn` returned by a successful `Yield`. It validates the pending and yielded state before mutation and starts no provider work. The caller-supplied `recorded_at` value is passed unchanged to durable admission staging and timestamps the admission journal events. Zero or more admissions may be accepted serially at one Yield; every accepted admission is durably staged and checkpointed before `Ok(true)`, while an empty `User` admission returns `Ok(false)` without a checkpoint. After draining admissions, the caller invokes `advance_pending_turn` exactly once.
A `User` admission makes the latest admitted genuine user event the current launch authority and external-response identity and resets both persistent and turn-local round counts. It preserves the turn lease, operation lineage, deadlines, provider affinity, and all other opaque turn state. A `Source` admission stages user- or Kennedy-owned source content while preserving launch authority, external identity, and both round counts. When several User admissions are accepted before the next inference, the latest one governs launch authority and response identity. Unfinished older launch intents remain subject to the established reconciliation and sealing rules.
Both admission variants require object-valued metadata. `pendingTurnAdmissionKind` is reserved by the admission library. Marked journal-ahead admissions restore in durable order without a checkpoint-schema migration; a marked Source admission never grants user-turn authority.
Invalid metadata, a potentially partial durable write, recovery failure, snapshot failure, or ambiguous checkpoint failure poisons the yielded handle. The caller must discard the Session and reconstruct it from its authoritative journal and last checkpoint rather than retrying the admission or advancing the handle. Stale or foreign validation occurs before cleanup and cannot clear deadlines belonging to a newer turn.
Every accepted external or controller mutation through the ordinary Session lifecycle or input methods still invalidates outstanding stepped values and clears transient turn and provider deadlines. `admit_pending_turn` is the sole yield-bound input path that preserves the active stepped turn. Callers using the stepped API must route intervening input through this method and other mutations through their public controller methods rather than directly modifying legacy public fields.
Transient token identity rejects stale or duplicate stepped values without interior mutability and without changing the serialized checkpoint format. The compatibility `run_pending_turn` adapter waits and applies each `Await`, then immediately continues at every `Yield`, preserving the established synchronous caller contract.
This release does not add a mailbox, background task scheduler, concurrent tool execution, parallel subagents, transport ingress while inference is running, persistence migration, or downstream adoption. Existing tool and capture work may still run serially while Session is borrowed.
## LaunchSession
`LaunchSession` is described to the primary provider only during a pending turn created by a genuine current user admission in `Conversation` mode for a `conversation`, `telegram`, or `telegram-group` session. Dispatch independently rechecks the same authority. It is unavailable to subagents and to synthetic launch bootstrap, ingress, self-time, wakeup, and other autonomous turns. Caller/model metadata and restored provenance never grant authority.
Arguments are a strict object containing exactly `directive: string` and `contextNodeIds: array<string>`. The directive must be nonblank after trimming and is otherwise retained byte-for-byte without a package limit. The context list may be empty and has no package cap; order is preserved. Each entry must be a distinct canonical node ID already present as a fully loaded node in the current parent context. Pending IDs, markers, malformed IDs, duplicates, and unloaded nodes are rejected before launch intent or child effect.
At most ten new durable launch intents may be created during one genuine user turn. Invalid calls, unauthorized calls, and replay reconciliation do not consume that allowance. A later genuine user turn receives a fresh allowance while unfinished older intents remain recoverable. The eleventh call fails before a launch intent or child effect.
Before the lower launch effect, the checkpoint contains a durable intent with the invocation UUID, authoritative user-turn event ID, stable timestamp, parent session ID, effective context tokens, ordered writable roots, ordered reference roots, and ordered selected context IDs. The directive remains only in the durable `ToolInvoked` arguments. The invocation UUID is the child session ID. Session History determines the command ID and lifecycle-last ordering. A successful result is exactly a compact JSON object with only `sessionId` and `commandId`.
The child is an ordinary browser conversation. Its state contains the ordered roots, reference roots, `launchContextNodeIds`, minimal top-level deny-only `launchProvenance`, and `orchestration.launchBootstrapPending`. Its first synthetic input carries the exact directive plus package-owned deny-only provenance and cannot launch another child. A later genuine user turn may launch normally. Child construction loads ordinary roots and selected canonical IDs fresh as direct full nodes and fails closed if they are unavailable. No parent box bodies, transcript, provider continuation, pending resources, staged Kweb plan, source handles, attachments, subagents, or unresolved effects are copied.
Construction validates the checkpoint event cursor against the durable journal and recognizes authority only from a genuine authoritative User message event. Ordered journal-ahead marked admissions are replayed when the journal is newer than the checkpoint; marked Source admissions never grant authority, and the latest marked User admission wins. Unmarked ordinary user-turn recovery remains compatible. A pending synthetic-bootstrap marker is consumed in order and denies that synthetic turn.
Every unsealed construction restores and reconciles unfinished intent-backed launches in recorded order before generic unfinished-tool repair, in every mode. A storage-classified lower failure returns before broad repair and leaves the invocation unresolved. Exact accepted effects are recovered by Session History idempotency. Terminal invalid-input or conflict results may be completed as failures. Generic repair then closes remaining pre-intent invocations. Completed prior-turn intents are pruned; current-turn intents and every unfinished prior intent remain. An unresolved intent-backed launch blocks interruption, sealing, and every broad repair site before mutation.
## Lifecycle, context, and tools
`Session::new` creates or reopens the durable Session History journal, restores compatible state, loads Kweb roots, and prepares history ingress when requested. The supplied start time must be RFC 3339 and match restored state. A sealed autonomous journal continues commit recovery. An unsealed ingress retry resets attempt-local provider state and revalidates loaded nodes.
Primary initial loads, reopened-session restoration, ingress revalidation, and explicit `LoadNodes` use cache-safe Kweb synchronization. Unseen load boxes append. When a source node changes, canonical state advances without immediately changing its visible representation: the exact old visible bytes remain. Provider preparation automatically discovers, records, and deduplicates stale markers. The latest content becomes visible only after explicit `HydrateBox`, which may reset provider cache. Mutable staged Kweb synchronization and managed-source snapshot application deliberately reveal latest content; generic post-tool dehydration and context-overflow recovery remain separate.
`begin_user_turn` rejects empty input or an already-pending turn, durably stages text and attachments, resets round accounting, and opens a turn. `append_final_user_message` and `stage_source_message` remain ordinary invalidating input paths and do not preserve a yielded handle. Free-time and wakeup openings are synthetic and grant no user-turn authority. `interrupt_current_turn` first refuses unresolved launch intents, then repairs ordinary unfinished tools, records a stop notice, and returns to idle.
`run_pending_turn` checkpoints after durable semantic transitions. It returns a terminal conversational answer when one is newly produced and otherwise returns `None` for an emitted object, source termination, or autonomous completion. The application-owned operation ID supplies cancellation lineage. Provider affinity, sparse context markers, native resume reconciliation, accounting, and the ingress timer retain their established durable behavior. The transient turn deadline is never persisted.
The first provider call sends the complete projection. Compatible later calls use native continuation and only later projected events; rewritten provider-visible history clears affinity and restarts with one complete projection. Checkpoint state is opaque and must be persisted as a unit.
Subagents use one box-free projection and one fresh native provider turn. They cannot use `LaunchSession`, nested `RunSubagent`, `EndSession`, or parent box controls. Delegated object and Telegram effects preserve their ordinary parent behavior. Managed-source and Kweb updates alter child current state without copying parent boxes.
Kweb plan restoration, mutation, projection, and commit material are owned by the leaf plan library. This crate retains Kmap loading, pending-ID allocation, Chatend synchronization, archive/object assembly, the one Commit Session effect, receipt handling, and lifecycle reconciliation. Staged checkpoint field `kwebPlan` and established mutation behavior, ordering, and pending IDs are preserved. A successful `ConnectNodes` result keeps its existing staged-connections line and appends `Post-call recent connection counts: <id>: <count>, ... .`; each count is the distinct input node’s final staged `recent_connections` length, and nodes appear once in first-input order.
`NoteToSelf` accepts one nonblank message without a package length cap. Media prompts are passed unchanged after nonblank validation. Existing concrete security, path, MIME, provider, object-size, authority, and context-capacity checks remain at their owning boundaries.
Task-board tools map directly to their corresponding task-board methods. Category lookup uses the first user root and optional pagination. Kweb writes remain unavailable in read-only conversations. `resolve_object` returns exact session-pending or canonical object bytes and authoritative metadata. Telegram delivery retains channel eligibility and attachment validation.
## Persistence and authority
Session History owns the durable journal, launch command identity and lifecycle ordering, staged objects, archive, provider-resume reconciliation, and journal-ahead admission evidence. Kweb Manager owns database reads and writes; the Kweb loader owns durable read projection; Agent Runtime owns provider execution; the subagent-context library owns child-only projection; and Telegram coordination owns delivery. This crate retains session policy, yield-bound admission lifecycle, launch authorization and intent reconciliation, Kweb effect composition, tool dispatch, context recovery, affinity checkpoint policy, and final commit. The application retains authentication, prompt composition, runtime and root selection, scheduling, retry policy, checkpoint persistence, deployment, and transport delivery.