Skip to content

Commit c0d5f81

Browse files
committed
feat(plugin): configurable commit cluster trigger + heuristic gate fix + dashboard improvements
- Add commit_cluster_trigger config (enabled: true, min_clusters: 3 by default) allowing users to tune or disable commit-cluster based historian triggering - Fix heuristics blocked when ctx_reduce_enabled=false (no pending ops meant tool cleanup and reasoning clearing never ran on execute passes) - Update heuristic log message from misleading 'pending_ops_execute' to 'scheduler_execute' - Fix dashboard cache diagnostics labeling old events as 'First message (new session)' by checking whether earlier assistant messages exist in the DB - Add commit cluster trigger toggle + min clusters input to dashboard config editor - Document commit_cluster_trigger in CONFIGURATION.md with explanation of what commit clusters are
1 parent f195cfc commit c0d5f81

File tree

11 files changed

+140
-7
lines changed

11 files changed

+140
-7
lines changed

CONFIGURATION.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ Higher-tier models with longer cache windows benefit from a longer TTL. Setting
4949
| `compartment_token_budget` | `number` | `20000` | Token budget for historian input chunks. |
5050
| `historian_timeout_ms` | `number` | `300000` | Timeout per historian call (ms). |
5151
| `history_budget_percentage` | `number` (0–1) | `0.15` | Fraction of usable context reserved for the history block. Triggers compression when exceeded. |
52+
| `commit_cluster_trigger` | `object` | See below | Controls the commit-cluster historian trigger. |
53+
54+
### `commit_cluster_trigger`
55+
56+
A **commit cluster** is a distinct work phase where the agent made one or more git commits, separated from other commit clusters by meaningful user turns. For example, if the agent commits a fix, then the user asks a new question, and the agent commits another change — that's 2 commit clusters. This heuristic detects natural work-unit boundaries and fires historian to compartmentalize them, even when context pressure is low.
57+
58+
```jsonc
59+
{
60+
"commit_cluster_trigger": {
61+
"enabled": true, // default: true
62+
"min_clusters": 3 // default: 3, minimum: 1
63+
}
64+
}
65+
```
66+
67+
| Field | Type | Default | Description |
68+
|-------|------|---------|-------------|
69+
| `enabled` | `boolean` | `true` | Enable commit-cluster based historian triggering. |
70+
| `min_clusters` | `number` | `3` | Minimum number of commit clusters in the unsummarized tail before historian fires. The tail must also contain at least `compartment_token_budget` tokens. |
71+
72+
Set `enabled: false` to disable this trigger entirely and rely only on pressure-based and tail-size triggers for historian.
5273

5374
---
5475

packages/dashboard/src-tauri/src/db.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,41 @@ fn load_raw_db_cache_events(limit: usize, since_timestamp: Option<i64>) -> Resul
552552

553553
fn build_db_cache_events(rows: Vec<RawDbCacheEvent>, enrich_causes: bool) -> Vec<DbCacheEvent> {
554554
let log_cause_candidates = if enrich_causes { build_log_cause_candidates() } else { Vec::new() };
555+
556+
// Build a map of earliest timestamp per session in our window so we can
557+
// detect whether an event is truly the session's first message vs just
558+
// the oldest event in the current 200-event window.
559+
let mut earliest_ts_in_window: HashMap<String, i64> = HashMap::new();
560+
for row in &rows {
561+
earliest_ts_in_window
562+
.entry(row.session_id.clone())
563+
.and_modify(|ts| { if row.timestamp < *ts { *ts = row.timestamp; } })
564+
.or_insert(row.timestamp);
565+
}
566+
567+
// Check which sessions truly have their first-ever assistant message in our window
568+
// by querying whether an earlier assistant message exists in the DB.
569+
let true_first_sessions: HashSet<String> = if let Some(opencode_db_path) = resolve_opencode_db_path() {
570+
if let Ok(conn) = open_readonly(&opencode_db_path) {
571+
earliest_ts_in_window.iter().filter(|(session_id, &earliest_ts)| {
572+
// If there's any assistant message with tokens BEFORE our earliest, it's not the first
573+
let has_earlier: bool = conn.query_row(
574+
"SELECT EXISTS(SELECT 1 FROM message WHERE session_id = ?1
575+
AND json_extract(data, '$.role') = 'assistant'
576+
AND COALESCE(CAST(json_extract(data, '$.tokens.total') AS INTEGER), 0) > 0
577+
AND time_created < ?2)",
578+
rusqlite::params![session_id, earliest_ts],
579+
|row| row.get(0),
580+
).unwrap_or(false);
581+
!has_earlier
582+
}).map(|(sid, _)| sid.clone()).collect()
583+
} else {
584+
HashSet::new()
585+
}
586+
} else {
587+
HashSet::new()
588+
};
589+
555590
let mut seen_sessions = HashSet::new();
556591
let mut chronological = Vec::with_capacity(rows.len());
557592

