Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 10 additions & 8 deletions interface/src/api/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5052,14 +5052,16 @@ export interface components {
transcript?: components["schemas"]["TranscriptStep"][] | null;
worker_type: string;
};
/** @description How much conversation history a worker receives. */
WorkerHistoryMode: "none" | "summary" | {
/**
* Format: int32
* @description Last N messages from the parent conversation.
*/
recent: number;
} | "full";
/**
* @description How much conversation history a worker receives.
*
* Workers fork their channel the way branches do: the difference between a
* worker and a branch is the tools it gets, not the context it has. `Clean`
* is the explicit opt-out for fan-out and mechanical tasks where the
* conversation is noise.
* @enum {string}
*/
WorkerHistoryMode: "fork" | "clean";
WorkerListItem: {
channel_id?: string | null;
channel_name?: string | null;
Expand Down
4 changes: 2 additions & 2 deletions interface/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export type ConversationSettings = {
response_mode?: "active" | "observe" | "mention_only";
save_attachments?: boolean;
worker_context?: {
history?: "none" | "summary" | "recent" | "full";
history?: "fork" | "clean";
memory?: "none" | "ambient" | "tools" | "full";
};
};
Expand All @@ -132,7 +132,7 @@ export type ConversationDefaultsResponse = {
memory: "full" | "ambient" | "off";
delegation: "standard" | "direct";
worker_context: {
history: "none" | "summary" | "recent" | "full";
history: "fork" | "clean";
memory: "none" | "ambient" | "tools" | "full";
};
available_models: Array<{
Expand Down
14 changes: 6 additions & 8 deletions interface/src/components/ConversationSettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const PRESETS: Array<{
settings: {
memory: "full",
delegation: "standard",
worker_context: {history: "none", memory: "none"},
worker_context: {history: "fork", memory: "none"},
},
},
{
Expand All @@ -43,7 +43,7 @@ const PRESETS: Array<{
settings: {
memory: "ambient",
delegation: "standard",
worker_context: {history: "none", memory: "none"},
worker_context: {history: "fork", memory: "none"},
},
},
{
Expand All @@ -54,7 +54,7 @@ const PRESETS: Array<{
settings: {
memory: "off",
delegation: "direct",
worker_context: {history: "recent", memory: "tools"},
worker_context: {history: "fork", memory: "tools"},
},
},
{
Expand All @@ -64,7 +64,7 @@ const PRESETS: Array<{
settings: {
memory: "off",
delegation: "standard",
worker_context: {history: "none", memory: "none"},
worker_context: {history: "clean", memory: "none"},
},
},
];
Expand Down Expand Up @@ -109,10 +109,8 @@ const RESPONSE_MODE_DESCRIPTIONS: Record<string, string> = {
};

const WORKER_HISTORY_OPTIONS = [
{value: "none", label: "None"},
{value: "summary", label: "Summary"},
{value: "recent", label: "Recent (20)"},
{value: "full", label: "Full"},
{value: "fork", label: "Full context"},
{value: "clean", label: "Task only"},
] as const;

const WORKER_MEMORY_OPTIONS = [
Expand Down
13 changes: 13 additions & 0 deletions prompts/en/memory_persistence.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ This is an automatic process triggered periodically during conversation. You are
- Ephemeral task chatter: retries, progress updates, temporary tool output,
or worker process chatter.

These exclusions govern memory extraction only. When this pass includes
skill reflection, worker transcripts are in scope there — the retries and
dead ends are the raw material a procedure is distilled from.

8. Verify stale memory before relying on it. If a recalled memory conflicts with
the newer conversation context, treat the older item as stale, avoid propagating
it as truth, and capture the latest truth via `updates` or `contradicts`.
Expand All @@ -75,6 +79,15 @@ it as truth, and capture the latest truth via `updates` or `contradicts`.
This session involved substantial work, so this pass also decides whether it produced a reusable procedure. Memory answers "who is the user and what is going on"; skills answer "how do we do this class of task here." A correction like "stop posting walls of text in Discord" is not a fact about the user — it's a standing procedure change, and it belongs in the skill governing that task class.

First decide whether anything is worth keeping. Ending with no skill writes is the common case and completely acceptable — but treat a session where the user corrected the agent's procedure as a strong write signal.
{% if reflection_worker_ids %}

These workers completed since the last reflection pass:
{% for worker_id in reflection_worker_ids %}
- `{{ worker_id }}`
{% endfor %}

Before deciding, pull their transcripts with `worker_inspect` — the lesson usually lives in what a worker tried, not in the summary it returned to the channel. A worker that succeeded after failed attempts — its own retries or a failed run listed above — is the strongest signal there is a procedure worth writing down. If the trail extends past this list, `worker_inspect` without an id shows older runs.
{% endif %}

If something is worth keeping, follow this order strictly:

Expand Down
2 changes: 1 addition & 1 deletion prompts/en/tools/spawn_worker_description.md.j2
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Spawn an independent worker process. By default uses a built-in agent with {tools} tools. The worker only sees the task description you provide — no conversation history.{opencode_note}
Spawn an independent worker process. By default uses a built-in agent with {tools} tools. {history_note}{opencode_note}

If OpenCode is enabled and the task is coding-heavy (multi-file edits, debugging, refactors), set `worker_type` to `"opencode"` and include a `directory`.
38 changes: 20 additions & 18 deletions src/agent/branch.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Branch: Fork context for thinking and delegation.

use crate::agent::compactor::estimate_history_tokens;
use crate::error::Result;
use crate::hooks::SpacebotHook;
use crate::llm::SpacebotModel;
Expand Down Expand Up @@ -107,7 +106,7 @@ impl Branch {

// Pre-flight context check: if the forked history is already large,
// compact before we even make the first LLM call.
self.maybe_compact_history();
self.maybe_compact_history(&prompt);

let routing = self.deps.runtime_config.routing.load();
let model_name = self
Expand Down Expand Up @@ -280,24 +279,27 @@ impl Branch {
Ok(conclusion)
}

/// Compact history if approaching context window limit.
/// Removes the oldest 50% of messages when usage exceeds 70%.
fn maybe_compact_history(&mut self) {
/// Compact history down to what the context window has left once this
/// branch's own preamble and prompt are accounted for, dropping the oldest
/// half of the messages at a time until it fits.
fn maybe_compact_history(&mut self, prompt: &str) {
let context_window = **self.deps.runtime_config.context_window.load();
let estimated = estimate_history_tokens(&self.history);
let usage = estimated as f32 / context_window as f32;

if usage < 0.70 {
return;
}

tracing::info!(
branch_id = %self.id,
usage = %format!("{:.0}%", usage * 100.0),
history_len = self.history.len(),
"branch pre-compacting history"
let prompt_tokens = crate::agent::compactor::estimate_text_tokens(&self.system_prompt)
+ crate::agent::compactor::estimate_text_tokens(prompt);
let removed = crate::agent::compactor::precompact_forked_history(
&mut self.history,
context_window,
0.50,
prompt_tokens,
);
self.compact_history(0.50);
if removed > 0 {
tracing::info!(
branch_id = %self.id,
removed,
history_len = self.history.len(),
"branch pre-compacted forked history"
);
}
}

/// Aggressive compaction for overflow recovery. Removes 75% of messages.
Expand Down
Loading
Loading