docs: consolidate design documentation and standardize crate English
This commit is contained in:
@@ -1,32 +1,15 @@
|
||||
# session-store
|
||||
|
||||
## Role
|
||||
`session-store` stores replayable Session history and current Worker restoration data.
|
||||
Session history uses append-only JSONL; current Segment references and child/peer
|
||||
registrations live separately in Worker metadata. It does not own process liveness
|
||||
checks or Runtime catalog lifecycle management.
|
||||
|
||||
`session-store` owns replayable append-only session logs.
|
||||
## Design entry points
|
||||
|
||||
## Boundaries
|
||||
|
||||
Owns:
|
||||
|
||||
- session identifiers and segment lineage
|
||||
- JSONL log entries for replayable conversation/runtime history
|
||||
- restoring Engine/session state from committed records
|
||||
- schema surfaces that should make drift compile-visible
|
||||
|
||||
Does not own:
|
||||
|
||||
- current Worker-name metadata (`session-store` worker metadata)
|
||||
- live process/socket discovery (`worker-allocation`, `client`)
|
||||
- UI state (`tui`)
|
||||
- generated memory summaries (`memory`)
|
||||
|
||||
## Design notes
|
||||
|
||||
A session log records what happened. It is not the current Worker registry and should not be queried as the only source of "what does Worker X mean now?"
|
||||
|
||||
Prefer explicit current log variants over broad legacy compatibility when schema changes; hidden compatibility can make future replay bugs silent.
|
||||
|
||||
## See also
|
||||
|
||||
- [`../../docs/design/worker-session-state.md`](../../docs/design/worker-session-state.md)
|
||||
- [`../../docs/design/context-history.md`](../../docs/design/context-history.md)
|
||||
- [`segment_log`] / [`segment`]: history replay and fork/compaction lineage
|
||||
- [`worker_session_store`]: storage boundary for a Session bound to a Worker
|
||||
- [`worker_metadata`]: current Worker state and active Segment switching
|
||||
- [`system_item`]: records of system input presented to the model
|
||||
- [`public_snapshot`] / [`public_index`]: public projections of stored history
|
||||
- [`event_trace`]: diagnostic records independent of history replay
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
//! Debug-only LLM request/stream trace recording.
|
||||
//! Diagnostic records of LLM transport, independent of history replay.
|
||||
//!
|
||||
//! [`TraceEntry`] captures stream lifecycle markers and normalized provider
|
||||
//! stream events for debugging stalls. It is not a byte-for-byte raw SSE
|
||||
//! capture. Written to a separate `.trace.jsonl` file,
|
||||
//! completely independent of the segment log used for state restoration.
|
||||
//!
|
||||
//! Disabled by default. Enable via `SessionConfig::record_event_trace`.
|
||||
//! [`TraceEntry`] records normalized events and lifecycle boundaries, not a complete
|
||||
//! copy of raw SSE. Stored separately from Segment logs, it is not a source for
|
||||
//! restored model input. This separates observed transport state from input/output
|
||||
//! committed to history.
|
||||
|
||||
use agen::llm_client::event::Event;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -1,35 +1,4 @@
|
||||
//! Session persistence via append-only JSONL logs.
|
||||
//!
|
||||
//! # Architecture
|
||||
//!
|
||||
//! A [`Session`](SessionId) is a fork-tree of [`Segment`](SegmentId)s
|
||||
//! belonging to the same logical conversation. Each Segment is recorded
|
||||
//! as a sequence of [`LogEntry`] values, one per line in a `.jsonl`
|
||||
//! file. Reading a segment log and collecting entries reconstructs the
|
||||
//! Engine state at that segment — no separate snapshots or checkpoints
|
||||
//! needed. Compaction and fork operations mint a fresh Segment within
|
||||
//! the same Session.
|
||||
//!
|
||||
//! This crate provides free functions for persistence operations.
|
||||
//! The caller (typically Worker) holds the Engine directly and calls these
|
||||
//! functions after state-mutating operations.
|
||||
//!
|
||||
//! Debug-mode [`TraceEntry`] records capture raw stream events in a separate
|
||||
//! `.trace.jsonl` file, independent of the segment log.
|
||||
//!
|
||||
//! # Quick start
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use session_store::{create_segment, restore, save_delta, FsStore, SegmentStartState};
|
||||
//!
|
||||
//! let store = FsStore::new("./sessions")?;
|
||||
//! let (session_id, segment_id) = create_segment(&store, SegmentStartState {
|
||||
//! system_prompt: None,
|
||||
//! config: &config,
|
||||
//! history: Vec::new(),
|
||||
//! user_segments: Vec::new(),
|
||||
//! })?;
|
||||
//! ```
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
@@ -141,11 +110,10 @@ pub use worker_metadata::{
|
||||
};
|
||||
pub use worker_session_store::WorkerSessionStore;
|
||||
|
||||
/// Session identifier — the fork-tree root. UUID v7 (time-ordered).
|
||||
/// UUID v7 identifying Segments belonging to the same conversation.
|
||||
///
|
||||
/// All Segments belonging to the same Session share this ID. Compaction
|
||||
/// and fork operations create a new Segment within the same Session, so
|
||||
/// `WHERE session_id = ?` retrieves the full lineage.
|
||||
/// Fork/compaction preserves this ID and only adds Segments. Switching the current
|
||||
/// history projection therefore does not change the conversation-wide reference.
|
||||
pub type SessionId = uuid::Uuid;
|
||||
|
||||
/// Segment identifier. UUID v7 (time-ordered, lexicographically sortable).
|
||||
|
||||
@@ -82,8 +82,8 @@ pub enum LoggedItem {
|
||||
summary: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
encrypted_content: Option<String>,
|
||||
/// Anthropic extended thinking signature。新世代 Claude で round-trip
|
||||
/// 必須。OpenAI Responses など他 scheme では `None`。
|
||||
/// Anthropic extended thinking signature, required for round-trips with
|
||||
/// newer Claude generations. `None` for other schemes such as OpenAI Responses.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
signature: Option<String>,
|
||||
},
|
||||
@@ -418,13 +418,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn round_trip_reasoning_preserves_signature() {
|
||||
// 新世代 Claude の thinking signature が history.json に永続化され、
|
||||
// resume 後の Item::Reasoning に復元されること。
|
||||
// A newer Claude thinking signature persists in history.json and is
|
||||
// restored to Item::Reasoning after resume.
|
||||
let original = Item::reasoning("inner thought").with_signature("SIG-OPUS-XYZ");
|
||||
let logged: LoggedItem = (&original).into();
|
||||
let json = serde_json::to_string(&logged).unwrap();
|
||||
// wire 形式に signature キーが乗ること(古い形式との互換のため
|
||||
// 値が None のときは省略される。Some の値は載る)
|
||||
// The wire format includes Some signatures; None is omitted for
|
||||
// compatibility with older formats.
|
||||
assert!(
|
||||
json.contains("SIG-OPUS-XYZ"),
|
||||
"serialised JSON must carry signature: {json}",
|
||||
@@ -443,8 +443,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn legacy_reasoning_without_signature_field_deserializes() {
|
||||
// signature フィールドが無い旧形式の history.json を読み込んでも
|
||||
// None としてロードできる(後方互換性)。
|
||||
// Legacy history.json without a signature field loads it as None
|
||||
// for backward compatibility.
|
||||
let legacy_json =
|
||||
r#"{"kind":"reasoning","text":"old","summary":[],"encrypted_content":null}"#;
|
||||
let parsed: LoggedItem = serde_json::from_str(legacy_json).unwrap();
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
//! Projects stored history for clients.
|
||||
//!
|
||||
//! Converts committed entry IDs and provenance to public DTOs, keeping JSONL replay
|
||||
//! rules out of clients. Model reasoning, system prompts, internal response boundaries,
|
||||
//! and extension payloads are not exposed in conversation views. Measurements use
|
||||
//! dedicated readers and do not enter ordinary snapshots.
|
||||
//!
|
||||
//! Retained-history reads validate active references and Session IDs and cap read volume.
|
||||
//! Observation neither migrates old formats nor repairs corruption; required migration
|
||||
//! or repair returns an error. Matches live output by entry ID, not timestamps alone.
|
||||
|
||||
mod run_stats;
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
//! Free functions for segment persistence operations.
|
||||
//! Segment creation, append, and restoration.
|
||||
//!
|
||||
//! These functions record and restore segment state without owning a Engine.
|
||||
//! The caller (typically Worker) holds the Engine directly and calls these
|
||||
//! functions after state-mutating operations.
|
||||
//! Records host-committed history and execution boundaries in Store without owning
|
||||
//! Engine. Fork and compaction create a Segment with initial history in the same
|
||||
//! Session without rewriting the source. Initial records retain the source Segment
|
||||
//! and adopted boundary so replacement and branching decisions remain traceable.
|
||||
//!
|
||||
//! Saving a new Segment does not change the active reference. The host coordinates
|
||||
//! compaction activation with Worker metadata and the live append target. On failure,
|
||||
//! a saved Segment alone does not establish that current history has switched.
|
||||
|
||||
use crate::segment_log::{self, LogEntry, SegmentOrigin};
|
||||
use crate::store::{Store, StoreError};
|
||||
@@ -400,12 +405,11 @@ pub fn save_run_errored(
|
||||
)
|
||||
}
|
||||
|
||||
/// Log an `LlmUsage` entry — 1 LLM リクエスト分の Usage スナップショット。
|
||||
/// Logs an `LlmUsage` entry: a usage snapshot for one LLM request.
|
||||
///
|
||||
/// `history_len` は送信時の `history.len()`。`input_total_tokens` は
|
||||
/// その prefix をプロバイダが実測した占有量(プロンプト全長)で、
|
||||
/// プロバイダ別の正規化(Anthropic では `input + cache_read + cache_creation`)を
|
||||
/// 済ませた値を渡す。
|
||||
/// `history_len` is `history.len()` at send time. `input_total_tokens` is the
|
||||
/// provider-measured size of that prefix (the full prompt). Pass a value normalized
|
||||
/// for the provider: `input + cache_read + cache_creation` for Anthropic.
|
||||
pub fn save_usage(
|
||||
store: &impl Store,
|
||||
session_id: SessionId,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
//! Segment log types for append-only JSONL persistence.
|
||||
//! Replays Segment state from append-only JSONL.
|
||||
//!
|
||||
//! Each [`LogEntry`] represents a single state transition within one
|
||||
//! segment, serialized as one line in a `.jsonl` file. Reading all
|
||||
//! entries and collecting them via [`collect_state`] reconstructs the
|
||||
//! full [`Engine`] state at that segment.
|
||||
//! [`collect_state`] restores model input, configuration, and execution interruption
|
||||
//! state from [`LogEntry`] position order and initial history. Fork/compaction seeds
|
||||
//! retain lineage to the old Segment and history item IDs and provenance.
|
||||
//!
|
||||
//! The on-disk format is one `LogEntry` per line — entries are positionally
|
||||
//! ordered. Fork lineage references between segments use turn-number indices
|
||||
//! (`SegmentOrigin.at_turn_index`) rather than per-entry hashes.
|
||||
//! An execution-start record alone does not imply success. Runs without terminal
|
||||
//! records restore as interrupted; uncommitted tool execution is not treated as
|
||||
//! successful. Yield/resume records mark intermediate logical-Run boundaries,
|
||||
//! preserving the turn budget even when resuming after compaction.
|
||||
|
||||
use agen::llm_client::types::{Item, RequestConfig};
|
||||
use agen::{EngineResult, UsageRecord};
|
||||
@@ -17,20 +17,10 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::history::{LoggedHistoryEntry, LoggedSystemHistoryEntry};
|
||||
use crate::logged_item::LoggedItem;
|
||||
|
||||
/// A single segment log entry, serialized as one JSONL line.
|
||||
/// Host-owned extension data.
|
||||
///
|
||||
/// Variants correspond to specific mutation points in `Engine`:
|
||||
/// - `SegmentStart` — always the first entry; captures initial state
|
||||
/// - `Invoke` — IDLE → active marker (start of a new self-driving cycle)
|
||||
/// - `UserInput` / `AssistantItem` / `ToolResult` / `SystemItem` — history appends
|
||||
/// - `TurnEnd` — AgentTurn boundary marker; carries the post-increment
|
||||
/// `turn_count`. With retry unimplemented today this fires once per
|
||||
/// `run()`/`resume()` (current callers persist a single TurnEnd at
|
||||
/// run completion); the fork-point seq for `at_turn_index` is the
|
||||
/// preceding `Invoke` entry, not the TurnEnd.
|
||||
/// - `RunCompleted` / `RunCancelled` / `RunErrored` — terminal run outcomes
|
||||
/// - `PausedTurnAbandoned` — explicit abandon/cancel of a paused interrupted turn
|
||||
/// - `ConfigChanged` — `RequestConfig` mutation
|
||||
/// Excluded from public conversation-history projections; interpreted by
|
||||
/// dedicated domain readers, such as metrics readers.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SessionExtension {
|
||||
pub domain: String,
|
||||
@@ -46,6 +36,11 @@ impl SessionExtension {
|
||||
}
|
||||
}
|
||||
|
||||
/// JSONL record of history additions and state transitions within a Segment.
|
||||
///
|
||||
/// History items and provenance commit in the same record, separately from execution
|
||||
/// boundaries and extension data. These records do not establish current Worker
|
||||
/// catalog state or process liveness.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum LogEntry {
|
||||
@@ -123,7 +118,7 @@ pub enum LogEntry {
|
||||
/// Turn boundary. Records the turn count after increment.
|
||||
TurnEnd { ts: u64, turn_count: usize },
|
||||
|
||||
/// `run()` / `resume()` が `EngineResult` で正常終了した。
|
||||
/// `run()` / `resume()` returned normally with an `EngineResult`.
|
||||
/// Replay restores both interruption state and any resumable logical-run
|
||||
/// turn budget.
|
||||
RunCompleted {
|
||||
@@ -172,9 +167,9 @@ pub enum LogEntry {
|
||||
entry_id: Option<crate::LoggedSessionHistoryEntryId>,
|
||||
},
|
||||
|
||||
/// `run()` / `resume()` が `EngineError` で終了した。
|
||||
/// `EngineError` は `Serialize` 不可なので `message` のみ lossy 保持する。
|
||||
/// Audit-only metadata: replay は `interrupted` のみ反映する。
|
||||
/// `run()` / `resume()` ended with an `EngineError`.
|
||||
/// `EngineError` is not `Serialize`, so only `message` is retained lossily.
|
||||
/// Audit-only metadata: replay applies only `interrupted`.
|
||||
RunErrored {
|
||||
ts: u64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -204,19 +199,19 @@ pub enum LogEntry {
|
||||
/// `RequestConfig` changed.
|
||||
ConfigChanged { ts: u64, config: RequestConfig },
|
||||
|
||||
/// LLM リクエスト 1 件分の Usage スナップショット。
|
||||
/// Usage snapshot for one LLM request.
|
||||
///
|
||||
/// `history_len` は送信時の `history.len()`。`input_total_tokens` は
|
||||
/// その prefix をプロバイダが実測した占有量(プロンプト全長)。
|
||||
/// このリクエスト 1 件で新しく追加された分ではない。
|
||||
/// `history_len` is `history.len()` at send time. `input_total_tokens` is the
|
||||
/// provider-measured size of that prefix (the full prompt), not the amount
|
||||
/// newly added by this request.
|
||||
///
|
||||
/// プロバイダ別の正規化(呼び出し側で行う想定):
|
||||
/// Provider-specific normalization (expected from the caller):
|
||||
/// - Anthropic: `input_tokens + cache_read + cache_creation`
|
||||
/// - OpenAI: `prompt_tokens`
|
||||
/// - Gemini: `promptTokenCount`
|
||||
/// - Ollama: `prompt_eval_count`
|
||||
///
|
||||
/// `cache_read_tokens` / `cache_write_tokens` は上記の内訳で、料金会計用。
|
||||
/// `cache_read_tokens` / `cache_write_tokens` break down the above for billing.
|
||||
LlmUsage {
|
||||
ts: u64,
|
||||
history_len: usize,
|
||||
@@ -226,14 +221,13 @@ pub enum LogEntry {
|
||||
output_tokens: u64,
|
||||
},
|
||||
|
||||
/// 汎用拡張点。ドメイン名で名前空間を切って任意 JSON を載せる。
|
||||
/// session-store は payload を不透明扱いし、replay 時は
|
||||
/// `RestoredState.extensions` に `(domain, payload)` を順に積むだけ。
|
||||
/// 各ドメイン側が自前で fold して最新値を取り出す前提。
|
||||
/// Generic extension point for arbitrary JSON namespaced by domain.
|
||||
/// session-store treats payloads as opaque; replay only appends
|
||||
/// `(domain, payload)` to `RestoredState.extensions` in order.
|
||||
/// Each domain folds its own records to obtain the latest value.
|
||||
///
|
||||
/// 想定用途: memory subsystem の extract 処理境界 pointer 等、
|
||||
/// 「session 寿命に縛りたいが session-store の型を汚したくない」
|
||||
/// メタデータ。
|
||||
/// Intended for metadata tied to the Session lifetime without adding
|
||||
/// session-store types, such as memory extraction boundary pointers.
|
||||
Extension {
|
||||
ts: u64,
|
||||
domain: String,
|
||||
@@ -317,12 +311,12 @@ pub struct RestoredState {
|
||||
/// Writers track their own append count via the same counter so
|
||||
/// `ensure_head_or_fork` can compare it with the on-disk count.
|
||||
pub entries_count: usize,
|
||||
/// LLM リクエストごとの Usage スナップショット時系列。
|
||||
/// `LogEntry::LlmUsage` を replay して時系列順に積まれる。
|
||||
/// 任意位置のトークン数推定に使う。
|
||||
/// Per-request usage snapshots in chronological order.
|
||||
/// Populated by replaying `LogEntry::LlmUsage`.
|
||||
/// Used to estimate token counts at arbitrary positions.
|
||||
pub usage_history: Vec<UsageRecord>,
|
||||
/// `LogEntry::Extension` を replay 順に積んだもの。`(domain, payload)`。
|
||||
/// session-store は domain を不透明扱いし、各ドメインが自前で fold する。
|
||||
/// `(domain, payload)` values from `LogEntry::Extension` in replay order.
|
||||
/// session-store treats domains as opaque; each domain folds its own records.
|
||||
pub extensions: Vec<(String, serde_json::Value)>,
|
||||
/// User submissions in original typed form, in submit order.
|
||||
/// One entry per `LogEntry::AnnotatedUserInput`; the K-th entry corresponds to
|
||||
@@ -766,9 +760,9 @@ mod tests {
|
||||
output_tokens: 5,
|
||||
},
|
||||
]);
|
||||
// history は LlmUsage で変化しない
|
||||
// LlmUsage does not change history.
|
||||
assert_eq!(state.history.len(), 2);
|
||||
// usage_history は時系列順
|
||||
// usage_history is in chronological order.
|
||||
assert_eq!(state.usage_history.len(), 2);
|
||||
assert_eq!(state.usage_history[0].history_len, 1);
|
||||
assert_eq!(state.usage_history[0].input_total_tokens, 50);
|
||||
@@ -1114,7 +1108,7 @@ mod tests {
|
||||
payload: serde_json::json!({ "x": 1 }),
|
||||
},
|
||||
]);
|
||||
// 順序保持で全件積まれる。fold は呼び出し側の責務。
|
||||
// All records retain their order. Folding is the caller's responsibility.
|
||||
assert_eq!(state.extensions.len(), 3);
|
||||
assert_eq!(state.extensions[0].0, "memory.extract");
|
||||
assert_eq!(state.extensions[1].1["processed_through_entry"], 12);
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
//! Typed system-message items injected by the agent system.
|
||||
//! Typed records of system input a Worker presents to the model.
|
||||
//!
|
||||
//! Items in worker history with `role:system` are never produced by the
|
||||
//! LLM — they are always inserted by the Worker itself (notifications,
|
||||
//! file ref resolutions, child-worker lifecycle events, reminders, …).
|
||||
//! [`SystemItem`] carries the
|
||||
//! typed shape of each such injection so clients can dispatch on
|
||||
//! `kind` instead of parsing text prefixes like `[Notification] …` or
|
||||
//! `[File: …]`.
|
||||
//! Notifications, child events, reference resolution, and reminders are Worker-added
|
||||
//! input, not model output. [`SystemItem`] retains the display kind and model-visible
|
||||
//! body, representing the same input in history and live events. Stored bodies allow
|
||||
//! old input to be reproduced even if prompt rendering rules later change.
|
||||
//!
|
||||
//! Persisted as the payload of [`crate::LogEntry::AnnotatedSystemItem`] (one
|
||||
//! entry per item), and broadcast live as the payload of
|
||||
//! `Event::SystemItem` on the wire.
|
||||
//!
|
||||
//! For LLM context replay, each `SystemItem` reduces to an
|
||||
//! `Item::system_message(...)` whose body matches the legacy free-text
|
||||
//! shape (see [`SystemItem::history_text`]). The kind metadata is
|
||||
//! preserved only on the log/wire side; the LLM still sees plain
|
||||
//! system-message text.
|
||||
//! Input affecting the model is committed via [`crate::LogEntry::AnnotatedSystemItem`]
|
||||
//! before use. Request-only transient insertion would leave later turns unable to
|
||||
//! explain decisions from stored history. The model projection uses only the body;
|
||||
//! clients can render using type information without parsing body prefixes.
|
||||
|
||||
use agen::llm_client::types::Item;
|
||||
use protocol::WorkerEvent;
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
//! Durable Worker-name metadata/state persistence.
|
||||
//! Stores current Worker restoration data separately from Session history.
|
||||
//!
|
||||
//! This crate owns the name-keyed Worker state surface under a Worker-state root,
|
||||
//! e.g. `{data_dir}/workers/{worker_name}/metadata.json`. Session JSONL replay stays
|
||||
//! in `session-store`; Worker metadata may point at a `(SessionId, SegmentId)` but
|
||||
//! does not own or replay session logs.
|
||||
//! [`WorkerAggregateStore`] handles metadata for one Runtime-selected Worker.
|
||||
//! The active Segment, resolved manifest, and child/peer visibility are current
|
||||
//! state, separate from state replayed from past conversation. Session model input
|
||||
//! is restored from Segment-log initial history and appended records.
|
||||
//!
|
||||
//! `resolved_manifest_snapshot` is authority only for Worker-name restore before
|
||||
//! loading the session log. Existing segment replay still uses `SegmentStart`
|
||||
//! entries from `session-store`. `spawned_children` is durable current parent
|
||||
//! Worker state for child registry/reclaim; child lifecycle messages shown to the
|
||||
//! model remain session JSONL history. Socket and callback paths are last-known
|
||||
//! runtime hints, not proof of liveness.
|
||||
//! Active-reference compare-and-swap replaces only a matching source Segment.
|
||||
//! Updates preserve unrelated metadata and prevent stale compaction results from
|
||||
//! overwriting newer references. Callers coordinate switching with append targets
|
||||
//! and allocation.
|
||||
//!
|
||||
//! Child delegation/reclamation state is current restoration data; model-facing
|
||||
//! child notifications are recorded separately in Session history. Peer registration
|
||||
//! includes no delegated scope. Stored socket/callback locations are last-known
|
||||
//! execution data, not proof of liveness.
|
||||
|
||||
use crate::{SegmentId, SessionId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
//! Filesystem store for the canonical `1 Worker = 1 Session` aggregate.
|
||||
//! File storage for a Session bound to one Worker.
|
||||
//!
|
||||
//! Layout under one Worker aggregate:
|
||||
//! - `session/session.json` — immutable Session identity
|
||||
//! - `session/segments/<segment_id>.jsonl`
|
||||
//! - `session/segments/<segment_id>.trace.jsonl`
|
||||
//! The manifest fixes the Session ID; requests to switch Sessions are rejected.
|
||||
//! Fork/compaction adds Segments within the same Session, so Worker restoration and
|
||||
//! history observation need not change the conversation ID. Runtime execution attempts
|
||||
//! and sockets are not part of this ID.
|
||||
//!
|
||||
//! Unlike [`crate::FsStore`], this store cannot enumerate or switch between
|
||||
//! arbitrary Sessions. The first segment materializes the sole Session identity;
|
||||
//! every later operation must use that same ID.
|
||||
//! Validates complete JSONL records before append; only a trailing fragment without
|
||||
//! a closing newline is discarded as uncommitted. Corrupt complete records are rejected.
|
||||
//! Failed appends attempt rollback to the previous boundary; if rollback also fails,
|
||||
//! both failures are returned. Whole-Segment and manifest saves sync temporary files
|
||||
//! and rename them.
|
||||
//!
|
||||
//! Retained-history observation uses a read path without creation or migration.
|
||||
//! Reads are rejected where access time cannot be preserved, avoiding observation-induced
|
||||
//! changes to stored state.
|
||||
|
||||
use crate::event_trace::TraceEntry;
|
||||
use crate::paste_artifact::{read_from_dir, write_to_dir};
|
||||
|
||||
Reference in New Issue
Block a user