feat: enable adaptive context pruning by default
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Agent: enable adaptive context pruning by default for tool-result trimming.
|
||||||
- Doctor: check config/state permissions and offer to tighten them. — thanks @steipete
|
- Doctor: check config/state permissions and offer to tighten them. — thanks @steipete
|
||||||
- Doctor/Daemon: audit supervisor configs, add --repair/--force flows, surface service config audits in daemon status, and document user vs system services. — thanks @steipete
|
- Doctor/Daemon: audit supervisor configs, add --repair/--force flows, surface service config audits in daemon status, and document user vs system services. — thanks @steipete
|
||||||
- Daemon: align generated systemd unit with docs for network-online + restart delay. (#479) — thanks @azade-c
|
- Daemon: align generated systemd unit with docs for network-online + restart delay. (#479) — thanks @azade-c
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
summary: "Session pruning: opt-in tool-result trimming to reduce context bloat"
|
summary: "Session pruning: tool-result trimming to reduce context bloat"
|
||||||
read_when:
|
read_when:
|
||||||
- You want to reduce LLM context growth from tool outputs
|
- You want to reduce LLM context growth from tool outputs
|
||||||
- You are tuning agent.contextPruning
|
- You are tuning agent.contextPruning
|
||||||
---
|
---
|
||||||
# Session Pruning
|
# Session Pruning
|
||||||
|
|
||||||
Session pruning trims **old tool results** from the in-memory context right before each LLM call. It is **opt-in** and does **not** rewrite the on-disk session history (`*.jsonl`).
|
Session pruning trims **old tool results** from the in-memory context right before each LLM call. It does **not** rewrite the on-disk session history (`*.jsonl`).
|
||||||
|
|
||||||
## When it runs
|
## When it runs
|
||||||
- Before each LLM request (context hook).
|
- Before each LLM request (context hook).
|
||||||
@@ -59,7 +59,7 @@ Pruning uses an estimated context window (chars ≈ tokens × 4). The window siz
|
|||||||
- `hardClear`: `{ enabled: true, placeholder: "[Old tool result content cleared]" }`
|
- `hardClear`: `{ enabled: true, placeholder: "[Old tool result content cleared]" }`
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
Minimal (adaptive):
|
Default (adaptive):
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
agent: {
|
agent: {
|
||||||
@@ -68,6 +68,15 @@ Minimal (adaptive):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To disable:
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agent: {
|
||||||
|
contextPruning: { mode: "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Aggressive:
|
Aggressive:
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ All session state is **owned by the gateway** (the “master” Clawdbot). UI cl
|
|||||||
- Group entries may include `displayName`, `provider`, `subject`, `room`, and `space` to label sessions in UIs.
|
- Group entries may include `displayName`, `provider`, `subject`, `room`, and `space` to label sessions in UIs.
|
||||||
- Clawdbot does **not** read legacy Pi/Tau session folders.
|
- Clawdbot does **not** read legacy Pi/Tau session folders.
|
||||||
|
|
||||||
## Session pruning (optional)
|
## Session pruning
|
||||||
Clawdbot can trim **old tool results** from the in-memory context right before LLM calls (opt-in).
|
Clawdbot trims **old tool results** from the in-memory context right before LLM calls by default.
|
||||||
This does **not** rewrite JSONL history. See [/concepts/session-pruning](/concepts/session-pruning).
|
This does **not** rewrite JSONL history. See [/concepts/session-pruning](/concepts/session-pruning).
|
||||||
|
|
||||||
## Mapping transports → session keys
|
## Mapping transports → session keys
|
||||||
|
|||||||
@@ -994,7 +994,7 @@ If you configure the same alias name (case-insensitive) yourself, your value win
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `agent.contextPruning` (opt-in tool-result pruning)
|
#### `agent.contextPruning` (tool-result pruning)
|
||||||
|
|
||||||
`agent.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM.
|
`agent.contextPruning` prunes **old tool results** from the in-memory context right before a request is sent to the LLM.
|
||||||
It does **not** modify the session history on disk (`*.jsonl` remains complete).
|
It does **not** modify the session history on disk (`*.jsonl` remains complete).
|
||||||
@@ -1025,7 +1025,7 @@ Notes / current limitations:
|
|||||||
- If the session doesn’t contain at least `keepLastAssistants` assistant messages yet, pruning is skipped.
|
- If the session doesn’t contain at least `keepLastAssistants` assistant messages yet, pruning is skipped.
|
||||||
- In `aggressive` mode, `hardClear.enabled` is ignored (eligible tool results are always replaced with `hardClear.placeholder`).
|
- In `aggressive` mode, `hardClear.enabled` is ignored (eligible tool results are always replaced with `hardClear.placeholder`).
|
||||||
|
|
||||||
Example (minimal):
|
Default (adaptive):
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
agent: {
|
agent: {
|
||||||
@@ -1036,6 +1036,17 @@ Example (minimal):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To disable:
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agent: {
|
||||||
|
contextPruning: {
|
||||||
|
mode: "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Defaults (when `mode` is `"adaptive"` or `"aggressive"`):
|
Defaults (when `mode` is `"adaptive"` or `"aggressive"`):
|
||||||
- `keepLastAssistants`: `3`
|
- `keepLastAssistants`: `3`
|
||||||
- `softTrimRatio`: `0.3` (adaptive only)
|
- `softTrimRatio`: `0.3` (adaptive only)
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ describe("config identity defaults", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not synthesize agent/session when absent", async () => {
|
it("does not synthesize session when absent", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const configDir = path.join(home, ".clawdbot");
|
const configDir = path.join(home, ".clawdbot");
|
||||||
await fs.mkdir(configDir, { recursive: true });
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
@@ -295,7 +295,7 @@ describe("config identity defaults", () => {
|
|||||||
expect(cfg.routing?.groupChat?.mentionPatterns).toEqual([
|
expect(cfg.routing?.groupChat?.mentionPatterns).toEqual([
|
||||||
"\\b@?Samantha\\b",
|
"\\b@?Samantha\\b",
|
||||||
]);
|
]);
|
||||||
expect(cfg.agent).toBeUndefined();
|
expect(cfg.agent?.contextPruning?.mode).toBe("adaptive");
|
||||||
expect(cfg.session).toBeUndefined();
|
expect(cfg.session).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -327,6 +327,48 @@ describe("config identity defaults", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("config pruning defaults", () => {
|
||||||
|
it("defaults contextPruning mode to adaptive", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const configDir = path.join(home, ".clawdbot");
|
||||||
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, "clawdbot.json"),
|
||||||
|
JSON.stringify({ agent: {} }, null, 2),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const { loadConfig } = await import("./config.js");
|
||||||
|
const cfg = loadConfig();
|
||||||
|
|
||||||
|
expect(cfg.agent?.contextPruning?.mode).toBe("adaptive");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not override explicit contextPruning mode", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const configDir = path.join(home, ".clawdbot");
|
||||||
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, "clawdbot.json"),
|
||||||
|
JSON.stringify(
|
||||||
|
{ agent: { contextPruning: { mode: "off" } } },
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const { loadConfig } = await import("./config.js");
|
||||||
|
const cfg = loadConfig();
|
||||||
|
|
||||||
|
expect(cfg.agent?.contextPruning?.mode).toBe("off");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("config discord", () => {
|
describe("config discord", () => {
|
||||||
let previousHome: string | undefined;
|
let previousHome: string | undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,25 @@ export function applyLoggingDefaults(cfg: ClawdbotConfig): ClawdbotConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyContextPruningDefaults(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
): ClawdbotConfig {
|
||||||
|
const agent = cfg.agent;
|
||||||
|
const contextPruning = agent?.contextPruning;
|
||||||
|
if (contextPruning?.mode) return cfg;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
agent: {
|
||||||
|
...agent,
|
||||||
|
contextPruning: {
|
||||||
|
...contextPruning,
|
||||||
|
mode: "adaptive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function resetSessionDefaultsWarningForTests() {
|
export function resetSessionDefaultsWarningForTests() {
|
||||||
defaultWarnState = { warned: false };
|
defaultWarnState = { warned: false };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
applyLoggingDefaults,
|
applyLoggingDefaults,
|
||||||
applyMessageDefaults,
|
applyMessageDefaults,
|
||||||
applyModelDefaults,
|
applyModelDefaults,
|
||||||
|
applyContextPruningDefaults,
|
||||||
applySessionDefaults,
|
applySessionDefaults,
|
||||||
applyTalkApiKey,
|
applyTalkApiKey,
|
||||||
} from "./defaults.js";
|
} from "./defaults.js";
|
||||||
@@ -135,10 +136,12 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const cfg = applyModelDefaults(
|
const cfg = applyModelDefaults(
|
||||||
applySessionDefaults(
|
applyContextPruningDefaults(
|
||||||
applyLoggingDefaults(
|
applySessionDefaults(
|
||||||
applyMessageDefaults(
|
applyLoggingDefaults(
|
||||||
applyIdentityDefaults(validated.data as ClawdbotConfig),
|
applyMessageDefaults(
|
||||||
|
applyIdentityDefaults(validated.data as ClawdbotConfig),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -182,7 +185,11 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
const exists = deps.fs.existsSync(configPath);
|
const exists = deps.fs.existsSync(configPath);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
const config = applyTalkApiKey(
|
const config = applyTalkApiKey(
|
||||||
applyModelDefaults(applySessionDefaults(applyMessageDefaults({}))),
|
applyModelDefaults(
|
||||||
|
applyContextPruningDefaults(
|
||||||
|
applySessionDefaults(applyMessageDefaults({})),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
const legacyIssues: LegacyConfigIssue[] = [];
|
const legacyIssues: LegacyConfigIssue[] = [];
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user