Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
09e0ac9
fix(cache): distinguish stable prefix from history rewrites
senamakel Sep 24, 2026
132a5da
fix(cache): require explicit stable message boundaries
senamakel Sep 24, 2026
ef79919
fix(cache): preserve frozen prefix through compaction
senamakel Sep 24, 2026
0579867
fix(cache): guard unknown prefix boundaries conservatively
senamakel Sep 24, 2026
a9d8a07
fix(cache): preserve explicit and resumed prefix boundaries
senamakel Sep 24, 2026
18d9022
fix(cache): preserve replacement prompt boundaries
senamakel Sep 24, 2026
620e1b0
fix(cache): retain custom volatile layout identity
senamakel Sep 24, 2026
dd3f861
fix(session): match scanned transcript prefix on resume
senamakel Sep 24, 2026
898622c
fix(cache): shift declared tiers for dynamic prompts
senamakel Sep 24, 2026
409796c
fix(session): retain stored boundary across repeated resume
senamakel Sep 24, 2026
091eff9
fix(cache): shift structured prompt declarations
senamakel Sep 24, 2026
ff80d94
feat(session): persist frozen prefix count per generation
senamakel Sep 24, 2026
1eac3db
test(cache): verify frozen tiers and reject mismatched sessions
senamakel Sep 24, 2026
6cd079c
fix(session): retain adopted frozen prefix boundary
senamakel Sep 24, 2026
25f0f91
fix(cache): carry frozen tiers into each harness invocation
senamakel Sep 24, 2026
094919a
fix(session): rebind cached prefix length to write target
senamakel Sep 24, 2026
f907fae
fix(cache): distinguish provider route from response identity
senamakel Sep 24, 2026
c534f9d
fix(cache): keep zero-prefix summaries volatile
senamakel Sep 24, 2026
9b73b3f
fix(session): validate exact transcript identity
senamakel Sep 24, 2026
c9c25fa
fix(cache): mark zero-prefix requests before compaction
senamakel Sep 24, 2026
a2c5151
fix(cache): promote stable prepends from empty marker
senamakel Sep 24, 2026
7fd7ac7
fix(cache): promote tools added after empty-prefix marker
senamakel Sep 24, 2026
a982005
fix(cache): preserve tools across zero-prefix prepends and guard cust…
senamakel Sep 24, 2026
1abc7f6
test(cache): cover system edits and preserve tail append reuse
senamakel Sep 24, 2026
0352890
fix(cache): fingerprint stable prepends before dispatch
senamakel Sep 24, 2026
8500fbf
fix(cache): report only stable-prefix changes in public events
senamakel Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 50 additions & 14 deletions crates/tinyagents-harness/src/agent_loop/run_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,16 +558,14 @@ impl<State: Send + Sync, Ctx: Send + Sync> AgentHarness<State, Ctx> {
// an explicit stable prefix, and the system instructions plus the
// name-sorted tool schemas are stable for this whole run.
status.mark_running(HarnessPhase::BuildingRequest);
let system_end = messages
.iter()
.take_while(|message| matches!(message, Message::System(_)))
.count();
let system_end = cacheable_system_prefix_end(messages, ctx.frozen_system_prefix_len);
let mut prompt = crate::prompt::PromptBuilder::new();
prompt.push_system_messages(&messages[..system_end]);
if !tool_schemas.is_empty() {
prompt.push_tools_segment("tools", tool_schemas.clone());
}
let mut request = prompt.build(messages[system_end..].to_vec());
mark_empty_frozen_prefix(&mut request, ctx.frozen_system_prefix_len);
// Provider adapters that maintain an external conversation (for
// example Claude Code's resumable CLI session) need the caller's
// logical thread id, not a hash of prompt text. Carry the harness
Expand Down Expand Up @@ -761,11 +759,9 @@ impl<State: Send + Sync, Ctx: Send + Sync> AgentHarness<State, Ctx> {
crate::structured::default_prompted_template().to_string()
});
let schema_text = serde_json::to_string_pretty(&schema).unwrap_or_default();
request.messages.insert(
0,
Message::system(format!(
"{instructions}\n\nJSON Schema for `{name}`:\n{schema_text}"
)),
crate::cache::prepend_system_message(
&mut request,
format!("{instructions}\n\nJSON Schema for `{name}`:\n{schema_text}"),
);
Some((StructuredStrategy::Prompted { template }, name, schema))
}
Expand Down Expand Up @@ -872,11 +868,11 @@ impl<State: Send + Sync, Ctx: Send + Sync> AgentHarness<State, Ctx> {
});
let schema_text =
serde_json::to_string_pretty(&schema).unwrap_or_default();
request.messages.insert(
0,
Message::system(format!(
crate::cache::prepend_system_message(
&mut request,
format!(
"{instructions}\n\nJSON Schema for `{name}`:\n{schema_text}"
)),
),
);
}
// `for_profile` never returns `ToolCallUnion`;
Expand Down Expand Up @@ -2024,6 +2020,38 @@ impl<State: Send + Sync, Ctx: Send + Sync> AgentHarness<State, Ctx> {
}
}

/// Use the frozen boundary supplied by a durable session when rebuilding a
/// model request on the next harness invocation. A later System summary is
/// model-visible history, not another stable prompt tier.
pub(super) fn cacheable_system_prefix_end(
messages: &[Message],
frozen_system_prefix_len: Option<usize>,
) -> usize {
let leading_system = messages
.iter()
.take_while(|message| matches!(message, Message::System(_)))
.count();
frozen_system_prefix_len.map_or(leading_system, |count| count.min(leading_system))
}

/// Prevent the dispatch refresh from inferring a newly leading System
/// summary as stable when a session explicitly froze zero messages. An empty
/// annotation means "infer from roles" to the harness, so retain an explicit
/// noncacheable marker in this zero-prefix, no-tools case even if the summary
/// has not been inserted by a later middleware yet.
pub(super) fn mark_empty_frozen_prefix(
request: &mut ModelRequest,
frozen_system_prefix_len: Option<usize>,
) {
if frozen_system_prefix_len == Some(0) && request.cache_segments.is_empty() {
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
request.cache_segments.push(PromptSegment {
Comment thread
senamakel marked this conversation as resolved.
id: crate::cache::VOLATILE_SYSTEM_HISTORY_SEGMENT_ID.into(),
role: SegmentRole::Volatile,
cacheable: false,
});
}
}

/// Refreshes the harness-owned stable-prefix annotation at model-call dispatch.
///
/// Lifecycle and wrap middleware may add or rewrite leading system messages.
Expand All @@ -2032,11 +2060,19 @@ impl<State: Send + Sync, Ctx: Send + Sync> AgentHarness<State, Ctx> {
/// middleware layer has delegated to the innermost call. Rebuilding that
/// annotation there keeps cache routing tied to the bytes sent to the provider.
pub(super) fn refresh_prompt_cache_fingerprint(request: &mut ModelRequest) {
let system_end = request
crate::cache::promote_tools_after_zero_prefix_marker(request);
let leading_system_end = request
.messages
.iter()
.take_while(|message| matches!(message, Message::System(_)))
.count();
// An explicit canonical layout names the cacheable system messages. A
// compaction summary can be another leading System message without being
// part of that frozen prefix; promoting it here re-rolls the provider's
// prompt_cache_key on every compaction. With no explicit boundary, keep
// the existing conservative leading-System behavior.
let system_end =
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
crate::cache::declared_system_prefix_len(request).unwrap_or(leading_system_end);
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
let mut expected_layout = (0..system_end)
.map(|index| PromptSegment {
id: crate::prompt::system_segment_id(index),
Expand Down
Loading
Loading