update: SessionId / SessionStart / SessionOrigin 等を Segment 系名称へ

- Type/Function/Variantを Segment* 系へ統一
  - SessionId/SessionStart/SessionOrigin/SessionStartState/SessionState/SessionLogSink/SessionLockInfo
  - new_session_id / session_id / create_session* / list_sessions / lookup_session / update_session / find_by_session
  - protocol Event::SessionRotated → SegmentRotated、CompactDone.new_session_id → new_segment_id
- Module: session_log → segment_log / session → segment (file mv 含む)
  pod 側の session_log_sink → segment_log_sink も同様
- crate 名 (session-store)、CLI flag (--session)、ResumeWithSession (CLI tied) は据え置き
- session-tests/session_metrics_test 等の Store impl も追従
This commit is contained in:
2026-05-20 05:06:04 +09:00
parent de549812ab
commit 22f5d02385
55 changed files with 611 additions and 610 deletions
+12 -12
View File
@@ -178,7 +178,7 @@ fn drain(rx: &mut broadcast::Receiver<Event>) -> Vec<Event> {
}
/// Collect every system-message text that the post-compaction
/// `SessionStart.history` carries, by reading the sink mirror directly.
/// `SegmentStart.history` carries, by reading the sink mirror directly.
fn system_texts_in_sink_session_start(
pod: &pod::Pod<
impl llm_worker::llm_client::client::LlmClient + Clone + 'static,
@@ -187,7 +187,7 @@ fn system_texts_in_sink_session_start(
) -> Vec<String> {
let (entries, _rx) = pod.sink().subscribe_with_snapshot();
for entry in entries.into_iter().rev() {
if let session_store::LogEntry::SessionStart { history, .. } = entry {
if let session_store::LogEntry::SegmentStart { history, .. } = entry {
return history
.into_iter()
.filter_map(|logged| {
@@ -229,7 +229,7 @@ async fn compact_emits_session_start_carrying_summary_and_task_snapshot() {
pod.compact(10_000).await.unwrap();
let system_texts = system_texts_in_sink_session_start(&pod);
// The post-compaction `SessionStart.history` carries the new system
// The post-compaction `SegmentStart.history` carries the new system
// messages introduced by the compactor. Clients re-seed their view
// from this entry alone, so it is the load-bearing payload.
assert!(
@@ -289,11 +289,11 @@ async fn pre_run_compact_success_broadcasts_start_and_done() {
// CompactDone carries the new session id.
let new_id_in_event = events.iter().find_map(|e| match e {
Event::CompactDone { new_session_id } => Some(*new_session_id),
Event::CompactDone { new_segment_id } => Some(*new_segment_id),
_ => None,
});
assert!(new_id_in_event.is_some(), "CompactDone missing");
assert_eq!(new_id_in_event.unwrap(), pod.session_id());
assert_eq!(new_id_in_event.unwrap(), pod.segment_id());
}
#[tokio::test]
@@ -345,10 +345,10 @@ async fn mid_turn_compact_success_broadcasts_start_and_done() {
);
let new_id_in_event = events.iter().find_map(|e| match e {
Event::CompactDone { new_session_id } => Some(*new_session_id),
Event::CompactDone { new_segment_id } => Some(*new_segment_id),
_ => None,
});
assert_eq!(new_id_in_event, Some(pod.session_id()));
assert_eq!(new_id_in_event, Some(pod.segment_id()));
}
/// Regression: `Pod::compact()` must reset the in-memory
@@ -520,7 +520,7 @@ async fn pre_run_compact_failure_broadcasts_start_and_failed() {
// ---------------------------------------------------------------------------
// Detached post-run memory jobs (`spawn_post_run_memory_jobs` /
// `wait_for_memory_jobs`). Covers the detach round-trip and the structural
// invariant that the cloned memory-task Pod shares `SessionState` with the
// invariant that the cloned memory-task Pod shares `SegmentState` with the
// source Pod, so that `save_extension` from the background extract does not
// leave the next turn's `save_user_input` looking at a stale session pointer.
@@ -570,7 +570,7 @@ async fn spawn_and_wait_drives_extract_to_completion() {
#[tokio::test]
async fn detached_extract_does_not_fork_session_log() {
// Source pod and the cloned memory-task pod share `SessionState` via
// Source pod and the cloned memory-task pod share `SegmentState` via
// `Arc<_>`. The detached extract advances the entry tally through
// `save_extension`; the next `run` must see that same tally so
// `ensure_head_or_fork` does not spawn a new session.
@@ -583,18 +583,18 @@ async fn detached_extract_does_not_fork_session_log() {
let mut pod = make_pod_with_manifest(EXTRACT_NO_COMPACT_MANIFEST, client).await;
pod.run_text("first").await.unwrap();
let session_before = pod.session_id();
let session_before = pod.segment_id();
pod.spawn_post_run_memory_jobs();
pod.wait_for_memory_jobs().await;
pod.run_text("second").await.unwrap();
let session_after = pod.session_id();
let session_after = pod.segment_id();
assert_eq!(
session_before, session_after,
"detached extract's save_extension and the next turn's save_user_input \
must share the entry tally through SessionState — a fork here means the \
must share the entry tally through SegmentState — a fork here means the \
clone carried its own counter"
);
}
+1 -1
View File
@@ -172,7 +172,7 @@ fn write_n_staging(layout: &WorkspaceLayout, n: usize) -> Vec<uuid::Uuid> {
let (id, _) = write_staging(
layout,
SourceRef {
session_id: format!("s-{i}"),
segment_id: format!("s-{i}"),
range: [i as u64, i as u64],
},
ExtractedPayload::default(),
+1 -1
View File
@@ -22,7 +22,7 @@ fn history_from_sink(handle: &PodHandle) -> Vec<Item> {
let mut items = Vec::new();
for entry in entries {
match entry {
LogEntry::SessionStart { history, .. } => {
LogEntry::SegmentStart { history, .. } => {
items.extend(history.into_iter().map(Item::from));
}
LogEntry::UserInput { segments, .. } => {
+1 -1
View File
@@ -349,7 +349,7 @@ async fn stop_pod_sends_shutdown_and_releases_scope() {
permission: Permission::Write,
recursive: true,
}],
session_store::new_session_id(),
session_store::new_segment_id(),
)
.unwrap();
pod_registry::delegate_scope(
+1 -1
View File
@@ -358,7 +358,7 @@ async fn shutdown_releases_scope_allocation_when_present() {
std::process::id(),
"/tmp/kid.sock".into(),
vec![],
session_store::new_session_id(),
session_store::new_segment_id(),
)
.unwrap();
std::mem::forget(guard);
+8 -8
View File
@@ -8,7 +8,7 @@
use std::sync::{LazyLock, Mutex};
use pod::{Pod, PodError};
use session_store::{FsStore, SessionId, StoreError};
use session_store::{FsStore, SegmentId, StoreError};
const MINIMAL_MANIFEST_TOML: &str = r#"
[pod]
@@ -42,7 +42,7 @@ async fn restore_from_manifest_rejects_unknown_session() {
// A freshly-minted id with no jsonl file at all → store returns
// NotFound, which `Pod::restore_from_manifest` surfaces verbatim
// as `PodError::Store`.
let unknown = session_store::new_session_id();
let unknown = session_store::new_segment_id();
let result =
Pod::restore_from_manifest(unknown, manifest, store, pod::PromptLoader::builtins_only())
.await;
@@ -67,7 +67,7 @@ async fn restore_from_manifest_rejects_empty_session_log() {
// `restore_from_manifest` rejects with `SessionEmpty` *before* it
// gets as far as building the LLM client — so the test does not
// need credentials or a runtime sandbox.
let id: SessionId = session_store::new_session_id();
let id: SegmentId = session_store::new_segment_id();
let path = store_tmp.path().join(format!("{id}.jsonl"));
std::fs::write(&path, b"").unwrap();
@@ -75,7 +75,7 @@ async fn restore_from_manifest_rejects_empty_session_log() {
Pod::restore_from_manifest(id, manifest, store, pod::PromptLoader::builtins_only()).await;
match result {
Err(PodError::SessionEmpty { session_id }) => assert_eq!(session_id, id),
Err(PodError::SessionEmpty { segment_id }) => assert_eq!(segment_id, id),
Err(other) => panic!("expected SessionEmpty, got {other:?}"),
Ok(_) => panic!("expected empty session log to fail"),
}
@@ -89,19 +89,19 @@ async fn restore_from_manifest_rejects_session_without_scope_snapshot() {
let store = FsStore::new(store_tmp.path()).unwrap();
let manifest = pod::PodManifest::from_toml(MINIMAL_MANIFEST_TOML).unwrap();
let id = session_store::new_session_id();
let state = session_store::SessionStartState {
let id = session_store::new_segment_id();
let state = session_store::SegmentStartState {
system_prompt: None,
config: &Default::default(),
history: &[],
};
session_store::create_session_with_id(&store, id, state).unwrap();
session_store::create_segment_with_id(&store, id, state).unwrap();
let result =
Pod::restore_from_manifest(id, manifest, store, pod::PromptLoader::builtins_only()).await;
match result {
Err(PodError::SessionScopeMissing { session_id }) => assert_eq!(session_id, id),
Err(PodError::SessionScopeMissing { segment_id }) => assert_eq!(segment_id, id),
Err(other) => panic!("expected SessionScopeMissing, got {other:?}"),
Ok(_) => panic!("expected missing scope snapshot to fail"),
}
+18 -18
View File
@@ -26,7 +26,7 @@ use llm_worker::llm_client::event::{Event as LlmEvent, ResponseStatus, StatusEve
use llm_worker::llm_client::{ClientError, LlmClient, Request};
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use session_metrics::{DOMAIN, Metric, metrics_from_extensions};
use session_store::{FsStore, LogEntry, SessionId, Store, StoreError, TraceEntry};
use session_store::{FsStore, LogEntry, SegmentId, Store, StoreError, TraceEntry};
use pod::{Pod, PodManifest};
@@ -200,7 +200,7 @@ async fn prune_metrics_emit_skip_then_fire_with_post_request_join() {
text_response_with_cache("done", 1234, 50),
]);
let (mut pod, _store_tmp, _pwd_tmp) = make_pod(manifest_toml(1, 1), client, "big_tool").await;
let session_id = pod.session_id();
let segment_id = pod.segment_id();
// Cloning the store handle to read the session log back after the
// runs complete — the Pod retains its own copy.
let store = pod.store().clone();
@@ -208,7 +208,7 @@ async fn prune_metrics_emit_skip_then_fire_with_post_request_join() {
pod.run_text("first").await.unwrap();
pod.run_text("second").await.unwrap();
let state = session_store::restore(&store, session_id).unwrap();
let state = session_store::restore(&store, segment_id).unwrap();
let metrics = metrics_from_extensions(&state.extensions);
// Run 1 has 2 LLM iterations (tool loop), each evaluates prune with
@@ -288,13 +288,13 @@ async fn prune_metrics_record_below_min_savings_skip() {
]);
let (mut pod, _store_tmp, _pwd_tmp) =
make_pod(manifest_toml(1, u64::MAX), client, "big_tool").await;
let session_id = pod.session_id();
let segment_id = pod.segment_id();
let store = pod.store().clone();
pod.run_text("first").await.unwrap();
pod.run_text("second").await.unwrap();
let state = session_store::restore(&store, session_id).unwrap();
let state = session_store::restore(&store, segment_id).unwrap();
let metrics = metrics_from_extensions(&state.extensions);
let below = metrics
.iter()
@@ -327,7 +327,7 @@ struct MetricFailingStore {
}
impl Store for MetricFailingStore {
fn append(&self, id: SessionId, entry: &LogEntry) -> Result<(), StoreError> {
fn append(&self, id: SegmentId, entry: &LogEntry) -> Result<(), StoreError> {
if let LogEntry::Extension { domain, .. } = entry {
if domain == DOMAIN {
return Err(StoreError::Io(std::io::Error::other("synthetic failure")));
@@ -335,22 +335,22 @@ impl Store for MetricFailingStore {
}
self.inner.append(id, entry)
}
fn read_all(&self, id: SessionId) -> Result<Vec<LogEntry>, StoreError> {
fn read_all(&self, id: SegmentId) -> Result<Vec<LogEntry>, StoreError> {
self.inner.read_all(id)
}
fn list_sessions(&self) -> Result<Vec<SessionId>, StoreError> {
self.inner.list_sessions()
fn list_segments(&self) -> Result<Vec<SegmentId>, StoreError> {
self.inner.list_segments()
}
fn create_session(&self, id: SessionId, entries: &[LogEntry]) -> Result<(), StoreError> {
self.inner.create_session(id, entries)
fn create_segment(&self, id: SegmentId, entries: &[LogEntry]) -> Result<(), StoreError> {
self.inner.create_segment(id, entries)
}
fn exists(&self, id: SessionId) -> Result<bool, StoreError> {
fn exists(&self, id: SegmentId) -> Result<bool, StoreError> {
self.inner.exists(id)
}
fn read_entry_count(&self, id: SessionId) -> Result<usize, StoreError> {
fn read_entry_count(&self, id: SegmentId) -> Result<usize, StoreError> {
self.inner.read_entry_count(id)
}
fn append_trace(&self, id: SessionId, entry: &TraceEntry) -> Result<(), StoreError> {
fn append_trace(&self, id: SegmentId, entry: &TraceEntry) -> Result<(), StoreError> {
self.inner.append_trace(id, entry)
}
}
@@ -386,12 +386,12 @@ async fn metric_write_failure_emits_warn_alert_and_does_not_abort_run() {
let alerter = pod::Alerter::new(tx);
pod.attach_alerter(alerter);
let session_id = pod.session_id();
let segment_id = pod.segment_id();
// Run completes successfully despite metric failure.
pod.run_text("hello").await.unwrap();
// No metrics ended up in the log (writes were rejected).
let state = session_store::restore(&store, session_id).unwrap();
let state = session_store::restore(&store, segment_id).unwrap();
let metrics = metrics_from_extensions(&state.extensions);
assert!(metrics.is_empty(), "metrics must drop on write failure");
@@ -446,10 +446,10 @@ permission = "write"
let mut pod = Pod::new(manifest, worker, store.clone(), pwd, scope)
.await
.unwrap();
let session_id = pod.session_id();
let segment_id = pod.segment_id();
pod.run_text("hello").await.unwrap();
let state = session_store::restore(&store, session_id).unwrap();
let state = session_store::restore(&store, segment_id).unwrap();
let metrics = metrics_from_extensions(&state.extensions);
assert!(
metrics.is_empty(),
+1 -1
View File
@@ -73,7 +73,7 @@ async fn setup_spawner(
permission: Permission::Write,
recursive: true,
}],
session_store::new_session_id(),
session_store::new_segment_id(),
)
.unwrap();
// Leak the guard — the spawner allocation needs to outlive the
@@ -182,16 +182,16 @@ async fn session_start_state_captures_rendered_prompt() {
.unwrap();
pod.run_text("hi").await.unwrap();
let entries = pod.store().read_all(pod.session_id()).unwrap();
let entries = pod.store().read_all(pod.segment_id()).unwrap();
let first = entries.first().expect("at least one entry");
match first {
LogEntry::SessionStart { system_prompt, .. } => {
LogEntry::SegmentStart { system_prompt, .. } => {
let sp = system_prompt.as_deref().expect("system prompt set");
assert!(sp.starts_with("hello cwd="));
assert!(sp.contains(&pwd.display().to_string()));
assert!(sp.contains("## Working boundaries"));
}
other => panic!("expected SessionStart as first entry, got {other:?}"),
other => panic!("expected SegmentStart as first entry, got {other:?}"),
}
}