Skip to content

feat: per-agent compaction configuration overrides#60807

Closed
1052326311 wants to merge 5 commits into
openclaw:mainfrom
1052326311:feat/per-agent-compaction
Closed

feat: per-agent compaction configuration overrides#60807
1052326311 wants to merge 5 commits into
openclaw:mainfrom
1052326311:feat/per-agent-compaction

Conversation

@1052326311

Copy link
Copy Markdown
Contributor

Summary

Closes #57174

Allow individual agents to override global agents.defaults.compaction settings, enabling different compaction tuning per agent based on model context window size.

Motivation

Currently, compaction config is only available at agents.defaults level. In multi-agent setups with different model providers (e.g., Opus 1M, GLM-5 200K, GPT-4o 128K), a single reserveTokens value is suboptimal — models with larger context windows waste tokens on overly aggressive compaction, while smaller-context models may not compact aggressively enough.

Changes

Schema & Types

  • New: zod-schema.compaction.ts — shared CompactionConfigSchema (extracted from inline definition)
  • Updated: AgentEntrySchema — added optional compaction field
  • Updated: AgentConfig type — added compaction?: AgentCompactionConfig
  • Updated: AgentDefaultsSchema — now imports shared schema instead of inline definition

Runtime Resolution

  • New: resolveAgentCompaction(cfg, agentId) — shallow-merges agent-level compaction over defaults
  • New: resolveEffectiveCompaction(cfg, agentId) — public API in pi-settings.ts

Call Sites Updated (8 files)

File What Changed
pi-settings.ts applyPiCompactionSettingsFromConfig accepts optional agentId
pi-project-settings.ts createPreparedEmbeddedPiSettingsManager threads agentId
extensions.ts buildEmbeddedExtensionFactories resolves per-agent mode/safeguard config
compaction-safety-timeout.ts resolveCompactionTimeoutMs accepts agentId
compaction-hooks.ts runPostCompactionSideEffects and postIndexSync resolve per-agent
compact.ts Model override, timeout, truncateAfterCompaction use resolved config
run/attempt.ts Timeout and extension factories use resolved config
schema.labels.ts Added 25 per-agent compaction labels

Example Config

agents:
  defaults:
    compaction:
      reserveTokens: 80000
      mode: safeguard
  list:
    - id: opus-agent
      compaction:
        reserveTokens: 400000  # Opus 200K can afford much more
    - id: glm-agent
      compaction:
        reserveTokens: 60000   # GLM-5 200K needs tighter threshold
    - id: cheap-worker
      compaction:
        mode: default          # No quality guard needed for bulk work

Testing

  • All existing pi-settings tests pass (8/8)
  • TypeScript compilation: zero new errors
  • Compaction test suite: 18/21 files pass (3 pre-existing failures unrelated to this change)
  • Per-agent overrides verified: agent config merges on top of defaults (shallow merge), missing fields fall back to defaults

Design Decisions

  1. Shallow merge — agent compaction merges on top of defaults. Nested objects like qualityGuard and memoryFlush are fully replaced (not deep-merged) to keep behavior predictable.

  2. No new config validation — reuses the exact same CompactionConfigSchema for both defaults and per-agent entries, ensuring validation parity.

  3. Minimal blast radius — only touches compaction-specific resolution. Other agent-level overrides (model, sandbox, heartbeat) already follow this pattern.

@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations agents Agent runtime and tooling size: M labels Apr 4, 2026
@greptile-apps

greptile-apps Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds per-agent compaction configuration overrides by extracting a shared CompactionConfigSchema, adding an optional compaction field to AgentEntrySchema and AgentConfig, and wiring a resolveAgentCompaction shallow-merge through all relevant call sites (timeout, hooks, extensions, settings manager). The approach is clean and consistent with how other per-agent overrides work, but one field falls through the gap.

  • reserveTokensFloor per-agent override is silently ignored: resolveCompactionReserveTokensFloor in pi-settings.ts reads directly from cfg?.agents?.defaults?.compaction?.reserveTokensFloor and accepts no agentId, so per-agent floor values are never applied even though the schema accepts them. The downstream call in applyPiCompactionSettingsFromConfig (line 81) correctly passes params.agentId to resolveEffectiveCompaction for other fields but omits it here, meaning the safety floor always comes from global defaults.

Confidence Score: 4/5

Safe to merge after fixing the reserveTokensFloor gap; all other per-agent compaction fields are correctly threaded.

One P1 defect: per-agent reserveTokensFloor is accepted by the schema but silently bypassed at runtime because resolveCompactionReserveTokensFloor does not take an agentId. The rest of the PR — shallow merge, timeout, mode/safeguard, postIndexSync, hooks, settings manager threading — is implemented correctly and consistently.

src/agents/pi-settings.ts — resolveCompactionReserveTokensFloor needs an agentId parameter and should delegate to resolveAgentCompaction rather than reading agents.defaults directly.

