use crate::core::provider::{AgentMessage, ContentBlock, Usage};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
#[derive(Debug, thiserror::Error)]
pub enum RecordLogError {
#[error("corruption<non_consecutive_seq>: {message}")]
NonConsecutiveSeq { message: String },
#[error("corruption<references_missing_lane>: {message}")]
MissingLane { message: String },
#[error("corruption<references_missing_lane_target>: {message}")]
MissingLaneTarget { message: String },
#[error("corruption<references_missing_parent>: {message}")]
MissingParent { message: String },
#[error("corruption<does_not_chain_to_lane_leaf>: {message}")]
DoesNotChainToLaneLeaf { message: String },
#[error("corruption<references_missing_label_target>: {message}")]
MissingLabelTarget { message: String },
#[error("corruption<unknown_operation>: {message}")]
UnknownOperation { message: String },
#[error("corruption<record_after_finish>: {message}")]
RecordAfterFinish { message: String },
#[error("corruption<non_consecutive_attempt>: {message}")]
NonConsecutiveAttempt { message: String },
#[error("corruption<invalid_compaction_reason>: {message}")]
InvalidCompactionReason { message: String },
#[error("corruption<queue_after_abort>: {message}")]
QueueAfterAbort { message: String },
#[error("corruption<invalid_queue_cancellation>: {message}")]
InvalidQueueCancellation { message: String },
#[error("corruption<inconsistent_step>: {message}")]
InconsistentStep { message: String },
#[error("corruption<tool_call_mismatch>: {message}")]
ToolCallMismatch { message: String },
#[error("corruption<duplicate_tool_invocation>: {message}")]
DuplicateToolInvocation { message: String },
#[error("corruption<provisioned_entry_mismatch>: {message}")]
ProvisionedEntryMismatch { message: String },
#[error("corruption<invalid_deferred_handle>: {message}")]
InvalidDeferredHandle { message: String },
#[error("corruption<duplicate_id>: {message}")]
DuplicateId { message: String },
#[error("corruption<lane_not_found>: {message}")]
LaneNotFound { message: String },
#[error("corruption<entry_not_found>: {message}")]
EntryNotFound { message: String },
#[error("corruption<cycle>: {message}")]
Cycle { message: String },
#[error("corruption<fork_target_not_message>: {message}")]
ForkTargetNotMessage { message: String },
#[error("{message}")]
InvalidJson { message: String },
#[error("{message}")]
NotJsonObject { message: String },
#[error("{message}")]
InvalidKind { message: String },
#[error("{message}")]
InvalidEntry { message: String },
#[error("{message}")]
InvalidRecord { message: String },
#[error("{message}")]
InvalidLane { message: String },
#[error("{message}")]
InvalidFact { message: String },
#[error("{message}")]
UnknownFactType { message: String },
#[error("{message}")]
UnknownMutationKind { message: String },
#[error("{message}")]
InvalidSeq { message: String },
#[error("{message}")]
InvalidTimestamp { message: String },
#[error("{message}")]
InvalidCustomType { message: String },
}
fn corrupt<T>(e: RecordLogError) -> Result<T, RecordLogError> {
Err(e)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntryBase {
pub id: String,
pub seq: u64,
pub timestamp: i64,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "parentId")]
pub parent_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
pub enum Entry {
Message {
#[serde(flatten)]
base: EntryBase,
message: AgentMessage,
#[serde(default, skip_serializing_if = "Option::is_none")]
terminate: Option<bool>,
},
ModelChange {
#[serde(flatten)]
base: EntryBase,
provider: String,
#[serde(rename = "modelId")]
model_id: String,
},
ThinkingLevelChange {
#[serde(flatten)]
base: EntryBase,
#[serde(rename = "thinkingLevel")]
thinking_level: String,
},
ActiveToolsChange {
#[serde(flatten)]
base: EntryBase,
#[serde(rename = "activeToolNames")]
active_tool_names: Vec<String>,
},
Compaction {
#[serde(flatten)]
base: EntryBase,
summary: String,
#[serde(
default,
rename = "firstKeptEntryId",
skip_serializing_if = "Option::is_none"
)]
first_kept_entry_id: Option<String>,
#[serde(rename = "tokensBefore")]
tokens_before: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
details: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
usage: Option<Usage>,
},
BranchSummary {
#[serde(flatten)]
base: EntryBase,
#[serde(rename = "fromId")]
from_id: String,
summary: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
details: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
usage: Option<Usage>,
},
ContextEdit {
#[serde(flatten)]
base: EntryBase,
#[serde(rename = "targetId")]
target_id: String,
replacement: Option<ContextEditReplacement>,
},
Custom {
#[serde(flatten)]
base: EntryBase,
#[serde(rename = "customType")]
custom_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
data: Option<Value>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextEditReplacement {
pub content: Vec<ContentBlock>,
}
impl Entry {
pub fn id(&self) -> &str {
match self {
Entry::Message { base, .. }
| Entry::ModelChange { base, .. }
| Entry::ThinkingLevelChange { base, .. }
| Entry::ActiveToolsChange { base, .. }
| Entry::Compaction { base, .. }
| Entry::BranchSummary { base, .. }
| Entry::ContextEdit { base, .. }
| Entry::Custom { base, .. } => &base.id,
}
}
pub fn seq(&self) -> u64 {
match self {
Entry::Message { base, .. }
| Entry::ModelChange { base, .. }
| Entry::ThinkingLevelChange { base, .. }
| Entry::ActiveToolsChange { base, .. }
| Entry::Compaction { base, .. }
| Entry::BranchSummary { base, .. }
| Entry::ContextEdit { base, .. }
| Entry::Custom { base, .. } => base.seq,
}
}
pub fn is_context_edit(&self) -> bool {
matches!(self, Entry::ContextEdit { .. })
}
pub fn context_edit_target(&self) -> Option<&str> {
match self {
Entry::ContextEdit { target_id, .. } => Some(target_id),
_ => None,
}
}
pub fn payload_json(&self) -> Value {
let v = serde_json::to_value(self).unwrap_or(Value::Null);
let mut m = match v {
Value::Object(m) => m,
_ => serde_json::Map::new(),
};
m.remove("parentId");
m.remove("seq");
m.remove("timestamp");
Value::Object(m)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvisionedEntry {
pub id: String,
#[serde(flatten)]
pub payload: Value,
}
impl ProvisionedEntry {
pub fn payload_json(&self) -> Value {
self.payload.clone()
}
pub fn matches_entry(&self, entry: &Entry) -> bool {
let mut ours = self.payload.clone();
let mut theirs = entry.payload_json();
if let (Value::Object(a), Value::Object(b)) = (&mut ours, &mut theirs) {
a.remove("id");
b.remove("type");
b.remove("id");
}
ours == theirs
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonlV4Header {
pub kind: String,
pub version: u32,
pub id: String,
pub created_at: i64,
pub cwd: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_session_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
impl JsonlV4Header {
pub fn new(
id: String,
cwd: String,
created_at: i64,
parent_session_id: Option<String>,
) -> Self {
Self {
kind: "header".to_string(),
version: 4,
id,
created_at,
cwd,
parent_session_id,
metadata: None,
}
}
pub fn encode(&self) -> String {
format!(
"{}\n",
serde_json::to_string(self).unwrap_or_else(|_| "{}".to_string())
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
pub enum SessionMutation {
Entry {
#[serde(default, skip_serializing_if = "Option::is_none")]
lane: Option<String>,
#[serde(flatten)]
entry: Entry,
},
Record {
#[serde(flatten)]
record: LaneRecord,
},
Lane {
seq: u64,
lane: String,
#[serde(rename = "leafId")]
leaf_id: Option<String>,
},
Fact {
seq: u64,
fact: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "targetId")]
target_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
}
pub fn encode_mutation(m: &SessionMutation) -> String {
let v = serde_json::to_value(m).unwrap_or(Value::Null);
format!("{}\n", v)
}
pub fn parse_mutation(line: &str) -> Result<SessionMutation, RecordLogError> {
let value: Value = serde_json::from_str(line).map_err(|e| RecordLogError::InvalidJson {
message: format!("is not valid JSON: {e}"),
})?;
let obj = match value {
Value::Object(m) => m,
_ => {
return Err(RecordLogError::NotJsonObject {
message: "is not a JSON object".to_string(),
});
}
};
let kind =
obj.get("kind")
.and_then(|v| v.as_str())
.ok_or_else(|| RecordLogError::InvalidKind {
message: "has invalid kind".to_string(),
})?;
match kind {
"entry" => {
let m: SessionMutation = serde_json::from_value(Value::Object(obj)).map_err(|e| {
RecordLogError::InvalidEntry {
message: format!("has invalid entry: {e}"),
}
})?;
let SessionMutation::Entry { entry, .. } = &m else {
unreachable!()
};
validate_entry_payload(entry)?;
Ok(m)
}
"record" => {
let m: SessionMutation = serde_json::from_value(Value::Object(obj)).map_err(|e| {
RecordLogError::InvalidRecord {
message: format!("has invalid record: {e}"),
}
})?;
let SessionMutation::Record { record } = &m else {
unreachable!()
};
validate_record_payload(record)?;
Ok(m)
}
"lane" => {
let m: SessionMutation = serde_json::from_value(Value::Object(obj)).map_err(|e| {
RecordLogError::InvalidLane {
message: format!("has invalid lane: {e}"),
}
})?;
Ok(m)
}
"fact" => {
let m: SessionMutation = serde_json::from_value(Value::Object(obj)).map_err(|e| {
RecordLogError::InvalidFact {
message: format!("has invalid fact: {e}"),
}
})?;
let SessionMutation::Fact { fact, .. } = &m else {
unreachable!()
};
if fact != "name" && fact != "label" {
return Err(RecordLogError::UnknownFactType {
message: format!("has unknown fact type {fact}"),
});
}
Ok(m)
}
_ => Err(RecordLogError::UnknownMutationKind {
message: format!("has unknown mutation kind {kind}"),
}),
}
}
fn validate_entry_payload(entry: &Entry) -> Result<(), RecordLogError> {
if entry.seq() == 0 {
return Err(RecordLogError::InvalidSeq {
message: "has invalid seq".to_string(),
});
}
let base = entry_base(entry);
if base.timestamp < 0 {
return Err(RecordLogError::InvalidTimestamp {
message: "has invalid timestamp".to_string(),
});
}
if let Entry::Custom { custom_type, .. } = entry
&& custom_type.is_empty()
{
return Err(RecordLogError::InvalidCustomType {
message: "has invalid customType".to_string(),
});
}
if let Entry::ContextEdit { target_id, .. } = entry
&& target_id.is_empty()
{
return Err(RecordLogError::InvalidEntry {
message: "context_edit has empty targetId".to_string(),
});
}
Ok(())
}
fn validate_record_payload(record: &LaneRecord) -> Result<(), RecordLogError> {
if record.seq() == 0 {
return Err(RecordLogError::InvalidSeq {
message: "has invalid seq".to_string(),
});
}
if record.timestamp() < 0 {
return Err(RecordLogError::InvalidTimestamp {
message: "has invalid timestamp".to_string(),
});
}
Ok(())
}
pub fn entry_base(entry: &Entry) -> &EntryBase {
match entry {
Entry::Message { base, .. }
| Entry::ModelChange { base, .. }
| Entry::ThinkingLevelChange { base, .. }
| Entry::ActiveToolsChange { base, .. }
| Entry::Compaction { base, .. }
| Entry::BranchSummary { base, .. }
| Entry::ContextEdit { base, .. }
| Entry::Custom { base, .. } => base,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordBase {
pub id: String,
pub seq: u64,
pub lane: String,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum OperationIntent {
Run {
#[serde(rename = "originalPrompt")]
original_prompt: Vec<AgentMessage>,
#[serde(rename = "initialMessages")]
initial_messages: Vec<ProvisionedEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(rename = "systemPromptOverride")]
system_prompt_override: Option<String>,
},
Compaction {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(rename = "customInstructions")]
custom_instructions: Option<String>,
#[serde(rename = "resultEntryId")]
result_entry_id: String,
},
Navigation {
#[serde(rename = "targetId")]
target_id: Option<String>,
summarize: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(rename = "customInstructions")]
custom_instructions: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(rename = "summaryEntryId")]
summary_entry_id: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum LaneRecord {
OperationStarted {
#[serde(flatten)]
base: RecordBase,
#[serde(rename = "sourceLeafId")]
source_leaf_id: Option<String>,
intent: OperationIntent,
},
AbortRequested {
#[serde(flatten)]
base: RecordBase,
#[serde(rename = "runId")]
run_id: String,
},
OperationFinished {
#[serde(flatten)]
base: RecordBase,
#[serde(rename = "runId")]
run_id: String,
outcome: String,
error: Option<Value>,
},
StepAttempt {
#[serde(flatten)]
base: RecordBase,
#[serde(rename = "runId")]
run_id: String,
step: String,
attempt: u32,
#[serde(rename = "resultEntryId")]
result_entry_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(rename = "compactionReason")]
compaction_reason: Option<String>,
},
ToolStarted {
#[serde(flatten)]
base: RecordBase,
#[serde(rename = "runId")]
run_id: String,
#[serde(rename = "assistantEntryId")]
assistant_entry_id: String,
#[serde(rename = "toolIndex")]
tool_index: usize,
#[serde(rename = "toolCallId")]
tool_call_id: String,
#[serde(rename = "toolName")]
tool_name: String,
#[serde(rename = "effectiveArgs")]
effective_args: Value,
#[serde(rename = "resultEntryId")]
result_entry_id: String,
replay: String,
},
QueueEnqueued {
#[serde(flatten)]
base: RecordBase,
queue: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(rename = "runId")]
run_id: Option<String>,
target: ProvisionedEntry,
},
QueueCancelled {
#[serde(flatten)]
base: RecordBase,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(rename = "runId")]
run_id: Option<String>,
#[serde(rename = "entryId")]
entry_id: String,
},
WriteDeferred {
#[serde(flatten)]
base: RecordBase,
#[serde(rename = "runId")]
run_id: String,
target: ProvisionedEntry,
},
UsageRecord {
#[serde(flatten)]
base: RecordBase,
usage: Usage,
cause: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
model: Option<String>,
#[serde(rename = "runId")]
run_id: Option<String>,
#[serde(rename = "entryId")]
entry_id: Option<String>,
attempt: Option<u32>,
#[serde(rename = "stopReason")]
stop_reason: Option<String>,
#[serde(rename = "toolCallId")]
tool_call_id: Option<String>,
},
}
impl LaneRecord {
pub fn seq(&self) -> u64 {
match self {
LaneRecord::OperationStarted { base, .. }
| LaneRecord::AbortRequested { base, .. }
| LaneRecord::OperationFinished { base, .. }
| LaneRecord::StepAttempt { base, .. }
| LaneRecord::ToolStarted { base, .. }
| LaneRecord::QueueEnqueued { base, .. }
| LaneRecord::QueueCancelled { base, .. }
| LaneRecord::WriteDeferred { base, .. }
| LaneRecord::UsageRecord { base, .. } => base.seq,
}
}
pub fn timestamp(&self) -> i64 {
match self {
LaneRecord::OperationStarted { base, .. }
| LaneRecord::AbortRequested { base, .. }
| LaneRecord::OperationFinished { base, .. }
| LaneRecord::StepAttempt { base, .. }
| LaneRecord::ToolStarted { base, .. }
| LaneRecord::QueueEnqueued { base, .. }
| LaneRecord::QueueCancelled { base, .. }
| LaneRecord::WriteDeferred { base, .. }
| LaneRecord::UsageRecord { base, .. } => base.timestamp,
}
}
pub fn lane(&self) -> &str {
match self {
LaneRecord::OperationStarted { base, .. }
| LaneRecord::AbortRequested { base, .. }
| LaneRecord::OperationFinished { base, .. }
| LaneRecord::StepAttempt { base, .. }
| LaneRecord::ToolStarted { base, .. }
| LaneRecord::QueueEnqueued { base, .. }
| LaneRecord::QueueCancelled { base, .. }
| LaneRecord::WriteDeferred { base, .. }
| LaneRecord::UsageRecord { base, .. } => &base.lane,
}
}
pub fn id(&self) -> &str {
match self {
LaneRecord::OperationStarted { base, .. }
| LaneRecord::AbortRequested { base, .. }
| LaneRecord::OperationFinished { base, .. }
| LaneRecord::StepAttempt { base, .. }
| LaneRecord::ToolStarted { base, .. }
| LaneRecord::QueueEnqueued { base, .. }
| LaneRecord::QueueCancelled { base, .. }
| LaneRecord::WriteDeferred { base, .. }
| LaneRecord::UsageRecord { base, .. } => &base.id,
}
}
fn run_id(&self) -> Option<&str> {
match self {
LaneRecord::OperationStarted { .. } => None,
LaneRecord::AbortRequested { run_id, .. }
| LaneRecord::OperationFinished { run_id, .. }
| LaneRecord::StepAttempt { run_id, .. }
| LaneRecord::ToolStarted { run_id, .. }
| LaneRecord::WriteDeferred { run_id, .. } => Some(run_id),
LaneRecord::QueueEnqueued { run_id, .. } => run_id.as_deref(),
LaneRecord::QueueCancelled { run_id, .. } => run_id.as_deref(),
LaneRecord::UsageRecord { run_id, .. } => run_id.as_deref(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct EffectiveLaneConfiguration {
pub provider: String,
pub model_id: String,
pub thinking_level: String,
pub active_tool_names: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct StepState {
pub kind: String,
pub attempts: u32,
pub result_entry_id: String,
pub compaction_reason: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ToolBatchState {
pub assistant_entry_id: String,
pub calls: Vec<ToolBatchCall>,
pub truncated: bool,
pub unresolved: bool,
}
#[derive(Debug, Clone)]
pub struct ToolBatchCall {
pub tool_index: usize,
pub tool_call_id: String,
pub tool_name: String,
pub arguments: Value,
pub result_exists: bool,
pub terminate: Option<bool>,
}
#[derive(Debug, Clone, Default)]
pub struct OperationState {
pub id: String,
pub kind: String,
pub source_leaf_id: Option<String>,
pub aborting: bool,
pub step: Option<StepState>,
pub tool_batch: Option<ToolBatchState>,
pub missing_initial_messages: Vec<ProvisionedEntry>,
pub pending_steer: Vec<ProvisionedEntry>,
pub pending_follow_up: Vec<ProvisionedEntry>,
pub pending_writes: Vec<ProvisionedEntry>,
pub overflow_recovery_used: bool,
pub newest_own: Option<Value>,
pub targets: Value,
}
#[derive(Debug, Clone)]
pub struct LaneState {
pub lane: String,
pub leaf_id: Option<String>,
pub operation: Option<OperationState>,
pub pending_next_run: Vec<ProvisionedEntry>,
}
#[derive(Debug, Clone)]
pub struct TerminalFailureState {
pub entry_id: String,
pub source: String,
pub message: AgentMessage,
}
#[derive(Debug, Clone)]
pub struct LaneReductionResult {
pub lane_state: LaneState,
pub effective_configuration: EffectiveLaneConfiguration,
pub terminal_failure: Option<TerminalFailureState>,
}
pub struct LaneReductionInput {
pub lane: String,
pub leaf_id: Option<String>,
pub open_operations: Vec<LaneRecord>,
pub records: Vec<LaneRecord>,
pub entries: Vec<Entry>,
pub own_entries: Vec<Entry>,
pub configuration_entries: Vec<Entry>,
pub defaults: EffectiveLaneConfiguration,
}
fn matches_provisioned(target: &ProvisionedEntry, entry: &Entry) -> bool {
target.matches_entry(entry)
}
fn validate_exact_provisioned(
entries_by_id: &HashMap<String, Entry>,
target: &ProvisionedEntry,
) -> Result<(), RecordLogError> {
if let Some(entry) = entries_by_id.get(&target.id)
&& !matches_provisioned(target, entry)
{
return corrupt(RecordLogError::ProvisionedEntryMismatch {
message: format!(
"Provisioned entry {} exists with content different from its intent",
target.id
),
});
}
Ok(())
}
fn validate_result_entry(
entries_by_id: &HashMap<String, Entry>,
result_entry_id: &str,
matches: impl Fn(&Entry) -> bool,
description: &str,
) -> Result<(), RecordLogError> {
if let Some(entry) = entries_by_id.get(result_entry_id)
&& !matches(entry)
{
return corrupt(RecordLogError::ProvisionedEntryMismatch {
message: format!(
"Provisioned {} entry {} exists with different content",
description, result_entry_id
),
});
}
Ok(())
}
fn validate_attempt_reason(record: &LaneRecord) -> Result<(), RecordLogError> {
if let LaneRecord::StepAttempt {
step,
compaction_reason,
base,
..
} = record
{
let reason = compaction_reason.as_deref();
if step == "compaction" {
if !matches!(
reason,
Some("manual") | Some("threshold") | Some("overflow")
) {
return corrupt(RecordLogError::InvalidCompactionReason {
message: format!(
"Compaction attempt {} has no valid compaction reason",
base.id
),
});
}
} else if reason.is_some() {
return corrupt(RecordLogError::InvalidCompactionReason {
message: format!("{} attempt {} has a compaction reason", step, base.id),
});
}
}
Ok(())
}
#[derive(Clone)]
struct AttemptSeries {
record: LaneRecord,
}
fn validate_attempt_sequence(
record: &LaneRecord,
previous: Option<&AttemptSeries>,
_entries_by_id: &HashMap<String, Entry>,
) -> Result<(), RecordLogError> {
let LaneRecord::StepAttempt {
step,
attempt,
result_entry_id,
compaction_reason,
base,
..
} = record
else {
return Ok(());
};
let continues_series = match &previous {
Some(p) => {
let prev_step = match &p.record {
LaneRecord::StepAttempt { step, .. } => step,
_ => return Ok(()),
};
prev_step == step
}
None => false,
};
let expected = if continues_series {
match &previous.unwrap().record {
LaneRecord::StepAttempt { attempt, .. } => attempt + 1,
_ => 1,
}
} else {
1
};
if *attempt != expected {
return corrupt(RecordLogError::NonConsecutiveAttempt {
message: format!(
"{} attempt {} is {}; expected {}",
step, base.id, attempt, expected
),
});
}
if !continues_series || step == "assistant" || previous.is_none() {
return Ok(());
}
let prev = previous.unwrap();
let (_prev_step, prev_result, prev_reason) = match &prev.record {
LaneRecord::StepAttempt {
step,
result_entry_id,
compaction_reason,
..
} => (step.as_str(), result_entry_id, compaction_reason.as_deref()),
_ => return Ok(()),
};
if prev_result != result_entry_id {
return corrupt(RecordLogError::InconsistentStep {
message: format!("{} attempts disagree on their result entry id", step),
});
}
if prev_reason != compaction_reason.as_deref() {
return corrupt(RecordLogError::InconsistentStep {
message: format!("{} attempts disagree on their compaction reason", step),
});
}
Ok(())
}
fn validate_attempt_result(
entries_by_id: &HashMap<String, Entry>,
record: &LaneRecord,
) -> Result<(), RecordLogError> {
let LaneRecord::StepAttempt {
step,
result_entry_id,
..
} = record
else {
return Ok(());
};
match step.as_str() {
"assistant" => validate_result_entry(
entries_by_id,
result_entry_id,
|e| matches!(e, Entry::Message { message, .. } if message.role == "assistant"),
"assistant result",
),
"compaction" => validate_result_entry(
entries_by_id,
result_entry_id,
|e| matches!(e, Entry::Compaction { .. }),
"compaction result",
),
"branch_summary" => validate_result_entry(
entries_by_id,
result_entry_id,
|e| matches!(e, Entry::BranchSummary { .. }),
"branch-summary result",
),
_ => Ok(()),
}
}
fn validate_tool_start(
record: &LaneRecord,
entries_by_id: &HashMap<String, Entry>,
invocations: &mut HashSet<String>,
) -> Result<(), RecordLogError> {
let LaneRecord::ToolStarted {
assistant_entry_id,
tool_index,
tool_call_id,
tool_name,
result_entry_id,
base,
..
} = record
else {
return Ok(());
};
let invocation = format!("{}\u{0}{}", assistant_entry_id, tool_index);
if !invocations.insert(invocation) {
return corrupt(RecordLogError::DuplicateToolInvocation {
message: format!(
"Tool invocation {}:{} is duplicated",
assistant_entry_id, tool_index
),
});
}
let assistant = entries_by_id.get(assistant_entry_id);
match assistant {
Some(Entry::Message { message, .. }) if message.role == "assistant" => {
let tool_calls: Vec<&ContentBlock> = message
.content
.iter()
.filter(|b| matches!(b, ContentBlock::ToolCall { .. }))
.collect();
let tool_call = tool_calls.get(*tool_index);
let (call_id, call_name, _args) = match tool_call {
Some(ContentBlock::ToolCall { id, name, .. }) => (id.as_str(), name.as_str(), ()),
_ => {
return corrupt(RecordLogError::ToolCallMismatch {
message: format!(
"Tool start {} does not reference an assistant entry with tool calls",
base.id
),
});
}
};
if call_id != tool_call_id || call_name != tool_name {
return corrupt(RecordLogError::ToolCallMismatch {
message: format!(
"Tool start {} does not match its assistant tool-call ordinal",
base.id
),
});
}
}
_ => {
return corrupt(RecordLogError::ToolCallMismatch {
message: format!(
"Tool start {} does not reference an assistant entry",
base.id
),
});
}
}
validate_result_entry(
entries_by_id,
result_entry_id,
|e| {
matches!(
e,
Entry::Message {
message,
..
} if message.role == "toolResult" && message.tool_call_id.as_deref() == Some(tool_call_id)
&& message.tool_name.as_deref() == Some(tool_name)
)
},
"tool result",
)
}
fn validate_deferred_handles(entries: Vec<&Entry>) -> Result<(), RecordLogError> {
for entry in entries {
if let Entry::Message { message, .. } = entry
&& message.role == "assistant"
&& message.stop_reason.as_deref() == Some("deferred")
&& message.deferred.is_none()
{
return corrupt(RecordLogError::InvalidDeferredHandle {
message: format!(
"Deferred assistant entry {} does not carry a handle",
entry.id()
),
});
}
}
Ok(())
}
fn validate_operation_result(
entries_by_id: &HashMap<String, Entry>,
record: &LaneRecord,
) -> Result<(), RecordLogError> {
let LaneRecord::OperationStarted { intent, .. } = record else {
return Ok(());
};
match intent {
OperationIntent::Run {
initial_messages, ..
} => {
for target in initial_messages {
validate_exact_provisioned(entries_by_id, target)?;
}
}
OperationIntent::Compaction {
result_entry_id, ..
} => {
validate_result_entry(
entries_by_id,
result_entry_id,
|e| matches!(e, Entry::Compaction { .. }),
"manual compaction",
)?;
}
OperationIntent::Navigation {
summary_entry_id, ..
} => {
if let Some(id) = summary_entry_id {
validate_result_entry(
entries_by_id,
id,
|e| matches!(e, Entry::BranchSummary { .. }),
"navigation summary",
)?;
}
}
}
Ok(())
}
pub fn validate_record_log(
open_operations: &[LaneRecord],
records: &[LaneRecord],
entries: &[Entry],
) -> Result<(), RecordLogError> {
let entries_by_id: HashMap<String, Entry> = entries
.iter()
.map(|e| (e.id().to_string(), e.clone()))
.collect();
validate_deferred_handles(entries.iter().collect())?;
let mut starts: HashMap<String, LaneRecord> = HashMap::new();
let mut finished_at: HashMap<String, u64> = HashMap::new();
let mut aborted_at: HashMap<String, u64> = HashMap::new();
let mut queue_enqueues: HashMap<String, LaneRecord> = HashMap::new();
let mut latest_attempt: HashMap<String, AttemptSeries> = HashMap::new();
let mut tool_invocations: HashSet<String> = HashSet::new();
for op in open_operations {
if let LaneRecord::OperationStarted { base, .. } = op {
starts.insert(base.id.clone(), op.clone());
validate_operation_result(&entries_by_id, op)?;
}
}
let mut sorted: Vec<&LaneRecord> = records.iter().collect();
sorted.sort_by_key(|r| r.seq());
for record in sorted {
if matches!(record, LaneRecord::OperationStarted { .. }) {
starts.insert(record_base_id(record).to_string(), record.clone());
validate_operation_result(&entries_by_id, record)?;
continue;
}
if let Some(run_id) = record.run_id() {
if !starts.contains_key(run_id) {
return corrupt(RecordLogError::UnknownOperation {
message: format!(
"Record {} references unknown operation {}",
record_base_id(record),
run_id
),
});
}
if let Some(finish_seq) = finished_at.get(run_id)
&& record.seq() > *finish_seq
{
return corrupt(RecordLogError::RecordAfterFinish {
message: format!(
"Record {} follows the finish of operation {}",
record_base_id(record),
run_id
),
});
}
}
match record {
LaneRecord::OperationStarted { .. } => {}
LaneRecord::OperationFinished { run_id, base, .. } => {
finished_at.insert(run_id.clone(), base.seq);
}
LaneRecord::AbortRequested { run_id, base, .. } => {
aborted_at.insert(run_id.clone(), base.seq);
}
LaneRecord::StepAttempt { .. } => {
validate_attempt_reason(record)?;
let prev = record.run_id().and_then(|rid| latest_attempt.get(rid));
validate_attempt_sequence(record, prev, &entries_by_id)?;
validate_attempt_result(&entries_by_id, record)?;
if let Some(rid) = record.run_id() {
latest_attempt.insert(
rid.to_string(),
AttemptSeries {
record: record.clone(),
},
);
}
}
LaneRecord::ToolStarted { .. } => {
validate_tool_start(record, &entries_by_id, &mut tool_invocations)?;
}
LaneRecord::QueueEnqueued {
queue,
run_id,
target,
base,
..
} => {
if let Some(run_id) = run_id.as_ref()
&& queue != "nextRun"
&& aborted_at
.get(run_id)
.is_some_and(|&aborted| base.seq > aborted)
{
return corrupt(RecordLogError::QueueAfterAbort {
message: format!("{} item {} was enqueued after abort", queue, target.id),
});
}
queue_enqueues.insert(target.id.clone(), record.clone());
validate_exact_provisioned(&entries_by_id, target)?;
}
LaneRecord::QueueCancelled {
entry_id,
run_id,
base,
..
} => {
let enqueue = queue_enqueues.get(entry_id);
let enqueue_seq = enqueue.map(|e| e.seq()).unwrap_or(u64::MAX);
let enqueue_run = enqueue.and_then(|e| e.run_id().map(|s| s.to_string()));
if enqueue.is_none()
|| enqueue_seq >= base.seq
|| enqueue_run != run_id.clone().or(None)
|| entries_by_id.contains_key(entry_id)
{
return corrupt(RecordLogError::InvalidQueueCancellation {
message: format!(
"Queue cancellation {} has no pending matching enqueue",
base.id
),
});
}
}
LaneRecord::WriteDeferred { target, .. } => {
validate_exact_provisioned(&entries_by_id, target)?;
}
LaneRecord::UsageRecord { .. } => {}
}
}
Ok(())
}
fn record_base_id(record: &LaneRecord) -> &str {
match record {
LaneRecord::OperationStarted { base, .. }
| LaneRecord::AbortRequested { base, .. }
| LaneRecord::OperationFinished { base, .. }
| LaneRecord::StepAttempt { base, .. }
| LaneRecord::ToolStarted { base, .. }
| LaneRecord::QueueEnqueued { base, .. }
| LaneRecord::QueueCancelled { base, .. }
| LaneRecord::WriteDeferred { base, .. }
| LaneRecord::UsageRecord { base, .. } => &base.id,
}
}
fn by_sequence<T: Clone + HasSeq>(values: &[T]) -> Vec<T> {
let mut v: Vec<T> = values.to_vec();
v.sort_by_key(|a| a.seq());
v
}
trait HasSeq {
fn seq(&self) -> u64;
}
impl HasSeq for Entry {
fn seq(&self) -> u64 {
self.seq()
}
}
impl HasSeq for LaneRecord {
fn seq(&self) -> u64 {
self.seq()
}
}
fn derive_effective_configuration(input: &LaneReductionInput) -> EffectiveLaneConfiguration {
let mut configuration = input.defaults.clone();
let mut all: Vec<Entry> = Vec::new();
all.extend(input.configuration_entries.iter().cloned());
all.extend(input.own_entries.iter().cloned());
for entry in by_sequence(&all) {
match &entry {
Entry::ModelChange {
provider, model_id, ..
} => {
configuration.provider = provider.clone();
configuration.model_id = model_id.clone();
}
Entry::ThinkingLevelChange { thinking_level, .. } => {
configuration.thinking_level = thinking_level.clone();
}
Entry::ActiveToolsChange {
active_tool_names, ..
} => {
configuration.active_tool_names = active_tool_names.clone();
}
Entry::Message { message, .. } if message.role == "assistant" => {
if let (Some(prov), Some(model)) = (&message.provider, &message.model) {
configuration.provider = prov.clone();
configuration.model_id = model.clone();
}
}
_ => {}
}
}
configuration
}
fn derive_newest_own(entries: &[Entry]) -> Option<Value> {
let entry = entries.last()?;
let v = serde_json::to_value(entry.payload_json()).unwrap_or(Value::Null);
Some(v)
}
pub fn reduce_lane_state(input: LaneReductionInput) -> Result<LaneReductionResult, RecordLogError> {
validate_record_log(&input.open_operations, &input.records, &input.entries)?;
let records = by_sequence(&input.records);
let own_entries = by_sequence(&input.own_entries);
let mut entries_by_id: HashMap<String, Entry> = HashMap::new();
for entry in input.entries.iter().chain(input.own_entries.iter()) {
entries_by_id.insert(entry.id().to_string(), entry.clone());
}
let cancelled: HashSet<String> = records
.iter()
.filter(|r| matches!(r, LaneRecord::QueueCancelled { .. }))
.filter_map(|r| match r {
LaneRecord::QueueCancelled { entry_id, .. } => Some(entry_id.clone()),
_ => None,
})
.collect();
let pending_queue_records: Vec<LaneRecord> = records
.iter()
.filter(|r| {
matches!(r, LaneRecord::QueueEnqueued { .. })
&& !entries_by_id.contains_key(target_id_of(r))
&& !cancelled.contains(target_id_of(r))
})
.cloned()
.collect();
let started = input
.open_operations
.iter()
.max_by_key(|r| r.seq())
.cloned();
let captured_initial = if let Some(LaneRecord::OperationStarted { intent, .. }) = &started {
match intent {
OperationIntent::Run {
initial_messages, ..
} => initial_messages
.iter()
.map(|t| t.id.clone())
.collect::<HashSet<_>>(),
_ => Default::default(),
}
} else {
Default::default()
};
let pending_next_run: Vec<ProvisionedEntry> = pending_queue_records
.iter()
.filter(|r| queue_of(r) == "nextRun" && !captured_initial.contains(target_id_of(r)))
.filter_map(target_of)
.cloned()
.collect();
let effective_configuration = derive_effective_configuration(&input);
let Some(started) = started else {
return Ok(LaneReductionResult {
lane_state: LaneState {
lane: input.lane,
leaf_id: input.leaf_id,
operation: None,
pending_next_run,
},
effective_configuration,
terminal_failure: None,
});
};
let started_id = match &started {
LaneRecord::OperationStarted { base, .. } => base.id.clone(),
_ => String::new(),
};
let operation_records: Vec<&LaneRecord> = records
.iter()
.filter(|r| {
if matches!(r, LaneRecord::OperationStarted { .. }) {
record_base_id(r) == started_id
} else {
r.run_id().map(|rid| rid == started_id).unwrap_or(false)
}
})
.collect();
let aborting = operation_records
.iter()
.any(|r| matches!(r, LaneRecord::AbortRequested { .. }));
let pending_steer = if aborting {
vec![]
} else {
pending_queue_records
.iter()
.filter(|r| {
queue_of(r) == "steer" && r.run_id().map(|rid| rid == started_id).unwrap_or(false)
})
.filter_map(target_of)
.cloned()
.collect()
};
let pending_follow_up = if aborting {
vec![]
} else {
pending_queue_records
.iter()
.filter(|r| {
queue_of(r) == "followUp"
&& r.run_id().map(|rid| rid == started_id).unwrap_or(false)
})
.filter_map(target_of)
.cloned()
.collect()
};
let pending_writes: Vec<ProvisionedEntry> = operation_records
.iter()
.filter(|r| {
matches!(r, LaneRecord::WriteDeferred { .. })
&& !entries_by_id.contains_key(target_id_of(r))
})
.filter_map(|r| target_of(r))
.cloned()
.collect();
let missing_initial_messages = if let LaneRecord::OperationStarted { intent, .. } = &started {
match intent {
OperationIntent::Run {
initial_messages, ..
} => initial_messages
.iter()
.filter(|t| !entries_by_id.contains_key(&t.id))
.cloned()
.collect(),
_ => vec![],
}
} else {
vec![]
};
let newest_attempt = operation_records
.iter()
.rfind(|r| matches!(r, LaneRecord::StepAttempt { .. }));
let step = newest_attempt.and_then(|r| match r {
LaneRecord::StepAttempt {
step,
attempt,
result_entry_id,
compaction_reason,
..
} => {
if entries_by_id.contains_key(result_entry_id) {
None
} else {
Some(StepState {
kind: step.clone(),
attempts: *attempt,
result_entry_id: result_entry_id.clone(),
compaction_reason: compaction_reason.clone(),
})
}
}
_ => None,
});
let mut consumed: HashSet<String> = HashSet::new();
if let LaneRecord::OperationStarted { intent, .. } = &started
&& let OperationIntent::Run {
initial_messages, ..
} = intent
{
for t in initial_messages {
consumed.insert(t.id.clone());
}
}
for record in &operation_records {
if let LaneRecord::QueueEnqueued { queue, target, .. } = record
&& queue != "nextRun"
{
consumed.insert(target.id.clone());
}
}
let mut newest_consumed_seq = 0u64;
for id in &consumed {
if let Some(Entry::Message { base, .. }) = entries_by_id.get(id) {
newest_consumed_seq = newest_consumed_seq.max(base.seq);
}
}
let overflow_recovery_used = operation_records.iter().any(|r| {
matches!(
r,
LaneRecord::StepAttempt {
step,
compaction_reason,
base,
..
} if step == "compaction"
&& compaction_reason.as_deref() == Some("overflow")
&& base.seq > newest_consumed_seq
)
});
let newest_own_entry = own_entries
.iter()
.rev()
.find(|e| !e.is_context_edit())
.cloned();
let newest_own = derive_newest_own(&own_entries);
let _deferred = match &newest_own_entry {
Some(Entry::Message { message, .. })
if message.role == "assistant"
&& message.stop_reason.as_deref() == Some("deferred") =>
{
message.deferred.clone()
}
_ => None,
};
let mut targets = serde_json::Map::new();
if let LaneRecord::OperationStarted { intent, .. } = &started {
match intent {
OperationIntent::Compaction {
result_entry_id, ..
} => {
targets.insert(
"result".into(),
Value::Bool(entries_by_id.contains_key(result_entry_id)),
);
}
OperationIntent::Navigation {
summary_entry_id: Some(id),
..
} => {
targets.insert(
"summary".into(),
Value::Bool(entries_by_id.contains_key(id)),
);
}
_ => {}
}
}
let deferred_write_ids: HashSet<String> = operation_records
.iter()
.filter(|r| matches!(r, LaneRecord::WriteDeferred { .. }))
.filter_map(|r| {
let id = target_id_of(r);
if id.is_empty() {
None
} else {
Some(id.to_string())
}
})
.collect();
let mut terminal_failure: Option<TerminalFailureState> = None;
if let Some(Entry::Message { message, base, .. }) = &newest_own_entry
&& message.role == "assistant"
&& message.stop_reason.as_deref() == Some("error")
&& !deferred_write_ids.contains(&base.id)
{
let produced_by_step = operation_records.iter().any(|r| {
matches!(r, LaneRecord::StepAttempt { result_entry_id, .. } if result_entry_id == &base.id)
});
let produced_by_deferred_fetch = operation_records.iter().any(|r| {
matches!(
r,
LaneRecord::UsageRecord {
cause,
entry_id,
..
} if cause == "deferred_fetch" && entry_id.as_deref() == Some(&base.id)
)
}) || matches!(
own_entries.get(own_entries.len().wrapping_sub(2)),
Some(Entry::Message {
message,
..
}) if message.role == "assistant" && message.stop_reason.as_deref() == Some("deferred")
);
if produced_by_step || produced_by_deferred_fetch {
terminal_failure = Some(TerminalFailureState {
entry_id: base.id.clone(),
source: if produced_by_step {
"step".into()
} else {
"deferred_fetch".into()
},
message: message.clone(),
});
}
}
let tool_batch = {
let dw: HashSet<String> = deferred_write_ids.clone();
let ass_id = own_entries
.iter()
.rev()
.find(|e| {
matches!(
e,
Entry::Message {
message,
..
} if message.role == "assistant"
&& message.content.iter().any(|b| matches!(b, ContentBlock::ToolCall { .. }))
)
})
.map(|e| e.id().to_string());
if let Some(ass_id) = ass_id {
let mut starts: HashMap<usize, LaneRecord> = HashMap::new();
for record in &operation_records {
if let LaneRecord::ToolStarted {
assistant_entry_id,
tool_index,
..
} = record
&& assistant_entry_id == &ass_id
{
starts.insert(*tool_index, (*record).clone());
}
}
let assistant_entry = own_entries.iter().find(|e| e.id() == ass_id).cloned();
if let Some(Entry::Message { message, base, .. }) = assistant_entry {
let tool_calls: Vec<ContentBlock> = message
.content
.iter()
.filter(|b| matches!(b, ContentBlock::ToolCall { .. }))
.cloned()
.collect();
let mut calls = Vec::new();
let mut unresolved = false;
for (tool_index, tc) in tool_calls.iter().enumerate() {
let (call_id, call_name, call_args) = match tc {
ContentBlock::ToolCall {
id,
name,
arguments,
..
} => (id.clone(), name.clone(), arguments.clone()),
_ => continue,
};
let started = starts.get(&tool_index);
let started_result = started.and_then(|s| match s {
LaneRecord::ToolStarted {
result_entry_id, ..
} => entries_by_id.get(result_entry_id),
_ => None,
});
let blocked_result = own_entries.iter().find(|e| {
matches!(
e,
Entry::Message {
message,
base: eb,
..
} if message.role == "toolResult"
&& message.tool_call_id.as_deref() == Some(&call_id)
&& eb.seq > base.seq
&& !dw.contains(&eb.id)
)
});
let result = started_result.or(blocked_result);
let terminate = result.and_then(|e| match e {
Entry::Message { terminate, .. } => *terminate,
_ => None,
});
if result.is_none() {
unresolved = true;
}
calls.push(ToolBatchCall {
tool_index,
tool_call_id: call_id,
tool_name: call_name,
arguments: call_args,
result_exists: result.is_some(),
terminate,
});
}
Some(ToolBatchState {
assistant_entry_id: base.id.clone(),
calls,
truncated: message.stop_reason.as_deref() == Some("length"),
unresolved,
})
} else {
None
}
} else {
None
}
};
Ok(LaneReductionResult {
lane_state: LaneState {
lane: input.lane,
leaf_id: input.leaf_id,
operation: Some(OperationState {
id: started_id,
kind: match &started {
LaneRecord::OperationStarted { intent, .. } => match intent {
OperationIntent::Run { .. } => "run".into(),
OperationIntent::Compaction { .. } => "compaction".into(),
OperationIntent::Navigation { .. } => "navigation".into(),
},
_ => String::new(),
},
source_leaf_id: match &started {
LaneRecord::OperationStarted { source_leaf_id, .. } => source_leaf_id.clone(),
_ => None,
},
aborting,
step,
tool_batch,
missing_initial_messages,
pending_steer,
pending_follow_up,
pending_writes,
overflow_recovery_used,
newest_own,
targets: Value::Object(targets),
}),
pending_next_run,
},
effective_configuration,
terminal_failure,
})
}
fn target_id_of(record: &LaneRecord) -> &str {
match record {
LaneRecord::QueueEnqueued { target, .. } | LaneRecord::WriteDeferred { target, .. } => {
&target.id
}
_ => "",
}
}
fn queue_of(record: &LaneRecord) -> &str {
match record {
LaneRecord::QueueEnqueued { queue, .. } => queue.as_str(),
_ => "",
}
}
fn target_of(record: &LaneRecord) -> Option<&ProvisionedEntry> {
match record {
LaneRecord::QueueEnqueued { target, .. } | LaneRecord::WriteDeferred { target, .. } => {
Some(target)
}
_ => None,
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct SessionStats {
pub message_count: usize,
pub cached_tokens: u64,
pub uncached_tokens: u64,
pub total_tokens: u64,
pub cost_total: f64,
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum LogItem {
Entry { seq: u64, entry: Entry },
Record { seq: u64, record: LaneRecord },
Lane {
seq: u64,
lane: String,
leaf_id: Option<String>,
},
FactName { seq: u64, name: Option<String> },
FactLabel {
seq: u64,
target_id: String,
label: Option<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ForkPosition {
Before,
At,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ForkScope {
Tree,
Branch {
entry_id: Option<String>,
position: Option<ForkPosition>,
},
}
#[derive(Debug, Clone, Default)]
pub struct SessionState {
sequence: u64,
used_ids: HashSet<String>,
entries: Vec<Entry>,
entries_by_id: HashMap<String, Entry>,
records: Vec<LaneRecord>,
open_operations_by_lane: HashMap<String, HashMap<String, LaneRecord>>,
lanes: HashMap<String, Option<String>>,
log: Vec<LogItem>,
stats: SessionStats,
name: Option<String>,
labels: HashMap<String, String>,
}
impl SessionState {
pub fn new() -> Self {
let mut lanes = HashMap::new();
lanes.insert("main".to_string(), None);
Self {
lanes,
..Default::default()
}
}
pub fn next_sequence(&self) -> u64 {
self.sequence + 1
}
pub fn get_lanes(&self) -> Vec<(String, Option<String>)> {
let mut lanes: Vec<(String, Option<String>)> = self
.lanes
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
lanes.sort();
lanes
}
pub fn require_lane(&self, lane: &str) -> Result<Option<String>, RecordLogError> {
self.lanes
.get(lane)
.cloned()
.ok_or_else(|| RecordLogError::LaneNotFound {
message: format!("Lane not found: {lane}"),
})
}
pub fn lane_leaf(&self, lane: &str) -> Option<&str> {
self.lanes.get(lane).and_then(|l| l.as_deref())
}
pub fn get_entry(&self, id: &str) -> Option<&Entry> {
self.entries_by_id.get(id)
}
pub fn has_id(&self, id: &str) -> bool {
self.used_ids.contains(id)
}
pub fn entries(&self) -> &[Entry] {
&self.entries
}
pub fn get_entries(&self) -> &[Entry] {
&self.entries
}
pub fn records(&self) -> &[LaneRecord] {
&self.records
}
pub fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn get_label(&self, id: &str) -> Option<&str> {
self.labels.get(id).map(|s| s.as_str())
}
pub fn get_stats(&self) -> &SessionStats {
&self.stats
}
pub fn apply_mutation(&mut self, m: SessionMutation) -> Result<(), RecordLogError> {
let seq = mutation_seq(&m);
if seq != self.sequence + 1 {
return corrupt(RecordLogError::NonConsecutiveSeq {
message: format!("has non-consecutive seq {seq}"),
});
}
match m {
SessionMutation::Entry { lane, entry } => {
if self.used_ids.contains(entry.id()) {
return corrupt(RecordLogError::DuplicateId {
message: format!("contains duplicate id {}", entry.id()),
});
}
let base = entry_base(&entry);
if let Some(lane_name) = lane.as_deref() {
let leaf_id =
self.lanes
.get(lane_name)
.ok_or_else(|| RecordLogError::MissingLane {
message: format!("references missing lane {lane_name}"),
})?;
if base.parent_id.as_deref() != leaf_id.as_deref() {
return corrupt(RecordLogError::DoesNotChainToLaneLeaf {
message: "does not chain to the lane leaf".to_string(),
});
}
}
if base
.parent_id
.as_deref()
.is_some_and(|p| !self.entries_by_id.contains_key(p))
{
return corrupt(RecordLogError::MissingParent {
message: format!("references missing parent {:?}", base.parent_id),
});
}
self.sequence = seq;
self.used_ids.insert(entry.id().to_string());
self.entries.push(entry.clone());
self.entries_by_id
.insert(entry.id().to_string(), entry.clone());
if let Some(lane_name) = lane.as_deref() {
self.lanes
.insert(lane_name.to_string(), Some(entry.id().to_string()));
}
self.log.push(LogItem::Entry {
seq,
entry: entry.clone(),
});
if matches!(entry, Entry::Message { .. }) {
self.stats.message_count += 1;
}
}
SessionMutation::Record { record } => {
let lane = record.lane().to_string();
if !self.lanes.contains_key(&lane) {
return corrupt(RecordLogError::MissingLane {
message: format!("references missing lane {lane}"),
});
}
if self.used_ids.contains(record.id()) {
return corrupt(RecordLogError::DuplicateId {
message: format!("contains duplicate id {}", record.id()),
});
}
self.sequence = seq;
self.used_ids.insert(record.id().to_string());
let op_id = record.id().to_string();
let is_start = matches!(record, LaneRecord::OperationStarted { .. });
let is_finish = matches!(record, LaneRecord::OperationFinished { .. });
if is_start {
self.open_operations_by_lane
.entry(lane.clone())
.or_default()
.insert(op_id.clone(), record.clone());
} else if is_finish
&& let LaneRecord::OperationFinished { run_id, .. } = &record
&& let Some(open) = self.open_operations_by_lane.get_mut(&lane)
{
open.remove(run_id);
}
self.records.push(record.clone());
self.log.push(LogItem::Record {
seq,
record: record.clone(),
});
if let LaneRecord::UsageRecord { usage, .. } = &record {
self.stats.cached_tokens += u64::from(usage.cache_read);
self.stats.uncached_tokens +=
u64::from(usage.input) + u64::from(usage.cache_write);
self.stats.total_tokens += u64::from(usage.total_tokens);
self.stats.cost_total += usage.cost.total;
}
}
SessionMutation::Lane {
seq: _seq,
lane,
leaf_id,
} => {
if leaf_id
.as_deref()
.is_some_and(|id| !self.entries_by_id.contains_key(id))
{
return corrupt(RecordLogError::MissingLaneTarget {
message: format!("references missing lane target {:?}", leaf_id),
});
}
self.sequence = seq;
self.lanes.insert(lane.clone(), leaf_id.clone());
self.log.push(LogItem::Lane { seq, lane, leaf_id });
}
SessionMutation::Fact {
seq: _seq,
fact,
name,
target_id,
label,
} => {
if fact == "label"
&& target_id
.as_deref()
.is_some_and(|id| !self.entries_by_id.contains_key(id))
{
return corrupt(RecordLogError::MissingLabelTarget {
message: format!("references missing label target {:?}", target_id),
});
}
self.sequence = seq;
match fact.as_str() {
"name" => {
self.name = name.clone();
self.log.push(LogItem::FactName { seq, name });
}
"label" => {
if let Some(t) = target_id {
match label.clone() {
Some(l) => {
self.labels.insert(t.clone(), l.clone());
}
None => {
self.labels.remove(&t);
}
}
self.log.push(LogItem::FactLabel {
seq,
target_id: t,
label,
});
}
}
_ => {}
}
}
}
Ok(())
}
fn walk_to_root<'a>(
&'a self,
start: &str,
stop_at_id: Option<&str>,
stop_at_type: Option<&str>,
) -> Result<Vec<&'a Entry>, RecordLogError> {
let mut visited = HashSet::new();
let mut current =
self.entries_by_id
.get(start)
.ok_or_else(|| RecordLogError::EntryNotFound {
message: format!("Entry not found: {start}"),
})?;
let mut path = Vec::new();
loop {
if visited.contains(current.id()) {
return corrupt(RecordLogError::Cycle {
message: format!("Session branch contains a cycle at {}", current.id()),
});
}
visited.insert(current.id().to_string());
path.push(current);
let base = entry_base(current);
let Some(parent_id) = base.parent_id.as_deref() else {
break;
};
if parent_id == "null" || parent_id.is_empty() {
break;
}
if stop_at_id.is_some_and(|id| current.id() == id)
|| stop_at_type.is_some_and(|t| entry_type_name(current) == t)
{
break;
}
current =
self.entries_by_id
.get(parent_id)
.ok_or_else(|| RecordLogError::EntryNotFound {
message: format!("Entry not found: {parent_id}"),
})?;
}
Ok(path)
}
pub fn find_entries_on_branch(
&self,
start: &str,
stop_at_id: Option<&str>,
stop_at_type: Option<&str>,
oldest_first: bool,
) -> Result<Vec<Entry>, RecordLogError> {
let path = self.walk_to_root(start, stop_at_id, stop_at_type)?;
let mut entries: Vec<Entry> = path.into_iter().cloned().collect();
if oldest_first {
entries.reverse();
}
Ok(entries)
}
pub fn find_open_operations(&self, lane: &str, limit: Option<usize>) -> Vec<LaneRecord> {
let mut ops: Vec<LaneRecord> = match self.open_operations_by_lane.get(lane) {
Some(map) => map.values().cloned().collect(),
None => Vec::new(),
};
ops.sort_by_key(|b| std::cmp::Reverse(b.seq()));
if let Some(limit) = limit {
ops.truncate(limit);
}
ops
}
pub fn create_fork_mutations(
&self,
scope: &ForkScope,
) -> Result<Vec<SessionMutation>, RecordLogError> {
let (copied_entries, fork_lanes) = match scope {
ForkScope::Tree => (self.entries.clone(), self.get_lanes()),
ForkScope::Branch { entry_id, position } => {
let selected_entry_id = match entry_id {
Some(id) => Some(id.clone()),
None => self.require_lane("main")?,
};
let mut target_id: Option<String> = None;
if let Some(id) = selected_entry_id {
let entry = self.entries_by_id.get(&id).ok_or_else(|| {
RecordLogError::EntryNotFound {
message: format!("Entry not found: {id}"),
}
})?;
if !matches!(entry, Entry::Message { .. }) {
return corrupt(RecordLogError::ForkTargetNotMessage {
message: format!("Fork target is not a message entry: {id}"),
});
}
let pos = match position {
Some(p) => *p,
None => {
if entry_id.is_none() {
ForkPosition::At
} else {
ForkPosition::Before
}
}
};
target_id = match pos {
ForkPosition::At => Some(id.clone()),
ForkPosition::Before => entry_base(entry).parent_id.clone(),
};
}
let copied = match &target_id {
Some(t) => self.find_entries_on_branch(t, None, None, true)?,
None => Vec::new(),
};
(copied, vec![("main".to_string(), target_id)])
}
};
let mut mutations: Vec<SessionMutation> = Vec::new();
let mut sequence = 1u64;
let mut fact_mutations: Vec<SessionMutation> = Vec::new();
if let Some(name) = &self.name {
fact_mutations.push(SessionMutation::Fact {
seq: 0,
fact: "name".to_string(),
name: Some(name.clone()),
target_id: None,
label: None,
});
}
for entry in &copied_entries {
if let Some(label) = self.labels.get(entry.id()) {
fact_mutations.push(SessionMutation::Fact {
seq: 0,
fact: "label".to_string(),
name: None,
target_id: Some(entry.id().to_string()),
label: Some(label.clone()),
});
}
}
for mut entry in copied_entries {
entry_base_mut(&mut entry).seq = sequence;
mutations.push(SessionMutation::Entry { lane: None, entry });
sequence += 1;
}
for (lane, leaf_id) in fork_lanes {
mutations.push(SessionMutation::Lane {
seq: sequence,
lane,
leaf_id,
});
sequence += 1;
}
for mut fact in fact_mutations {
let SessionMutation::Fact { seq, .. } = &mut fact else {
continue;
};
*seq = sequence;
mutations.push(fact);
sequence += 1;
}
Ok(mutations)
}
pub fn branch_mutations(&self, lane: &str) -> Result<Vec<SessionMutation>, RecordLogError> {
let leaf = self.require_lane(lane)?;
let entries = match &leaf {
Some(leaf_id) => self.find_entries_on_branch(leaf_id, None, None, true)?,
None => Vec::new(),
};
let mut labels: Vec<(String, String)> = Vec::new();
for entry in &entries {
if let Some(label) = self.labels.get(entry.id()) {
labels.push((entry.id().to_string(), label.clone()));
}
}
let mut mutations: Vec<SessionMutation> = Vec::new();
let mut sequence = 1u64;
for mut entry in entries {
entry_base_mut(&mut entry).seq = sequence;
mutations.push(SessionMutation::Entry { lane: None, entry });
sequence += 1;
}
mutations.push(SessionMutation::Lane {
seq: sequence,
lane: lane.to_string(),
leaf_id: leaf,
});
sequence += 1;
for (target_id, label) in labels {
mutations.push(SessionMutation::Fact {
seq: sequence,
fact: "label".to_string(),
name: None,
target_id: Some(target_id),
label: Some(label),
});
sequence += 1;
}
Ok(mutations)
}
}
fn mutation_seq(m: &SessionMutation) -> u64 {
match m {
SessionMutation::Entry { entry, .. } => entry.seq(),
SessionMutation::Record { record } => record.seq(),
SessionMutation::Lane { seq, .. } | SessionMutation::Fact { seq, .. } => *seq,
}
}
fn record_base_mut(record: &mut LaneRecord) -> &mut RecordBase {
match record {
LaneRecord::OperationStarted { base, .. }
| LaneRecord::AbortRequested { base, .. }
| LaneRecord::OperationFinished { base, .. }
| LaneRecord::StepAttempt { base, .. }
| LaneRecord::ToolStarted { base, .. }
| LaneRecord::QueueEnqueued { base, .. }
| LaneRecord::QueueCancelled { base, .. }
| LaneRecord::WriteDeferred { base, .. }
| LaneRecord::UsageRecord { base, .. } => base,
}
}
pub fn shift_mutation_seq(m: &mut SessionMutation, delta: i64) -> Option<u64> {
let new_seq = mutation_seq(m).checked_add_signed(delta)?;
match m {
SessionMutation::Entry { entry, .. } => entry_base_mut(entry).seq = new_seq,
SessionMutation::Record { record } => record_base_mut(record).seq = new_seq,
SessionMutation::Lane { seq, .. } | SessionMutation::Fact { seq, .. } => *seq = new_seq,
}
Some(new_seq)
}
pub fn set_mutation_seq(m: &mut SessionMutation, seq: u64) {
match m {
SessionMutation::Entry { entry, .. } => entry_base_mut(entry).seq = seq,
SessionMutation::Record { record } => record_base_mut(record).seq = seq,
SessionMutation::Lane { seq: s, .. } | SessionMutation::Fact { seq: s, .. } => *s = seq,
}
}
fn entry_type_name(entry: &Entry) -> &'static str {
match entry {
Entry::Message { .. } => "message",
Entry::ModelChange { .. } => "model_change",
Entry::ThinkingLevelChange { .. } => "thinking_level_change",
Entry::ActiveToolsChange { .. } => "active_tools_change",
Entry::Compaction { .. } => "compaction",
Entry::BranchSummary { .. } => "branch_summary",
Entry::ContextEdit { .. } => "context_edit",
Entry::Custom { .. } => "custom",
}
}
fn entry_base_mut(entry: &mut Entry) -> &mut EntryBase {
match entry {
Entry::Message { base, .. }
| Entry::ModelChange { base, .. }
| Entry::ThinkingLevelChange { base, .. }
| Entry::ActiveToolsChange { base, .. }
| Entry::Compaction { base, .. }
| Entry::BranchSummary { base, .. }
| Entry::ContextEdit { base, .. }
| Entry::Custom { base, .. } => base,
}
}
pub fn set_entry_base(entry: &mut Entry, base: EntryBase) {
match entry {
Entry::Message { base: b, .. }
| Entry::ModelChange { base: b, .. }
| Entry::ThinkingLevelChange { base: b, .. }
| Entry::ActiveToolsChange { base: b, .. }
| Entry::Compaction { base: b, .. }
| Entry::BranchSummary { base: b, .. }
| Entry::ContextEdit { base: b, .. }
| Entry::Custom { base: b, .. } => *b = base,
}
}
pub fn set_record_base(record: &mut LaneRecord, base: RecordBase) {
match record {
LaneRecord::OperationStarted { base: b, .. }
| LaneRecord::AbortRequested { base: b, .. }
| LaneRecord::OperationFinished { base: b, .. }
| LaneRecord::StepAttempt { base: b, .. }
| LaneRecord::ToolStarted { base: b, .. }
| LaneRecord::QueueEnqueued { base: b, .. }
| LaneRecord::QueueCancelled { base: b, .. }
| LaneRecord::WriteDeferred { base: b, .. }
| LaneRecord::UsageRecord { base: b, .. } => *b = base,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::provider::ContentBlock;
fn msg_entry(id: &str, seq: u64, role: &str, text: &str) -> Entry {
Entry::Message {
base: EntryBase {
id: id.into(),
seq,
timestamp: 0,
parent_id: None,
},
message: if role == "user" {
AgentMessage::user_text(text)
} else {
let mut m = AgentMessage::user_text(text);
m.role = role.to_string();
m
},
terminate: None,
}
}
fn assistant_toolcall_entry(id: &str, seq: u64, calls: Vec<(&str, &str)>) -> Entry {
let content: Vec<ContentBlock> = calls
.into_iter()
.map(|(cid, name)| ContentBlock::ToolCall {
id: cid.into(),
name: name.into(),
arguments: serde_json::json!({}),
thought_signature: None,
namespace: None,
arguments_error: None,
})
.collect();
let mut m = AgentMessage::user_text("");
m.role = "assistant".into();
m.stop_reason = Some("toolUse".into());
m.content = content;
Entry::Message {
base: EntryBase {
id: id.into(),
seq,
timestamp: 0,
parent_id: None,
},
message: m,
terminate: None,
}
}
#[test]
fn usage_record_without_attribution_still_loads() {
let legacy = serde_json::json!({
"type": "usage_record",
"id": "r1",
"seq": 1,
"lane": "main",
"timestamp": 0,
"cause": "cache_warm",
"usage": { "input": 1, "output": 2, "cacheRead": 3, "cacheWrite": 4, "totalTokens": 10 }
});
let record: LaneRecord = serde_json::from_value(legacy).unwrap();
match record {
LaneRecord::UsageRecord {
provider,
model,
usage,
..
} => {
assert!(provider.is_none() && model.is_none());
assert_eq!(usage.total_tokens, 10);
}
other => panic!("expected usage_record, got {other:?}"),
}
}
fn op_started(id: &str, seq: u64, lane: &str) -> LaneRecord {
LaneRecord::OperationStarted {
base: RecordBase {
id: id.into(),
seq,
lane: lane.into(),
timestamp: 0,
},
source_leaf_id: None,
intent: OperationIntent::Run {
original_prompt: vec![AgentMessage::user_text("hi")],
initial_messages: vec![],
system_prompt_override: None,
},
}
}
fn op_finished(id: &str, run_id: &str, seq: u64, lane: &str, outcome: &str) -> LaneRecord {
LaneRecord::OperationFinished {
base: RecordBase {
id: id.into(),
seq,
lane: lane.into(),
timestamp: 0,
},
run_id: run_id.into(),
outcome: outcome.into(),
error: None,
}
}
#[allow(clippy::too_many_arguments)]
fn step_attempt(
id: &str,
run_id: &str,
seq: u64,
lane: &str,
step: &str,
attempt: u32,
result_entry_id: &str,
compaction_reason: Option<&str>,
) -> LaneRecord {
LaneRecord::StepAttempt {
base: RecordBase {
id: id.into(),
seq,
lane: lane.into(),
timestamp: 0,
},
run_id: run_id.into(),
step: step.into(),
attempt,
result_entry_id: result_entry_id.into(),
compaction_reason: compaction_reason.map(|s| s.to_string()),
}
}
#[allow(clippy::too_many_arguments)]
fn tool_started(
id: &str,
run_id: &str,
seq: u64,
lane: &str,
assistant_entry_id: &str,
tool_index: usize,
tool_call_id: &str,
tool_name: &str,
result_entry_id: &str,
) -> LaneRecord {
LaneRecord::ToolStarted {
base: RecordBase {
id: id.into(),
seq,
lane: lane.into(),
timestamp: 0,
},
run_id: run_id.into(),
assistant_entry_id: assistant_entry_id.into(),
tool_index,
tool_call_id: tool_call_id.into(),
tool_name: tool_name.into(),
effective_args: serde_json::json!({}),
result_entry_id: result_entry_id.into(),
replay: "never".into(),
}
}
fn queue_enqueued(
id: &str,
run_id: &str,
seq: u64,
lane: &str,
queue: &str,
target: ProvisionedEntry,
) -> LaneRecord {
LaneRecord::QueueEnqueued {
base: RecordBase {
id: id.into(),
seq,
lane: lane.into(),
timestamp: 0,
},
queue: queue.into(),
run_id: if run_id.is_empty() {
None
} else {
Some(run_id.into())
},
target,
}
}
fn provisioned(id: &str) -> ProvisionedEntry {
ProvisionedEntry {
id: id.into(),
payload: serde_json::json!({ "type": "message", "message": { "role": "user", "content": [] } }),
}
}
fn base_input(lane: &str) -> LaneReductionInput {
LaneReductionInput {
lane: lane.into(),
leaf_id: None,
open_operations: vec![],
records: vec![],
entries: vec![],
own_entries: vec![],
configuration_entries: vec![],
defaults: EffectiveLaneConfiguration {
provider: "p".into(),
model_id: "m".into(),
thinking_level: "off".into(),
active_tool_names: vec![],
},
}
}
#[test]
fn idle_lane_reduces_empty_operation() {
let result = reduce_lane_state(base_input("l1")).unwrap();
assert!(result.lane_state.operation.is_none());
assert!(result.lane_state.pending_next_run.is_empty());
assert_eq!(result.effective_configuration.provider, "p");
assert!(result.terminal_failure.is_none());
}
#[test]
fn effective_configuration_derived_from_entries() {
let mut input = base_input("l1");
input.configuration_entries = vec![
Entry::ModelChange {
base: EntryBase {
id: "m1".into(),
seq: 1,
timestamp: 0,
parent_id: None,
},
provider: "anthropic".into(),
model_id: "claude".into(),
},
Entry::ThinkingLevelChange {
base: EntryBase {
id: "t1".into(),
seq: 2,
timestamp: 0,
parent_id: None,
},
thinking_level: "high".into(),
},
Entry::ActiveToolsChange {
base: EntryBase {
id: "a1".into(),
seq: 3,
timestamp: 0,
parent_id: None,
},
active_tool_names: vec!["read".into(), "bash".into()],
},
];
let result = reduce_lane_state(input).unwrap();
let ctx = result.effective_configuration;
assert_eq!(ctx.provider, "anthropic");
assert_eq!(ctx.model_id, "claude");
assert_eq!(ctx.thinking_level, "high");
assert_eq!(
ctx.active_tool_names,
vec!["read".to_string(), "bash".to_string()]
);
}
#[test]
fn open_operation_with_step_attempts_reduces() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.records = vec![
step_attempt("s1", run_id, 5, "l1", "assistant", 1, "e-assistant-1", None),
op_finished("f1", run_id, 9, "l1", "completed"),
];
input.own_entries = vec![msg_entry("e-user", 2, "user", "hi")];
input.entries = vec![assistant_toolcall_entry("e-assistant-1", 6, vec![])];
let result = reduce_lane_state(input).unwrap();
let st = result.lane_state.operation.unwrap();
assert_eq!(st.kind, "run");
assert!(!st.aborting);
assert!(st.step.is_none());
assert!(!st.overflow_recovery_used);
assert!(result.terminal_failure.is_none());
}
#[test]
fn unresolved_step_reduces_to_pending_step() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.records = vec![step_attempt(
"s1",
run_id,
5,
"l1",
"assistant",
1,
"e-missing",
None,
)];
input.own_entries = vec![msg_entry("e-user", 2, "user", "hi")];
let result = reduce_lane_state(input).unwrap();
let st = result.lane_state.operation.unwrap();
let step = st.step.unwrap();
assert_eq!(step.kind, "assistant");
assert_eq!(step.attempts, 1);
assert_eq!(step.result_entry_id, "e-missing");
}
#[test]
fn tool_batch_recovered_from_tool_started_records() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.own_entries = vec![
assistant_toolcall_entry("e-assistant", 3, vec![("call-1", "read")]),
msg_entry("e-tr", 4, "toolResult", "file contents"),
];
if let Entry::Message { message, .. } = &mut input.own_entries[1] {
message.tool_call_id = Some("call-1".into());
message.tool_name = Some("read".into());
}
input.entries = vec![assistant_toolcall_entry(
"e-assistant",
3,
vec![("call-1", "read")],
)];
input.records = vec![tool_started(
"t1",
run_id,
6,
"l1",
"e-assistant",
0,
"call-1",
"read",
"e-tr",
)];
let result = reduce_lane_state(input).unwrap();
let st = result.lane_state.operation.unwrap();
let batch = st.tool_batch.unwrap();
assert_eq!(batch.assistant_entry_id, "e-assistant");
assert_eq!(batch.calls.len(), 1);
assert_eq!(batch.calls[0].tool_call_id, "call-1");
assert!(batch.calls[0].result_exists);
assert!(!batch.unresolved);
assert!(!batch.truncated);
assert!(result.terminal_failure.is_none());
}
#[test]
fn pending_steer_follow_up_and_next_run_reduced() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.records = vec![
queue_enqueued("q1", run_id, 7, "l1", "steer", provisioned("e-steer")),
queue_enqueued("q2", run_id, 8, "l1", "followUp", provisioned("e-fu")),
queue_enqueued("q3", "", 9, "l1", "nextRun", provisioned("e-nr")),
];
input.own_entries = vec![msg_entry("e-user", 2, "user", "hi")];
let result = reduce_lane_state(input).unwrap();
let st = result.lane_state.operation.unwrap();
assert_eq!(st.pending_steer.len(), 1);
assert_eq!(st.pending_steer[0].id, "e-steer");
assert_eq!(st.pending_follow_up.len(), 1);
assert_eq!(st.pending_follow_up[0].id, "e-fu");
assert_eq!(result.lane_state.pending_next_run.len(), 1);
assert_eq!(result.lane_state.pending_next_run[0].id, "e-nr");
}
#[test]
fn aborted_operation_clears_pending_queues() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.records = vec![
queue_enqueued("q1", run_id, 5, "l1", "steer", provisioned("e-steer")),
LaneRecord::AbortRequested {
base: RecordBase {
id: "ab1".into(),
seq: 6,
lane: "l1".into(),
timestamp: 0,
},
run_id: run_id.into(),
},
];
input.own_entries = vec![msg_entry("e-user", 2, "user", "hi")];
let result = reduce_lane_state(input).unwrap();
let st = result.lane_state.operation.unwrap();
assert!(st.aborting);
assert!(st.pending_steer.is_empty());
}
#[test]
fn terminal_failure_from_error_step() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
let mut failure = AgentMessage::user_text("");
failure.role = "assistant".into();
failure.stop_reason = Some("error".into());
failure.error_message = Some("boom".into());
input.own_entries = vec![
msg_entry("e-user", 2, "user", "hi"),
Entry::Message {
base: EntryBase {
id: "e-err".into(),
seq: 5,
timestamp: 0,
parent_id: None,
},
message: failure,
terminate: None,
},
];
input.records = vec![step_attempt(
"s1",
run_id,
5,
"l1",
"assistant",
1,
"e-err",
None,
)];
let result = reduce_lane_state(input).unwrap();
let tf = result.terminal_failure.unwrap();
assert_eq!(tf.entry_id, "e-err");
assert_eq!(tf.source, "step");
assert_eq!(tf.message.error_message.as_deref(), Some("boom"));
}
#[test]
fn terminal_failure_survives_trailing_context_edit() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
let mut failure = AgentMessage::user_text("");
failure.role = "assistant".into();
failure.stop_reason = Some("error".into());
failure.error_message = Some("boom".into());
input.own_entries = vec![
msg_entry("e-user", 2, "user", "hi"),
Entry::Message {
base: EntryBase {
id: "e-err".into(),
seq: 5,
timestamp: 0,
parent_id: None,
},
message: failure,
terminate: None,
},
Entry::ContextEdit {
base: EntryBase {
id: "e-edit".into(),
seq: 6,
timestamp: 0,
parent_id: Some("e-err".into()),
},
target_id: "e-err".into(),
replacement: None,
},
];
input.records = vec![step_attempt(
"s1",
run_id,
5,
"l1",
"assistant",
1,
"e-err",
None,
)];
let result = reduce_lane_state(input).unwrap();
let tf = result
.terminal_failure
.expect("context_edit 不应顶掉终结失败判定");
assert_eq!(tf.entry_id, "e-err");
}
#[test]
fn corruption_unknown_operation() {
let mut input = base_input("l1");
input.records = vec![step_attempt(
"s1",
"no-such-run",
5,
"l1",
"assistant",
1,
"e-x",
None,
)];
let err = reduce_lane_state(input).unwrap_err();
assert!(
matches!(err, RecordLogError::UnknownOperation { .. }),
"{err}"
);
}
#[test]
fn multiple_open_operations_takes_newest_and_old_are_zombies() {
let mut input = base_input("l1");
input.open_operations = vec![op_started("op1", 1, "l1"), op_started("op2", 2, "l1")];
input.own_entries = vec![msg_entry("e-user", 3, "user", "hi")];
input.records = vec![
step_attempt("s1", "op1", 4, "l1", "assistant", 1, "e-missing-1", None),
step_attempt("s2", "op2", 5, "l1", "assistant", 1, "e-ok", None),
];
input.entries = vec![msg_entry("e-ok", 6, "assistant", "done")];
let result = reduce_lane_state(input).unwrap();
let st = result.lane_state.operation.unwrap();
assert_eq!(st.id, "op2");
assert!(st.step.is_none());
assert!(result.terminal_failure.is_none());
}
#[test]
fn multiple_open_operations_oldest_step_is_ignored() {
let mut input = base_input("l1");
input.open_operations = vec![op_started("op1", 1, "l1"), op_started("op2", 2, "l1")];
input.own_entries = vec![msg_entry("e-user", 3, "user", "hi")];
input.records = vec![step_attempt(
"s1",
"op2",
7,
"l1",
"assistant",
1,
"e-missing-2",
None,
)];
let result = reduce_lane_state(input).unwrap();
let st = result.lane_state.operation.unwrap();
assert_eq!(st.id, "op2");
let step = st.step.unwrap();
assert_eq!(step.result_entry_id, "e-missing-2");
}
#[test]
fn corruption_non_consecutive_attempt() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.records = vec![
step_attempt("s1", run_id, 5, "l1", "assistant", 1, "e-1", None),
step_attempt("s2", run_id, 7, "l1", "assistant", 3, "e-2", None),
];
let err = reduce_lane_state(input).unwrap_err();
assert!(
matches!(err, RecordLogError::NonConsecutiveAttempt { .. }),
"{err}"
);
}
#[test]
fn corruption_tool_call_mismatch() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.own_entries = vec![assistant_toolcall_entry(
"e-assistant",
3,
vec![("call-1", "read")],
)];
input.entries = vec![assistant_toolcall_entry(
"e-assistant",
3,
vec![("call-1", "read")],
)];
input.records = vec![tool_started(
"t1",
run_id,
6,
"l1",
"e-assistant",
0,
"WRONG-CALL",
"read",
"e-tr",
)];
let err = reduce_lane_state(input).unwrap_err();
assert!(
matches!(err, RecordLogError::ToolCallMismatch { .. }),
"{err}"
);
}
#[test]
fn corruption_invalid_compaction_reason() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.records = vec![step_attempt(
"s1",
run_id,
5,
"l1",
"compaction",
1,
"e-c",
Some("bogus"),
)];
let err = reduce_lane_state(input).unwrap_err();
assert!(
matches!(err, RecordLogError::InvalidCompactionReason { .. }),
"{err}"
);
}
#[test]
fn corruption_record_after_finish() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.records = vec![
op_finished("f1", run_id, 9, "l1", "completed"),
step_attempt("s1", run_id, 10, "l1", "assistant", 1, "e-1", None),
];
let err = reduce_lane_state(input).unwrap_err();
assert!(
matches!(err, RecordLogError::RecordAfterFinish { .. }),
"{err}"
);
}
#[test]
fn json_roundtrip_records() {
let records = vec![
op_started("op1", 1, "l1"),
step_attempt("s1", "run1", 5, "l1", "assistant", 1, "e-1", None),
op_finished("f1", "run1", 9, "l1", "completed"),
];
for r in &records {
let v = serde_json::to_value(r).unwrap();
let back: LaneRecord = serde_json::from_value(v).unwrap();
assert_eq!(r.seq(), back.seq());
}
}
#[test]
fn corruption_duplicate_tool_invocation() {
let mut input = base_input("l1");
let run_id = "run1";
input.open_operations = vec![op_started("run1", 1, "l1")];
input.own_entries = vec![assistant_toolcall_entry(
"e-assistant",
3,
vec![("call-1", "read")],
)];
input.entries = vec![assistant_toolcall_entry(
"e-assistant",
3,
vec![("call-1", "read")],
)];
input.records = vec![
tool_started(
"t1",
run_id,
6,
"l1",
"e-assistant",
0,
"call-1",
"read",
"e-tr",
),
tool_started(
"t2",
run_id,
8,
"l1",
"e-assistant",
0,
"call-1",
"read",
"e-tr",
),
];
let err = reduce_lane_state(input).unwrap_err();
assert!(
matches!(err, RecordLogError::DuplicateToolInvocation { .. }),
"{err}"
);
}
#[test]
fn validation_missing_provisioned_entry_ok() {
let mut input = base_input("l1");
input.records = vec![queue_enqueued(
"q1",
"run1",
7,
"l1",
"steer",
provisioned("e-steer"),
)];
input.open_operations = vec![op_started("run1", 1, "l1")];
let result = reduce_lane_state(input).unwrap();
let st = result.lane_state.operation.unwrap();
assert_eq!(st.pending_steer.len(), 1);
}
#[test]
fn entry_payload_json_excludes_metadata() {
let e = msg_entry("e1", 5, "user", "hi");
let p = e.payload_json();
assert!(p.get("parentId").is_none());
assert!(p.get("seq").is_none());
assert!(p.get("timestamp").is_none());
}
fn state_entry(
state: &mut SessionState,
id: &str,
parent: Option<&str>,
) -> Result<(), RecordLogError> {
let seq = state.next_sequence();
state.apply_mutation(SessionMutation::Entry {
lane: Some("main".to_string()),
entry: Entry::Message {
base: EntryBase {
id: id.into(),
seq,
timestamp: 1,
parent_id: parent.map(|s| s.to_string()),
},
message: AgentMessage::user_text("x"),
terminate: None,
},
})
}
#[test]
fn state_seq_must_be_consecutive_from_one() {
let mut st = SessionState::new();
let err = st
.apply_mutation(SessionMutation::Entry {
lane: Some("main".to_string()),
entry: Entry::Message {
base: EntryBase {
id: "e0".into(),
seq: 0,
timestamp: 1,
parent_id: None,
},
message: AgentMessage::user_text("x"),
terminate: None,
},
})
.unwrap_err();
assert!(err.to_string().contains("non-consecutive"), "{}", err);
let mut st = SessionState::new();
let _ = state_entry(&mut st, "e1", None);
let err = st
.apply_mutation(SessionMutation::Entry {
lane: Some("main".to_string()),
entry: Entry::Message {
base: EntryBase {
id: "e3".into(),
seq: 5,
timestamp: 1,
parent_id: Some("e1".into()),
},
message: AgentMessage::user_text("x"),
terminate: None,
},
})
.unwrap_err();
assert!(err.to_string().contains("non-consecutive"), "{}", err);
}
#[test]
fn state_entry_must_chain_to_lane_leaf() {
let mut st = SessionState::new();
let _ = state_entry(&mut st, "e1", None);
let err = st
.apply_mutation(SessionMutation::Entry {
lane: Some("main".to_string()),
entry: Entry::Message {
base: EntryBase {
id: "e2".into(),
seq: 2,
timestamp: 1,
parent_id: Some("phantom".into()),
},
message: AgentMessage::user_text("x"),
terminate: None,
},
})
.unwrap_err();
assert!(
err.to_string().contains("chain to the lane leaf"),
"{}",
err
);
}
#[test]
fn state_lane_pointer_and_walk_to_root() {
let mut st = SessionState::new();
let _ = state_entry(&mut st, "a", None);
let _ = state_entry(&mut st, "b", Some("a"));
let _ = state_entry(&mut st, "c", Some("b"));
assert_eq!(st.lane_leaf("main"), Some("c"));
let branch = st.find_entries_on_branch("c", None, None, false).unwrap();
let ids: Vec<&str> = branch.iter().map(|e| e.id()).collect();
assert_eq!(ids, vec!["c", "b", "a"]);
let rev = st.find_entries_on_branch("c", None, None, true).unwrap();
let ids: Vec<&str> = rev.iter().map(|e| e.id()).collect();
assert_eq!(ids, vec!["a", "b", "c"]);
let mut st2 = SessionState::new();
let _ = state_entry(&mut st2, "a", None);
let seq = st2.next_sequence();
let e = Entry::Message {
base: EntryBase {
id: "x".into(),
seq,
timestamp: 1,
parent_id: Some("missing".into()),
},
message: AgentMessage::user_text("x"),
terminate: None,
};
st2.entries.push(e.clone());
st2.entries_by_id.insert("x".into(), e);
let err = st2
.find_entries_on_branch("x", None, None, true)
.unwrap_err();
assert!(err.to_string().contains("Entry not found"), "{}", err);
}
#[test]
fn state_open_operations_tracked_and_cleared() {
let mut st = SessionState::new();
let _ = state_entry(&mut st, "a", None);
let seq = st.next_sequence();
st.apply_mutation(SessionMutation::Record {
record: LaneRecord::OperationStarted {
base: RecordBase {
id: "run-1".into(),
seq,
lane: "main".into(),
timestamp: 1,
},
source_leaf_id: None,
intent: OperationIntent::Run {
original_prompt: vec![AgentMessage::user_text("q")],
initial_messages: vec![],
system_prompt_override: None,
},
},
})
.unwrap();
assert_eq!(st.find_open_operations("main", None).len(), 1);
let seq = st.next_sequence();
st.apply_mutation(SessionMutation::Record {
record: LaneRecord::OperationFinished {
base: RecordBase {
id: "op-f".into(),
seq,
lane: "main".into(),
timestamp: 2,
},
run_id: "run-1".into(),
outcome: "completed".into(),
error: None,
},
})
.unwrap();
assert_eq!(st.find_open_operations("main", None).len(), 0);
}
#[test]
fn state_fork_mutations_exclude_records() {
let mut st = SessionState::new();
let _ = state_entry(&mut st, "a", None);
let _ = state_entry(&mut st, "b", Some("a"));
let seq = st.next_sequence();
st.apply_mutation(SessionMutation::Record {
record: LaneRecord::OperationStarted {
base: RecordBase {
id: "run-1".into(),
seq,
lane: "main".into(),
timestamp: 1,
},
source_leaf_id: None,
intent: OperationIntent::Run {
original_prompt: vec![],
initial_messages: vec![],
system_prompt_override: None,
},
},
})
.unwrap();
st.apply_mutation(SessionMutation::Fact {
seq: st.next_sequence(),
fact: "name".into(),
name: Some("n".into()),
target_id: None,
label: None,
})
.unwrap();
let ms = st.create_fork_mutations(&ForkScope::Tree).unwrap();
let kinds: Vec<&str> = ms
.iter()
.map(|m| match m {
SessionMutation::Entry { .. } => "entry",
SessionMutation::Record { .. } => "record",
SessionMutation::Lane { .. } => "lane",
SessionMutation::Fact { .. } => "fact",
})
.collect();
assert_eq!(kinds, vec!["entry", "entry", "lane", "fact"]);
}
#[test]
fn codec_roundtrip_header_and_mutations() {
let header = JsonlV4Header::new("id-1".into(), "/tmp".into(), 12345, Some("parent".into()));
let line = header.encode();
let v: Value = serde_json::from_str(line.trim()).unwrap();
assert_eq!(v["kind"], "header");
assert_eq!(v["version"], 4);
assert_eq!(v["createdAt"], 12345);
let m = SessionMutation::Entry {
lane: Some("main".to_string()),
entry: Entry::Message {
base: EntryBase {
id: "e1".into(),
seq: 1,
timestamp: 7,
parent_id: None,
},
message: AgentMessage::user_text("hi"),
terminate: None,
},
};
let encoded = encode_mutation(&m);
let parsed = parse_mutation(encoded.trim()).unwrap();
assert!(matches!(parsed, SessionMutation::Entry { lane: Some(l), .. } if l == "main"));
assert!(parse_mutation(r#"{"kind":"bogus","seq":1}"#).is_err());
assert!(parse_mutation(
r#"{"kind":"entry","lane":"main","type":"message","id":"x","parentId":null,"seq":0,"timestamp":1,"message":{"role":"user","content":[]}}"#
)
.is_err());
}
#[test]
fn context_edit_entry_roundtrips_and_validates() {
let line = r#"{"kind":"entry","lane":"main","type":"context_edit","id":"ce1","parentId":"e1","seq":9,"timestamp":3,"targetId":"e1","replacement":null}"#;
let parsed = parse_mutation(line).unwrap();
let SessionMutation::Entry { entry, .. } = &parsed else {
panic!("expected entry mutation");
};
assert!(entry.is_context_edit());
assert_eq!(entry.context_edit_target(), Some("e1"));
assert!(matches!(
entry,
Entry::ContextEdit {
replacement: None,
..
}
));
let v: Value = serde_json::from_str(encode_mutation(&parsed).trim()).unwrap();
assert_eq!(v["type"], "context_edit");
assert_eq!(v["targetId"], "e1");
assert!(v["replacement"].is_null());
let line = r#"{"kind":"entry","lane":"main","type":"context_edit","id":"ce2","seq":10,"timestamp":3,"targetId":"e1","replacement":{"content":[{"type":"text","text":"rewritten"}]}}"#;
let parsed = parse_mutation(line).unwrap();
let SessionMutation::Entry { entry, .. } = &parsed else {
panic!("expected entry mutation");
};
match entry {
Entry::ContextEdit { replacement, .. } => {
let rep = replacement.as_ref().expect("replacement present");
assert_eq!(rep.content.len(), 1);
}
other => panic!("expected context_edit, got {other:?}"),
}
let bad = r#"{"kind":"entry","lane":"main","type":"context_edit","id":"ce3","seq":11,"timestamp":3,"targetId":"","replacement":null}"#;
assert!(parse_mutation(bad).is_err());
let parsed = parse_mutation(line).unwrap();
let SessionMutation::Entry { entry, .. } = &parsed else {
unreachable!()
};
assert_eq!(entry_base(entry).id, "ce2");
assert_eq!(entry_type_name(entry), "context_edit");
}
}