@@ -564,7 +599,8 @@ fn build_db_cache_events(rows: Vec<RawDbCacheEvent>, enrich_causes: bool) -> Vec
564599
};
565600

566601
let is_first_session_event = seen_sessions.insert(row.session_id.clone());
567-
let (severity, cause) = if is_first_session_event {
602+
let is_truly_first = is_first_session_event && true_first_sessions.contains(&row.session_id);
603+
let (severity, cause) = if is_truly_first {
568604
(
569605
"info".to_string(),
570606
Some("First message (new session)".to_string()),

packages/dashboard/src/components/ConfigEditor/ConfigEditor.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,53 @@ function ConfigForm(props: {
512512
{/* Right: Settings sliders */}
513513
<div class="config-card-content">
514514
<For each={fields}>{renderField}</For>
515+
516+
{/* Commit Cluster Trigger */}
517+
{(() => {
518+
const commitCluster = () => (getNestedValue(formData(), "commit_cluster_trigger") as { enabled?: boolean; min_clusters?: number } | undefined) ?? {};
519+
const enabled = () => commitCluster().enabled ?? true;
520+
const minClusters = () => commitCluster().min_clusters ?? 3;
521+
return (
522+
<>
523+
<div class="config-field">
524+
<div class="config-field-header">
525+
<label class="config-field-label">Commit Cluster Trigger</label>
526+
<span class="config-field-key">commit_cluster_trigger.enabled</span>
527+
</div>
528+
<span class="config-field-desc">Fire historian when enough git commit clusters accumulate in the unsummarized conversation tail. A commit cluster is a distinct work phase where the agent made git commits, separated by meaningful user turns.</span>
529+
<label class="toggle-switch">
530+
<input
531+
type="checkbox"
532+
checked={enabled()}
533+
onChange={(e) => handleFieldChange("commit_cluster_trigger", { ...commitCluster(), enabled: e.currentTarget.checked })}
534+
/>
535+
<span class="toggle-slider" />
536+
<span class="toggle-label">{enabled() ? "Enabled" : "Disabled"}</span>
537+
</label>
538+
</div>
539+
540+
<Show when={enabled()}>
541+
<div class="config-field">
542+
<div class="config-field-header">
543+
<label class="config-field-label">Min Clusters</label>
544+
<span class="config-field-key">commit_cluster_trigger.min_clusters</span>
545+
</div>
546+
<span class="config-field-desc">Minimum number of commit clusters required to trigger historian</span>
547+
<input
548+
class="config-input"
549+
type="number"
550+
min={1}
551+
value={minClusters()}
552+
onInput={(e) => {
553+
const v = e.currentTarget.value;
554+
handleFieldChange("commit_cluster_trigger", { ...commitCluster(), min_clusters: v ? Math.max(1, Number(v)) : 3 });
555+
}}
556+
/>
557+
</div>
558+
</Show>
559+
</>
560+
);
561+
})()}
515562
</div>
516563
</div>
517564
) : (

packages/plugin/src/config/schema/magic-context.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ describe("MagicContextConfigSchema", () => {
5858
compartment_token_budget: 25_000,
5959
history_budget_percentage: 0.2,
6060
historian_timeout_ms: 360_000,
61+
commit_cluster_trigger: {
62+
enabled: true,
63+
min_clusters: 3,
64+
},
6165
embedding: {
6266
provider: "openai-compatible",
6367
endpoint: "http://localhost:1234/v1",

packages/plugin/src/config/schema/magic-context.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ export interface MagicContextConfig {
120120
compartment_token_budget: number;
121121
history_budget_percentage: number;
122122
historian_timeout_ms: number;
123+
commit_cluster_trigger: {
124+
enabled: boolean;
125+
min_clusters: number;
126+
};
123127
embedding: EmbeddingConfig;
124128
memory: {
125129
enabled: boolean;
@@ -175,6 +179,15 @@ export const MagicContextConfigSchema = z
175179
.default(DEFAULT_HISTORY_BUDGET_PERCENTAGE),
176180
/** Timeout for each historian prompt call in milliseconds (default: 300000) */
177181
historian_timeout_ms: z.number().min(60_000).default(DEFAULT_HISTORIAN_TIMEOUT_MS),
182+
/** Commit-cluster trigger: fire historian when enough commit clusters accumulate in the unsummarized tail */
183+
commit_cluster_trigger: z
184+
.object({
185+
/** Enable commit-cluster based historian triggering (default: true) */
186+
enabled: z.boolean().default(true),
187+
/** Minimum commit clusters required to trigger historian (min: 1, default: 3) */
188+
min_clusters: z.number().min(1).default(3),
189+
})
190+
.default({ enabled: true, min_clusters: 3 }),
178191
/** Embedding provider configuration */
179192
embedding: EmbeddingConfigSchema.default({
180193
provider: "local",

packages/plugin/src/hooks/magic-context/command-handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ export function createMagicContextCommandHandler(deps: {
183183
nudgeIntervalTokens?: number;
184184
executeThresholdPercentage?: number | { default: number; [modelKey: string]: number };
185185
historyBudgetPercentage?: number;
186+
commitClusterTrigger?: { enabled: boolean; min_clusters: number };
186187
getLiveModelKey?: (sessionId: string) => string | undefined;
187188
onFlush?: (sessionId: string) => void;
188189
executeRecomp?: (sessionId: string) => Promise<string>;
@@ -255,6 +256,7 @@ export function createMagicContextCommandHandler(deps: {
255256
deps.executeThresholdPercentage,
256257
liveModelKey,
257258
deps.historyBudgetPercentage,
259+
deps.commitClusterTrigger,
258260
);
259261
result += result ? `\n\n${statusOutput}` : statusOutput;
260262
}

packages/plugin/src/hooks/magic-context/compartment-trigger.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const PROACTIVE_TRIGGER_OFFSET_PERCENTAGE = 2;
1515
const POST_DROP_TARGET_RATIO = 0.75;
1616
const MIN_PROACTIVE_TAIL_TOKEN_ESTIMATE = 6_000;
1717
const MIN_PROACTIVE_TAIL_MESSAGE_COUNT = 12;
18-
const MIN_COMMIT_CLUSTERS_FOR_TRIGGER = 2;
18+
const DEFAULT_MIN_COMMIT_CLUSTERS_FOR_TRIGGER = 3;
1919
const TAIL_SIZE_TRIGGER_MULTIPLIER = 3;
2020
const FORCE_COMPARTMENT_PERCENTAGE = 80;
2121
const BLOCK_UNTIL_DONE_PERCENTAGE = 95;
@@ -182,6 +182,7 @@ export function checkCompartmentTrigger(
182182
autoDropToolAge?: number,
183183
protectedTagCount?: number,
184184
clearReasoningAge?: number,
185+
commitClusterTrigger?: { enabled: boolean; min_clusters: number },
185186
): CompartmentTriggerResult {
186187
if (sessionMeta.compartmentInProgress) {
187188
return { shouldFire: false };
@@ -223,14 +224,18 @@ export function checkCompartmentTrigger(
223224
return { shouldFire: true, reason: "force_80" };
224225
}
225226

226-
// Commit-cluster trigger: 2+ distinct work phases with commits, enough token volume
227+
// Commit-cluster trigger: N+ distinct work phases with commits, enough token volume
228+
const clusterEnabled = commitClusterTrigger?.enabled ?? true;
229+
const minClusters =
230+
commitClusterTrigger?.min_clusters ?? DEFAULT_MIN_COMMIT_CLUSTERS_FOR_TRIGGER;
227231
if (
228-
tailInfo.commitClusterCount >= MIN_COMMIT_CLUSTERS_FOR_TRIGGER &&
232+
clusterEnabled &&
233+
tailInfo.commitClusterCount >= minClusters &&
229234
tailInfo.tokenEstimate >= compartmentTokenBudget
230235
) {
231236
sessionLog(
232237
sessionId,
233-
`compartment trigger: commit-cluster fire — ${tailInfo.commitClusterCount} clusters, ~${tailInfo.tokenEstimate} tokens in eligible prefix`,
238+
`compartment trigger: commit-cluster fire — ${tailInfo.commitClusterCount} clusters (min=${minClusters}), ~${tailInfo.tokenEstimate} tokens in eligible prefix`,
234239
);
235240
return { shouldFire: true, reason: "commit_clusters" };
236241
}

packages/plugin/src/hooks/magic-context/event-handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface EventHandlerDeps {
6262
execute_threshold_percentage?: number | { default: number; [modelKey: string]: number };
6363
cache_ttl: CacheTtlConfig;
6464
modelContextLimitsCache?: Map<string, number>;
65+
commit_cluster_trigger?: { enabled: boolean; min_clusters: number };
6566
};
6667
tagger: Tagger;
6768
db: ReturnType<typeof import("../../features/magic-context/storage").openDatabase>;
@@ -292,6 +293,7 @@ export function createEventHandler(deps: EventHandlerDeps) {
292293
deps.config.auto_drop_tool_age ?? 100,
293294
deps.config.protected_tags,
294295
deps.config.clear_reasoning_age ?? 50,
296+
deps.config.commit_cluster_trigger,
295297
);
296298

297299
if (triggerResult.shouldFire) {

packages/plugin/src/hooks/magic-context/execute-status.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function executeStatus(
3333
| { default: number; [modelKey: string]: number } = DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE,
3434
liveModelKey?: string,
3535
historyBudgetPercentage?: number,
36+
commitClusterTrigger?: { enabled: boolean; min_clusters: number },
3637
): string {
3738
const executeThresholdPercentage = resolveExecuteThreshold(
3839
executeThresholdPercentageConfig,
@@ -123,7 +124,7 @@ export function executeStatus(
123124
`- Resolved context limit: ${contextLimit > 0 ? contextLimit.toLocaleString() : "unknown"}`,
124125
`- Proactive compartment evaluation: ${proactiveCompartmentTrigger}%`,
125126
`- Post-drop target for historian: ${(executeThresholdPercentage * POST_DROP_TARGET_RATIO).toFixed(0)}% (${executeThresholdPercentage}% * ${POST_DROP_TARGET_RATIO})`,
126-
`- Historian also fires on: 2+ commit clusters with sufficient tokens, or tail > ${3}x compartment budget`,
127+
`- Commit cluster trigger: ${commitClusterTrigger?.enabled !== false ? `enabled (min ${commitClusterTrigger?.min_clusters ?? 3} clusters)` : "disabled"}, tail-size trigger: > 3x compartment budget`,
127128
);
128129
}
129130

packages/plugin/src/hooks/magic-context/hook.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface MagicContextDeps {
7575
};
7676
sidekick?: SidekickConfig;
7777
dreamer?: DreamerConfig;
78+
commit_cluster_trigger?: { enabled: boolean; min_clusters: number };
7879
};
7980
}
8081

@@ -242,6 +243,7 @@ export function createMagicContextHook(deps: MagicContextDeps) {
242243
nudgeIntervalTokens: deps.config.nudge_interval_tokens ?? DEFAULT_NUDGE_INTERVAL_TOKENS,
243244
executeThresholdPercentage: deps.config.execute_threshold_percentage ?? 65,
244245
historyBudgetPercentage: deps.config.history_budget_percentage,
246+
commitClusterTrigger: deps.config.commit_cluster_trigger,
245247
getLiveModelKey: (sessionId) => {
246248
const model = liveModelBySession.get(sessionId);
247249
return model ? `${model.providerID}/${model.modelID}` : undefined;

0 commit comments

Comments
 (0)