Merge branch 'session-todo-tools' into develop

# Conflicts:
#	tickets/session-todo.md
This commit is contained in:
2026-05-03 21:50:30 +09:00
12 changed files with 944 additions and 94 deletions
+7 -1
View File
@@ -133,6 +133,7 @@ impl PodController {
// Stashed during tool registration below so we can attach a
// `PodFsView` to the shared state once the latter exists.
let fs_for_view: tools::ScopedFs;
let task_store = pod.task_store();
let scope_change_sink = pod.scope_change_sink();
@@ -266,7 +267,12 @@ impl PodController {
// query — keep a clone for the FS view we attach below,
// since the tools consume `fs` itself.
fs_for_view = fs.clone();
worker.register_tools(tools::builtin_tools(fs, tracker.clone(), bash_output_dir));
worker.register_tools(tools::builtin_tools(
fs,
tracker.clone(),
task_store.clone(),
bash_output_dir,
));
// Memory subsystem opt-in. When `[memory]` is present in
// the manifest, register the memory-specific Read/Write/Edit
+1 -1
View File
@@ -150,7 +150,7 @@ impl Interceptor for PodInterceptor {
// Internal mechanism: between-requests compaction trigger (safety net).
if let Some(state) = self.compact_state.as_ref() {
if !state.is_disabled() {
if !state.is_disabled() && !state.just_compacted() {
let current = current_tokens.unwrap_or(0);
if state.exceeds_request(current) {
info!(
+63 -4
View File
@@ -95,6 +95,11 @@ pub struct Pod<C: LlmClient, St: Store> {
/// tools so that Pod-owned operations (e.g. compaction) can consult
/// the recency of touched files.
tracker: Option<tools::Tracker>,
/// Session-lifetime task store from the builtin `tools` crate. Shared by
/// TaskCreate / TaskUpdate / TaskList / TaskGet and preserved across
/// compaction by keeping the same handle while the Worker history is
/// replaced. Restored Pods reconstruct it by replaying Task* tool calls.
task_store: tools::TaskStore,
/// Parsed system-prompt template awaiting first-turn materialisation.
/// `Some` until `ensure_system_prompt_materialized` renders it once,
/// then `None` forever — including after compaction.
@@ -215,6 +220,7 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
metrics_tracker: Arc::new(crate::compact::metrics_tracker::MetricsTracker::new()),
usage_history: Arc::new(Mutex::new(Vec::<UsageRecord>::new())),
tracker: None,
task_store: tools::TaskStore::new(),
system_prompt_template: None,
alerter: None,
event_tx: None,
@@ -479,6 +485,18 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
self.tracker = Some(tracker);
}
/// Attach the session-scoped TaskStore from the builtin `tools` crate.
/// Called by the Controller before registering builtin tools so the Pod
/// and Worker share one store.
pub fn attach_task_store(&mut self, task_store: tools::TaskStore) {
self.task_store = task_store;
}
/// Shared TaskStore handle.
pub fn task_store(&self) -> tools::TaskStore {
self.task_store.clone()
}
/// The attached session-scoped file-operation tracker, if any.
pub fn tracker(&self) -> Option<&tools::Tracker> {
self.tracker.as_ref()
@@ -1314,8 +1332,14 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
.unwrap_or_default();
// Input text fed to the compact worker. Includes the default
// references and the (pruned) conversation text.
let summary_input = build_summary_input(&items_to_summarise, &default_refs);
// references, current TaskStore snapshot, and the (pruned)
// conversation text.
let task_snapshot_text = self.task_store.snapshot_text();
let summary_input = build_summary_input(
&items_to_summarise,
&default_refs,
Some(task_snapshot_text.as_str()),
);
// Worker-side state collected by the compact worker's tool calls.
let ctx = Arc::new(std::sync::Mutex::new(CompactWorkerContext::with_budget(
@@ -1430,9 +1454,15 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
.filter(|i| i.is_user_message())
.count();
// Build new history: [summary, ...auto-read, references, ...retained].
// Build new history: [summary, ...auto-read, references, ...retained, task snapshot, TaskList synthetic call/result].
// The TaskStore snapshot trails the retained items so that, on resume,
// `replay_history` walks any pre-compact Task* calls preserved verbatim
// in retained_items first and the trailing snapshot's `replace_with`
// is the final word — pre-compact `TaskCreate` calls cannot leak as
// duplicate entries.
let mut new_history = Vec::with_capacity(
1 + auto_read_messages.len()
+ 3
+ reference_message.is_some() as usize
+ retained_items.len(),
);
@@ -1444,6 +1474,17 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
new_history.push(msg);
}
new_history.extend(retained_items);
new_history.push(Item::system_message(format!(
"[Session TaskStore snapshot]\n\n{task_snapshot_text}\n\n\
This is the complete session task list preserved across compaction. \
The following TaskList tool result presents the same state through the tool lane."
)));
new_history.push(Item::tool_call("compact-tasklist", "TaskList", "{}"));
new_history.push(Item::tool_result_with_content(
"compact-tasklist",
tools::task::snapshot_overview(&self.task_store.list()),
task_snapshot_text.clone(),
));
// Persist as a new compacted session.
let old_session_id = self.session_id;
@@ -2038,6 +2079,7 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
metrics_tracker: Arc::new(crate::compact::metrics_tracker::MetricsTracker::new()),
usage_history: Arc::new(Mutex::new(Vec::new())),
tracker: None,
task_store: tools::TaskStore::new(),
system_prompt_template: common.system_prompt_template,
alerter: None,
event_tx: None,
@@ -2101,6 +2143,7 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
metrics_tracker: Arc::new(crate::compact::metrics_tracker::MetricsTracker::new()),
usage_history: Arc::new(Mutex::new(Vec::new())),
tracker: None,
task_store: tools::TaskStore::new(),
system_prompt_template: common.system_prompt_template,
alerter: None,
event_tx: None,
@@ -2211,6 +2254,7 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
}
let extract_pointer = memory::extract::fold_pointer(&state.extensions);
let task_store = tools::TaskStore::from_history(&state.history);
let mut pod = Self {
manifest,
@@ -2227,6 +2271,7 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
metrics_tracker: Arc::new(crate::compact::metrics_tracker::MetricsTracker::new()),
usage_history: Arc::new(Mutex::new(state.usage_history)),
tracker: None,
task_store,
// Restore replays the saved system_prompt verbatim — no
// template re-render on resume.
system_prompt_template: None,
@@ -2323,7 +2368,11 @@ impl From<WorkerResult> for PodRunResult {
/// Build the compact worker's input: default-reference instructions,
/// the list of recently-touched files, and the pruned conversation
/// produced by [`build_summary_prompt`].
fn build_summary_input(items: &[Item], default_refs: &[PathBuf]) -> String {
fn build_summary_input(
items: &[Item],
default_refs: &[PathBuf],
task_snapshot: Option<&str>,
) -> String {
let mut out = String::new();
out.push_str(
"Summarise the conversation below into a structured summary and nominate \
@@ -2343,6 +2392,16 @@ fn build_summary_input(items: &[Item], default_refs: &[PathBuf]) -> String {
}
out.push('\n');
}
if let Some(task_snapshot) = task_snapshot {
out.push_str(
"## Current Session TaskStore\n\
This is the full current task list. Use it as source material for the \
summary, especially active (pending/inprogress) tasks, but do not edit tasks \
from the compact worker.\n",
);
out.push_str(task_snapshot);
out.push_str("\n\n");
}
out.push_str("## Conversation\n");
out.push_str(&build_summary_prompt(items));
out.push_str("\n\nWhen you are done, call `write_summary` with the final 5-section text.");