kcode-kennedy-sessions 0.2.4

Kennedy logical session lifecycle and agent orchestration
Documentation
# Kennedy logical sessions 0.2.4

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`; and durable input staging and transcript reconstruction to `kcode-kennedy-session-ingress`.

## Public API

```rust
pub use kcode_kennedy_session_objects::ResolvedObject;
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 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

Version 0.2.4 adds an owned stepped primary-turn interface built around 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. This is the insertion point for a future mailbox driver. `Complete` returns the same terminal `Option<String>` outcome used by `run_pending_turn`.

Any accepted external or controller mutation through Session's public lifecycle or input methods invalidates outstanding stepped turn and wake values and clears transient turn and provider deadlines. Validation of stale or foreign values occurs before cleanup, so rejecting an old value cannot clear deadlines for a newer turn. Callers using the stepped API must route intervening mutations through those 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 real `begin_user_turn` call 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 user authority only from typed `BoxCreated` events whose owner is `User`. It never infers a turn from the journal tail. When a pending checkpoint lags, exactly one authoritative user box after the cursor may restore the turn identity. Ambiguity fails construction. A pending synthetic-bootstrap marker is consumed in memory and denies that turn, including a synthetic input that became durable ahead of its checkpoint.

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.

`begin_user_turn` rejects empty input or an already-pending turn, durably stages text and attachments, resets round accounting, and opens a turn. Attachment persistence can precede later failure; such failure becomes fatal to the next run. `append_final_user_message` and `stage_source_message` stage source messages without granting launch authority. Free-time and wakeup openings are synthetic and likewise grant none. `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, and provider-resume reconciliation. 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, 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.