Comments Outside Diff (1)

  1. src/agents/pi-settings.ts, line 37-43 (link)

    P1 Per-agent reserveTokensFloor silently ignored

    resolveCompactionReserveTokensFloor reads directly from cfg?.agents?.defaults?.compaction?.reserveTokensFloor and has no agentId parameter, so any per-agent compaction.reserveTokensFloor override is silently discarded. The caller in applyPiCompactionSettingsFromConfig passes params.agentId to resolveEffectiveCompaction (for reserveTokens and keepRecentTokens) but calls this function without the agent id, so the safety floor is always resolved from global defaults — defeating per-agent floor configuration even though the schema accepts it.

    export function resolveCompactionReserveTokensFloor(cfg?: OpenClawConfig, agentId?: string): number {
      const compaction = agentId ? resolveAgentCompaction(cfg, agentId) : cfg?.agents?.defaults?.compaction;
      const raw = compaction?.reserveTokensFloor;
      if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) {
        return Math.floor(raw);
      }
      return DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR;
    }

    And update the call site at line 81:

    const reserveTokensFloor = resolveCompactionReserveTokensFloor(params.cfg, params.agentId);
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/pi-settings.ts
    Line: 37-43
    
    Comment:
    **Per-agent `reserveTokensFloor` silently ignored**
    
    `resolveCompactionReserveTokensFloor` reads directly from `cfg?.agents?.defaults?.compaction?.reserveTokensFloor` and has no `agentId` parameter, so any per-agent `compaction.reserveTokensFloor` override is silently discarded. The caller in `applyPiCompactionSettingsFromConfig` passes `params.agentId` to `resolveEffectiveCompaction` (for `reserveTokens` and `keepRecentTokens`) but calls this function without the agent id, so the safety floor is always resolved from global defaults — defeating per-agent floor configuration even though the schema accepts it.
    
    ```ts
    export function resolveCompactionReserveTokensFloor(cfg?: OpenClawConfig, agentId?: string): number {
      const compaction = agentId ? resolveAgentCompaction(cfg, agentId) : cfg?.agents?.defaults?.compaction;
      const raw = compaction?.reserveTokensFloor;
      if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) {
        return Math.floor(raw);
      }
      return DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR;
    }
    ```
    
    And update the call site at line 81:
    
    ```ts
    const reserveTokensFloor = resolveCompactionReserveTokensFloor(params.cfg, params.agentId);
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-settings.ts
Line: 37-43

Comment:
**Per-agent `reserveTokensFloor` silently ignored**

`resolveCompactionReserveTokensFloor` reads directly from `cfg?.agents?.defaults?.compaction?.reserveTokensFloor` and has no `agentId` parameter, so any per-agent `compaction.reserveTokensFloor` override is silently discarded. The caller in `applyPiCompactionSettingsFromConfig` passes `params.agentId` to `resolveEffectiveCompaction` (for `reserveTokens` and `keepRecentTokens`) but calls this function without the agent id, so the safety floor is always resolved from global defaults — defeating per-agent floor configuration even though the schema accepts it.

```ts
export function resolveCompactionReserveTokensFloor(cfg?: OpenClawConfig, agentId?: string): number {
  const compaction = agentId ? resolveAgentCompaction(cfg, agentId) : cfg?.agents?.defaults?.compaction;
  const raw = compaction?.reserveTokensFloor;
  if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) {
    return Math.floor(raw);
  }
  return DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR;
}
```

And update the call site at line 81:

```ts
const reserveTokensFloor = resolveCompactionReserveTokensFloor(params.cfg, params.agentId);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat: per-agent compaction configuration..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4509604f6c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/resolve-agent-compaction.ts Outdated
if (!agentId || !cfg?.agents?.list) {
return defaults;
}
const agentEntry = cfg.agents.list.find((a) => a.id === agentId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply agent ID normalization when selecting compaction overrides

resolveSessionAgentIds normalizes agent ids before they flow into compaction resolution, but this lookup compares raw config ids with a.id === agentId. If a configured id differs only by case or normalization (for example Main-Agent vs main-agent), the per-agent compaction block is never found and the code silently falls back to global defaults, so the new override feature does not work for those agents.

Useful? React with 👍 / 👎.

timeoutMs: resolveRunTimeoutWithCompactionGraceMs({
runTimeoutMs: params.timeoutMs,
compactionTimeoutMs: resolveCompactionTimeoutMs(params.config),
compactionTimeoutMs: resolveCompactionTimeoutMs(params.config, sessionAgentId),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reuse one compaction timeout source for all run deadlines

This path now uses an agent-specific timeout for the session lock budget, but the same function still derives the abort/grace timer timeout from resolveCompactionTimeoutMs(params.config) later on. When an agent override differs from agents.defaults.compaction.timeoutSeconds, lock expiration and abort grace are computed from different values, which can cause premature aborts or mismatched lock hold windows during compaction.

Useful? React with 👍 / 👎.

1. Normalize agent IDs in resolveAgentCompaction for case-insensitive matching
2. Pass sessionAgentId to all resolveCompactionTimeoutMs calls in attempt.ts
@1052326311

Copy link
Copy Markdown
Contributor Author

Codex P1 Review — Addressed in 8aa3a2a

P1: Agent ID normalization
resolveAgentCompaction now normalizes both the lookup ID and config entry IDs via normalizeAgentId() before comparison, ensuring case-insensitive matching (e.g., Main-Agent vs main-agent).

P1: Consistent compaction timeout in attempt.ts
Both resolveCompactionTimeoutMs calls in run/attempt.ts (lines 693 and 1293) now pass sessionAgentId, ensuring the session lock budget and abort/grace timer use the same agent-specific timeout value.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1521439796

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

sessionKey: params.sessionKey,
sessionFile,
mode: resolvePostCompactionIndexSyncMode(params.config),
mode: resolvePostCompactionIndexSyncMode(params.config, params.agentId),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve sync mode from session key when agentId is absent

runPostCompactionSideEffects now computes post-index sync mode only from params.agentId, so callers that still provide only sessionKey (for example the owns-compaction timeout path in src/agents/pi-embedded-runner/run.ts) silently fall back to agents.defaults.compaction.postIndexSync. That means per-agent compaction.postIndexSync overrides are ignored for those runs, causing unexpected waiting/skipping behavior after compaction for non-default agents.

Useful? React with 👍 / 👎.

Comment on lines +64 to +68
const watchOptions = {
...options,
nonInteractive: options.watch ? true : options.nonInteractive,
};
const loopCtx = { ...ctx, options: watchOptions };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recreate prompter with watch-mode nonInteractive settings

Watch mode sets nonInteractive only in watchOptions, but the prompter was already constructed from the original options before this block. As a result, any contribution that uses ctx.prompter.confirm* can still prompt interactively, so doctor watch mode can block on user input instead of running unattended polling loops.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Closing this as duplicate or superseded after Codex automated review.

Close #60807 as superseded by the newer active PR #69988. Current main still does not implement per-agent compaction overrides, but #69988 is now the canonical open implementation path for the same core config/runtime feature. #60807 is older, unmerged, based on an older main, and includes unrelated doctor/watch, agent-step, and cron test-harness changes alongside the compaction work.

Best possible solution:

Close #60807 as superseded by #69988. Keep the canonical per-agent compaction work concentrated in #69988 and land a focused implementation that adds agents.list[].compaction, merges it over agents.defaults.compaction, threads agent identity through Pi compaction/runtime/hook paths, updates generated schema/help/docs, and includes targeted config/runtime tests. Preserve the useful #57174 context and #60807 review findings, but avoid carrying the unrelated doctor/watch, agent-step, and cron test-harness changes from this older branch.

What I checked:

  • Current main rejects per-agent compaction config: AgentEntrySchema is a strict per-agent schema and currently lists fields such as model, memorySearch, tts, contextLimits, heartbeat, identity, groupChat, and subagents, but no compaction field. That means agents.list[].compaction is not accepted on current main. (src/config/zod-schema.agent-runtime.ts:822, e29d3516bf05)
  • Current type surface lacks AgentConfig.compaction: AgentConfig exposes per-agent overrides for model, skills, memorySearch, tts, contextLimits, heartbeat, sandbox, params, tools, and runtime, but no compaction property. (src/config/types.agents.ts:76, e29d3516bf05)
  • Runtime still resolves defaults-only compaction: applyPiCompactionSettingsFromConfig reads params.cfg?.agents?.defaults?.compaction, and resolveCompactionReserveTokensFloor reads cfg?.agents?.defaults?.compaction?.reserveTokensFloor; neither accepts or resolves an agent id on current main. (src/agents/pi-settings.ts:43, e29d3516bf05)
  • Other compaction runtime paths are also defaults-only: The embedded extension setup resolves compaction mode and safeguard config from cfg?.agents?.defaults?.compaction; post-compaction index sync and timeout resolution likewise read only defaults. (src/agents/pi-embedded-runner/extensions.ts:131, e29d3516bf05)
  • Docs only document defaults compaction: The public agent config docs section is agents.defaults.compaction; I found no docs entry for agents.list[].compaction on current main. Public docs: docs/gateway/config-agents.md. (docs/gateway/config-agents.md:540, e29d3516bf05)
  • Newer canonical PR tracks the same work: PR feat(config): support per-agent compaction overrides #69988, feat(config): support per-agent compaction overrides, is open and unmerged. Its body says it adds optional agents.list[].compaction, merges it over defaults, and threads effective per-agent compaction through embedded runtime paths. Its file list includes schema/types, agent-scope-config, pi-settings, embedded runner wiring, generated schema, and targeted tests. (eadf56e7c978)

So I’m closing this here and keeping the remaining discussion on the canonical linked item.

Codex Review notes: model gpt-5.5, reasoning high; reviewed against e29d3516bf05.

@clawsweeper clawsweeper Bot closed this Apr 26, 2026
@1052326311

Copy link
Copy Markdown
Contributor Author

Acknowledged — the assessment is fair. #60807 did pick up some unrelated doctor/watch and cron test-harness changes from an older branch state, and #69988 is a cleaner focused implementation of the same agents.list[].compaction feature.

Closing was the right call. I'll move support and any production validation context over to #69988.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Per-agent compaction configuration (thresholds, mode, reserveTokens)

1 participant