Merge branch 'main' into commands-list-clean

This commit is contained in:
Luke
2026-01-08 16:25:56 -05:00
committed by GitHub
7 changed files with 103 additions and 14 deletions

View File

@@ -2,6 +2,7 @@
## 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/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

View File

@@ -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:
- You want to reduce LLM context growth from tool outputs
- You are tuning agent.contextPruning
---
# 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
- 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]" }`
## Examples
Minimal (adaptive):
Default (adaptive):
```json5
{
agent: {
@@ -68,6 +68,15 @@ Minimal (adaptive):
}
```
To disable:
```json5
{
agent: {
contextPruning: { mode: "off" }
}
}
```
Aggressive:
```json5
{

View File

@@ -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.
- Clawdbot does **not** read legacy Pi/Tau session folders.
## Session pruning (optional)
Clawdbot can trim **old tool results** from the in-memory context right before LLM calls (opt-in).
## Session pruning
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).
## Mapping transports → session keys

View File

@@ -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.
It does **not** modify the session history on disk (`*.jsonl` remains complete).
@@ -1025,7 +1025,7 @@ Notes / current limitations:
- If the session doesnt 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`).
Example (minimal):
Default (adaptive):
```json5
{
agent: {
@@ -1036,6 +1036,17 @@ Example (minimal):
}
```
To disable:
```json5
{
agent: {
contextPruning: {
mode: "off"
}
}
}
```
Defaults (when `mode` is `"adaptive"` or `"aggressive"`):
- `keepLastAssistants`: `3`
- `softTrimRatio`: `0.3` (adaptive only)

View File

@@ -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) => {
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
@@ -295,7 +295,7 @@ describe("config identity defaults", () => {
expect(cfg.routing?.groupChat?.mentionPatterns).toEqual([
"\\b@?Samantha\\b",
]);
expect(cfg.agent).toBeUndefined();
expect(cfg.agent?.contextPruning?.mode).toBe("adaptive");
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", () => {
let previousHome: string | undefined;

View File

@@ -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() {
defaultWarnState = { warned: false };
}

View File

@@ -17,6 +17,7 @@ import {
applyLoggingDefaults,
applyMessageDefaults,
applyModelDefaults,
applyContextPruningDefaults,
applySessionDefaults,
applyTalkApiKey,
} from "./defaults.js";
@@ -135,10 +136,12 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
return {};
}
const cfg = applyModelDefaults(
applySessionDefaults(
applyLoggingDefaults(
applyMessageDefaults(
applyIdentityDefaults(validated.data as ClawdbotConfig),
applyContextPruningDefaults(
applySessionDefaults(
applyLoggingDefaults(
applyMessageDefaults(
applyIdentityDefaults(validated.data as ClawdbotConfig),
),
),
),
),
@@ -182,7 +185,11 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
const exists = deps.fs.existsSync(configPath);
if (!exists) {
const config = applyTalkApiKey(
applyModelDefaults(applySessionDefaults(applyMessageDefaults({}))),
applyModelDefaults(
applyContextPruningDefaults(
applySessionDefaults(applyMessageDefaults({})),
),
),
);
const legacyIssues: LegacyConfigIssue[] = [];
return {