Skip to content

perf(agents): add contextInjection option to skip workspace re-injection#55221

Closed
cgdusek wants to merge 4 commits into
openclaw:mainfrom
cgdusek:claude/quirky-ellis
Closed

perf(agents): add contextInjection option to skip workspace re-injection#55221
cgdusek wants to merge 4 commits into
openclaw:mainfrom
cgdusek:claude/quirky-ellis

Conversation

@cgdusek

@cgdusek cgdusek commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in agents.defaults.contextInjection config setting that skips redundant workspace bootstrap file injection on continuation message turns, addressing the ~93% token waste reported in #9157.

  • "always" (default): current behavior — workspace files injected every turn
  • "continuation-skip": skip injection when the session file already exists (continuation turns); always re-inject on first message and after compaction

Why existing PRs are not acceptable

Five community PRs attempted this fix (#46813, #44220, #52916, #28072, #40372) — all were either closed or have blocking issues:

How this fix differs

  1. Compaction-safe: compact.ts calls resolveBootstrapContextForRun() independently without the continuation-skip guard, so workspace files are always re-injected after compaction
  2. Tested: 8 new tests — 4 for resolveContextInjectionMode helper, 4 for Zod schema validation
  3. Clean session check: single early fs.stat before bootstrap resolution, not duplicated with the existing hadSessionFile (which serves a different purpose)
  4. Opt-in: default is "always" to avoid surprising behavior changes on upgrade

Usage

# openclaw.yaml
agents:
  defaults:
    contextInjection: continuation-skip

Closes #9157

Test plan

  • pnpm build passes (no INEFFECTIVE_DYNAMIC_IMPORT warnings)
  • pnpm check passes (0 errors, 0 warnings)
  • pnpm test -- src/agents/bootstrap-files.test.ts src/config/zod-schema.agent-defaults.test.ts — 14/14 pass
  • pnpm config:docs:gen / config:docs:check — in sync
  • Manual: set contextInjection: continuation-skip, verify first message includes workspace files, second message does not, post-compaction message re-includes them

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an opt-in agents.defaults.contextInjection configuration setting that allows workspace bootstrap files (AGENTS.md, SOUL.md, etc.) to be skipped on continuation turns, addressing a reported ~93% token waste on long sessions. The default remains "always" (existing behavior), while "continuation-skip" skips injection when the session file already exists.

Key aspects of the implementation:

  • Compaction-safe: compact.ts calls resolveBootstrapContextForRun() directly without the continuation-skip guard, so context is always re-injected after a compaction event.
  • Correct session detection: uses a single fs.stat(params.sessionFile) ahead of bootstrap resolution; this is deliberately separate from the pre-existing hadSessionFile check (which runs after repairSessionFileIfNeeded and serves a different purpose).
  • Well-tested: 8 new tests covering the helper function and Zod schema validation.
  • Two minor style nits: (1) contextInjection in AgentDefaultsConfig repeats the inline union "always" | "continuation-skip" instead of reusing the AgentContextInjection alias defined two lines above in the same file; (2) a blank line is missing between resolveContextInjectionMode and the following BootstrapContextRunKind type in bootstrap-files.ts.

Confidence Score: 5/5

Safe to merge — opt-in with a no-op default, compaction path verified, and tests cover the key edge cases.

The change is strictly additive and opt-in (default 'always' preserves existing behavior). The compaction-safety claim is verified: compact.ts calls resolveBootstrapContextForRun independently. Session-file detection logic is correct. The only findings are two minor style issues that don't affect correctness or reliability.

No files require special attention.

Important Files Changed

Filename Overview
src/agents/bootstrap-files.ts Adds resolveContextInjectionMode helper that reads the new config setting with a safe 'always' default. Minor: missing blank line after the new function.
src/agents/pi-embedded-runner/run/attempt.ts Core change: adds isContinuationTurn guard that skips bootstrap context resolution when contextInjection is 'continuation-skip' and the session file already exists. Logic is correct — compaction safety is delegated to compact.ts which calls resolveBootstrapContextForRun independently.
src/config/types.agent-defaults.ts Exports AgentContextInjection type and adds contextInjection field to AgentDefaultsConfig. Minor inconsistency: the field uses an inline union rather than the AgentContextInjection type alias defined in the same file.
src/config/zod-schema.agent-defaults.ts Adds optional contextInjection Zod union validator that accepts 'always' or 'continuation-skip', consistent with the TypeScript type definition.
src/agents/bootstrap-files.test.ts 4 new unit tests for resolveContextInjectionMode covering undefined config, missing field, 'continuation-skip', and explicit 'always'.
src/config/zod-schema.agent-defaults.test.ts 4 new schema validation tests: accepts 'always', 'continuation-skip', omitted value; rejects invalid string.
src/config/schema.base.generated.ts Generated schema updated to include contextInjection as a const-union anyOf and adds the corresponding label/help entry.
src/config/schema.help.ts Adds help text for agents.defaults.contextInjection describing both allowed values and their behavior.
src/config/schema.labels.ts Adds the 'Context Injection' display label for agents.defaults.contextInjection.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/config/types.agent-defaults.ts
Line: 150

Comment:
**Use `AgentContextInjection` instead of inline union**

`AgentContextInjection` is exported from the same file at line 11 specifically to represent this type. The `contextInjection` field in `AgentDefaultsConfig` should reference it rather than repeating the inline union, so future additions to the allowed values only need to be made in one place.

```suggestion
  contextInjection?: AgentContextInjection;
```

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

---

This is a comment left during a code review.
Path: src/agents/bootstrap-files.ts
Line: 22-23

Comment:
**Missing blank line after function declaration**

A blank line is missing between the closing brace of `resolveContextInjectionMode` and the `BootstrapContextRunKind` type declaration, breaking the visual separation that exists between all other top-level declarations in this file.

```suggestion
}

export type BootstrapContextRunKind = "default" | "heartbeat" | "cron";
```

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

Reviews (1): Last reviewed commit: "perf(agents): add contextInjection optio..." | Re-trigger Greptile

Comment thread src/config/types.agent-defaults.ts Outdated
* - continuation-skip: skip injection on continuation turns when a session
* file already exists, re-inject only on first message and after compaction
*/
contextInjection?: "always" | "continuation-skip";

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 Use AgentContextInjection instead of inline union

AgentContextInjection is exported from the same file at line 11 specifically to represent this type. The contextInjection field in AgentDefaultsConfig should reference it rather than repeating the inline union, so future additions to the allowed values only need to be made in one place.

Suggested change
contextInjection?: "always" | "continuation-skip";
contextInjection?: AgentContextInjection;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/config/types.agent-defaults.ts
Line: 150

Comment:
**Use `AgentContextInjection` instead of inline union**

`AgentContextInjection` is exported from the same file at line 11 specifically to represent this type. The `contextInjection` field in `AgentDefaultsConfig` should reference it rather than repeating the inline union, so future additions to the allowed values only need to be made in one place.

```suggestion
  contextInjection?: AgentContextInjection;
```

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in bad2a92.

Comment on lines +22 to 23
}
export type BootstrapContextRunKind = "default" | "heartbeat" | "cron";

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 Missing blank line after function declaration

A blank line is missing between the closing brace of resolveContextInjectionMode and the BootstrapContextRunKind type declaration, breaking the visual separation that exists between all other top-level declarations in this file.

Suggested change
}
export type BootstrapContextRunKind = "default" | "heartbeat" | "cron";
}
export type BootstrapContextRunKind = "default" | "heartbeat" | "cron";
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/bootstrap-files.ts
Line: 22-23

Comment:
**Missing blank line after function declaration**

A blank line is missing between the closing brace of `resolveContextInjectionMode` and the `BootstrapContextRunKind` type declaration, breaking the visual separation that exists between all other top-level declarations in this file.

```suggestion
}

export type BootstrapContextRunKind = "default" | "heartbeat" | "cron";
```

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in bad2a92.

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

Copy link
Copy Markdown

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: d52d036704

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1737 to +1741
contextInjectionMode === "continuation-skip" &&
(await fs
.stat(params.sessionFile)
.then(() => true)
.catch(() => false));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Detect continuation from transcript, not file existence

contextInjection: "continuation-skip" currently treats any existing sessionFile as a continuation turn, but this file can exist before a real prior turn is established (for example after prewarmSessionFile(...) on a failed first attempt, or during run-loop retries/failover). In those cases, this branch skips bootstrap/context injection even though there may be no assistant history yet; prepareSessionManagerForRun explicitly handles this "file exists but no assistant" state in src/agents/pi-embedded-runner/session-manager-init.ts. The result is that first successful retries can run without AGENTS/bootstrap instructions, which is a behavior regression introduced by this check.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in bad2a92 — replaced fs.stat with hasCompletedBootstrapTurn() that checks for assistant messages in the session JSONL.

@openclaw-barnacle openclaw-barnacle Bot added the docs Improvements or additions to documentation label Mar 26, 2026
@aisle-research-bot

aisle-research-bot Bot commented Mar 26, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Potential memory/time DoS via unbounded transcript read in hasCompletedBootstrapTurn()
2 🟡 Medium Optional skipping of workspace bootstrap context on continuation turns can drop safety constraints from system prompt
Vulnerabilities

1. 🟡 Potential memory/time DoS via unbounded transcript read in hasCompletedBootstrapTurn()

Property Value
Severity Medium
CWE CWE-400
Location src/agents/bootstrap-files.ts:29-35

Description

hasCompletedBootstrapTurn() reads the entire session transcript (.jsonl) into memory and then splits it into lines, potentially causing high memory usage and CPU overhead on large transcripts.

  • Input: sessionFile path (session transcript) passed into runEmbeddedAttempt() as params.sessionFile
  • Behavior: await fs.readFile(sessionFile, "utf-8") loads the full file, then content.split(/\r?\n/) creates an array of all lines
  • Impact: If a transcript grows very large (e.g., many turns / very long messages), each embedded attempt with contextInjection="continuation-skip" will incur O(file_size) memory + time overhead, risking latency spikes or process OOM (denial of service)

Vulnerable code:

content = await fs.readFile(sessionFile, "utf-8");
...
for (const line of content.split(/\r?\n/)) {
  ...
}

Recommendation

Avoid loading the full transcript into memory.

Safer options (pick one):

  1. Stream the file line-by-line (bounded memory) using fs.createReadStream() + readline:
import fs from "node:fs";
import readline from "node:readline";

export async function hasCompletedBootstrapTurn(sessionFile: string): Promise<boolean> {
  try {
    const stream = fs.createReadStream(sessionFile, { encoding: "utf8" });
    const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });

    let hasAssistant = false;
    let compactedAfterLastAssistant = false;

    for await (const line of rl) {
      if (!line) continue;// same parsing logic...
    }
    return hasAssistant && !compactedAfterLastAssistant;
  } catch {
    return false;
  }
}
  1. Add explicit size limits (e.g., stat() and bail out / treat as false when too large), and/or only scan the tail of the file (where the last assistant/compaction entries are likely to be).

These changes prevent large transcripts from causing unbounded RAM usage per attempt.


2. 🟡 Optional skipping of workspace bootstrap context on continuation turns can drop safety constraints from system prompt

Property Value
Severity Medium
CWE CWE-693
Location src/agents/pi-embedded-runner/run/attempt.ts:337-371

Description

In runEmbeddedAttempt, when agents.defaults.contextInjection is set to "continuation-skip" and the session file indicates a prior assistant turn, the runner skips resolveBootstrapContextForRun() and injects no contextFiles (and no bootstrapFiles) into the generated system prompt for that turn.

This is a security regression risk because the skipped files (e.g., AGENTS.md, SOUL.md, TOOLS.md, USER.md, MEMORY.md, etc.) can contain mandatory operator-/deployment-defined constraints such as:

  • “Red lines” / non-exfiltration and operational safety rules
  • Instructions restricting what actions are allowed in shared contexts vs. main sessions
  • Tooling/process requirements that the operator intends to be present every turn

While the code comment describes the continuation as “safe”, the safety property is only that a previous assistant message exists and no compaction occurred after it. It does not guarantee that the model will still follow operator-defined constraints if they are no longer present in the current system prompt, nor does it prevent prompt-injection in later turns from overriding missing guidance.

Vulnerable behavior:

const { bootstrapFiles: hookAdjustedBootstrapFiles, contextFiles } = isContinuationTurn
  ? {
      bootstrapFiles: [] as WorkspaceBootstrapFile[],
      contextFiles: [] as EmbeddedContextFile[],
    }
  : await resolveBootstrapContextForRun({ ... });

If an installation relies on these bootstrap files as part of its safety policy, enabling continuation-skip can effectively bypass that policy on continuation turns.

Recommendation

Treat bootstrap/context injection as part of the security boundary, not only as a token-optimization.

Options:

  1. Always inject security-critical bootstrap files, even in continuation-skip mode (e.g., inject a minimal subset like AGENTS.md + TOOLS.md + USER.md), or add a separate mode for skipping only persona/large context.

  2. Make safety/tool restrictions enforceable in code, not only in prompt text (e.g., explicit allowlists/denylists, per-channel capability enforcement, data egress controls).

  3. If keeping continuation-skip, add a guard that ensures the system prompt still includes required policy sections (e.g., a mandatory “red lines” section) on every turn.

Example: inject a minimal subset on continuation turns:

const minimalRun = await resolveBootstrapContextForRun({
  ...,
  contextMode: "lightweight",
  runKind: params.bootstrapContextRunKind,
});// or explicitly filter to required filenames before buildBootstrapContextFiles

Also update documentation to warn that continuation-skip may remove operator-defined safety constraints on later turns.


Analyzed PR: #55221 at commit 3ee8918

Last updated on: 2026-03-27T04:04:21Z

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

Copy link
Copy Markdown

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: 3892c4b68f

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1743 to +1746
const { bootstrapFiles: hookAdjustedBootstrapFiles, contextFiles } = isContinuationTurn
? {
bootstrapFiles: [] as WorkspaceBootstrapFile[],
contextFiles: [] as EmbeddedContextFile[],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve HEARTBEAT.md injection on heartbeat continuation runs

When agents.defaults.contextInjection is set to "continuation-skip", this branch bypasses resolveBootstrapContextForRun(...) and forces empty bootstrap/context files whenever a session file exists. That also affects heartbeat turns, even though heartbeat code can request lightweight context (src/infra/heartbeat-runner.ts) where applyContextModeFilter is designed to keep HEARTBEAT.md (src/agents/bootstrap-files.ts). As a result, heartbeat continuations lose their checklist context after the first persisted turn, which can change heartbeat decisions/output in production automation flows.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in bad2a92 — heartbeat runs are now explicitly excluded from continuation-skip, preserving the HEARTBEAT.md lightweight filtering path.

@cgdusek

cgdusek commented Mar 26, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the analysis @aisle-research-bot.

Addressing the security concern:

  1. This is opt-in — default is "always" (no behavior change). Users must deliberately set contextInjection: "continuation-skip" to enable it.

  2. Pre-existing precedent — the codebase already has two mechanisms that produce the same effect:

    • skipBootstrap: true drops all bootstrap files entirely
    • bootstrapContextMode: "lightweight" returns empty context for non-heartbeat runs

    Both are documented config options available today. This PR adds a middle ground that preserves first-turn + post-compaction injection.

  3. Compaction safetycompact.ts always re-injects full bootstrap context independently (line 729), so workspace files are restored after compaction events.

  4. LLM API is stateless — the system prompt is rebuilt on every API call. On continuation turns with continuation-skip, the model does not see bootstrap files in its system prompt. However, the previous turns (including the first turn with full bootstrap) remain in the conversation history, so the model still has access to the behavioral context from the initial injection.

The tradeoff (token savings vs. reduced system prompt surface on continuation turns) is intentional and opt-in. The existing skipBootstrap flag is strictly more aggressive than this feature.

Addressed Greptile's style nits in 769d5a0.

@steipete

Copy link
Copy Markdown
Contributor

This is a useful optimization target, but I do not think it is merge-ready as written.

Two behavioral regressions stand out:

  • continuation is detected from sessionFile existence, not from real prior-turn state. Header-only / pre-created session files can exist before the first successful assistant turn, so this can skip bootstrap on a recovered first turn.
  • heartbeat continuation under lightweight context loses HEARTBEAT.md, because this bypasses resolveBootstrapContextForRun(...) entirely instead of preserving the existing heartbeat-only filtering path.

I would want the continuation-skip gate tightened to real transcript state with at least one assistant turn, heartbeat lightweight semantics preserved, and run-level tests added for:

  • header-only or pre-created session file
  • heartbeat continuation
  • post-compaction reinjection

Also worth being explicit in the PR description: this can help workspace-bootstrap overhead, but it does not address the broader token-burn complaints from Mission Control heartbeats, repeated skill prompt injection, or long-session history growth.

@steipete

Copy link
Copy Markdown
Contributor

I think the underlying issue in #9157 is real and clear, but this specific fix is still using the wrong signal.

The new guard in src/agents/pi-embedded-runner/run/attempt.ts treats fs.stat(sessionFile) as equivalent to “bootstrap context is already represented in the model’s effective context”. In this runner, that is not a safe assumption.

Two concrete cases break:

  1. Pre-created / repaired / empty sessions

prepareSessionManagerForRun() explicitly handles hadSessionFile && header && !hasAssistant as a first-run state and resets the file so the first assistant flush persists header + user + assistant in order:

  • src/agents/pi-embedded-runner/session-manager-init.ts

So an existing session file does not necessarily mean the session has already had a real assistant turn with bootstrap context available. With contextInjection: continuation-skip, those sessions now skip AGENTS/SOUL/bootstrap injection too early.

  1. Post-compaction retry

The PR description says compaction is safe because compact.ts resolves bootstrap context independently. That is only true for the compaction prompt itself.

After successful compaction, the main run loop retries by calling runEmbeddedAttempt() again:

  • src/agents/pi-embedded-runner/run.ts (runEmbeddedAttempt(...))
  • timeout recovery branch: if (timeoutCompactResult.compacted) { ... continue; }
  • overflow recovery branch: if (compactResult.compacted) { ... continue; }

That retried attempt rebuilds the system prompt from scratch, but sessionFile still exists, so this new guard suppresses bootstrap injection on exactly the first post-compaction retry turn. compact.ts resolving contextFiles for the compaction operation does not persist that bootstrap context into later normal attempts.

So I don’t think session file exists is the right discriminator here. The fix needs a signal closer to “this session already has a completed prior assistant turn / safe-to-skip bootstrap”, or an explicit persisted marker/fingerprint that can be invalidated after compaction.

I’d want regression coverage for at least:

  • pre-created session file with no assistant turn yet
  • repaired/empty session file
  • first normal retry after successful compaction

@cgdusek cgdusek force-pushed the claude/quirky-ellis branch from 769d5a0 to bad2a92 Compare March 27, 2026 03:51
@cgdusek

cgdusek commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

Pushed bad2a92 addressing all review feedback. Here's what changed:

@steipete's feedback (both comments)

1. "continuation detected from sessionFile existence, not real prior-turn state"

Fixed. Replaced fs.stat(sessionFile) with hasCompletedBootstrapTurn(sessionFile) which reads the session JSONL and checks for at least one completed assistant turn (type: "message" with role: "assistant"). Pre-created, repaired, or header-only session files now correctly return false, ensuring bootstrap is always injected on the true first turn.

2. "heartbeat continuation under lightweight context loses HEARTBEAT.md"

Fixed. The continuation-skip gate now explicitly excludes heartbeat runs (params.bootstrapContextRunKind !== "heartbeat"), preserving the applyContextModeFilter path that keeps HEARTBEAT.md in lightweight heartbeat mode.

3. "post-compaction retry"

Fixed. hasCompletedBootstrapTurn tracks whether a type: "compaction" entry appears after the last assistant message. If so, it returns false, forcing bootstrap re-injection on the post-compaction retry. Once the retry completes (new assistant message after compaction), subsequent turns can safely skip again.

4. Regression tests requested

Added 8 tests for hasCompletedBootstrapTurn covering all requested scenarios:

  • Pre-created session file with no assistant turn → false
  • Empty session file → false
  • Header-only session → false
  • User-only messages → false
  • Completed assistant turn → true
  • Post-compaction retry (compaction after last assistant) → false
  • Normal continuation after compaction + new assistant → true
  • Malformed JSON lines → handled gracefully

Greptile style nits

Both addressed in previous commit (769d5a0, preserved through rebase):

  • contextInjection field now uses AgentContextInjection type alias
  • Blank line added between resolveContextInjectionMode and BootstrapContextRunKind

Codex inline comments

P1 "Detect continuation from transcript, not file existence" — same fix as steipete point 1 above.
P2 "Preserve HEARTBEAT.md injection" — same fix as steipete point 2 above.

Aisle Security concern

Addressed in previous comment — the feature is opt-in, has pre-existing precedent (skipBootstrap, lightweight mode), and compaction always re-injects. The new transcript-based detection adds further safety by ensuring bootstrap was actually injected and processed before skipping.

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

Copy link
Copy Markdown

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: bad2a92f68

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +352 to +356
const contextInjectionMode = resolveContextInjectionMode(params.config);
const isContinuationTurn =
contextInjectionMode === "continuation-skip" &&
params.bootstrapContextRunKind !== "heartbeat" &&
(await hasCompletedBootstrapTurn(params.sessionFile));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Re-check continuation status after taking session lock

The continuation-skip decision is made from hasCompletedBootstrapTurn(params.sessionFile) before the session lock is acquired later in this function, so another process can compact or rewrite the same transcript in between. In that window, this attempt can still skip bootstrap/context on what is now the first post-compaction turn, which reintroduces missing-context behavior under concurrent runners. Re-evaluate continuation state after acquireSessionWriteLock(...) (or move this check under the lock) so the decision matches the transcript state actually used for the run.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. In practice, this race window is narrow and bounded:

  1. Compaction is process-scoped — it happens within the same run loop (the continue branches in run.ts), not cross-process. The session lock serializes writes within the current process.

  2. If a concurrent process does compact between the check and the lock, the worst case is skipping bootstrap on one turn — the next compaction event will re-inject full context via compact.ts independently.

  3. Moving the check under the lock would require restructuring the bootstrap resolution to happen after session manager init (line 704+), which is a larger refactor. The current position mirrors the existing resolveBootstrapContextForRun call site.

I think this is a valid concern for a follow-up hardening pass but not a blocking regression for an opt-in feature. The cross-process concurrent compaction scenario is an existing design limitation that affects other session state decisions too.

Comment on lines +43 to +44
content = await fs.readFile(sessionFile, "utf-8");
} catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stream session scan instead of reading full transcript

hasCompletedBootstrapTurn loads the entire session JSONL into memory with readFile before scanning, which adds large allocations on every continuation-skip turn. Since session files can grow substantially in long-lived conversations, this can cause avoidable latency and memory pressure in the exact workload this optimization targets. A streaming line reader (or precomputed session metadata) would keep this check O(1) memory.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call. For v1, the full-file read with fast-path string checks (line.includes('"assistant"')) before JSON parsing keeps the overhead reasonable — JSON.parse is only called for lines that match the fast check, which is a small subset.

For large sessions (100+ turns), a streaming line reader or precomputed session metadata would be better. I'll note this as a follow-up optimization. The fast-path checks mean the current implementation is O(n) time but mostly avoids allocation pressure from JSON parsing.

@cgdusek cgdusek force-pushed the claude/quirky-ellis branch from bad2a92 to 3ee8918 Compare March 27, 2026 04:01

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

Copy link
Copy Markdown

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: 3ee891815c

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +352 to +356
const contextInjectionMode = resolveContextInjectionMode(params.config);
const isContinuationTurn =
contextInjectionMode === "continuation-skip" &&
params.bootstrapContextRunKind !== "heartbeat" &&
(await hasCompletedBootstrapTurn(params.sessionFile));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Re-evaluate continuation skip after acquiring session lock

isContinuationTurn is decided before acquireSessionWriteLock, so the decision can become stale while this attempt waits for the lock. Fresh evidence: same-session concurrency is expected because active runs can still take the run-now path (src/auto-reply/reply/queue-policy.ts:13-20, wired in src/auto-reply/reply/agent-runner.ts:213-256), which allows another attempt to append a compaction entry first. In that case this turn still skips bootstrap/context even though it is now the first post-compaction turn, reintroducing the missing-context behavior this feature was trying to avoid.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same concern as the earlier thread — acknowledged there. The run-now path is a good additional data point.

To summarize: this is a known limitation of the current placement. The continuation check runs before the session lock, so a concurrent compaction between check and lock could cause one turn to skip bootstrap incorrectly. The fallback is that the next compaction event will re-inject full context, bounding the exposure to at most one turn.

Moving the check under the lock requires restructuring bootstrap resolution into the inner try block (after prepareSessionManagerForRun), which I think is the right follow-up but too invasive for this PR. Happy to do it as a follow-up if maintainers want it before merge.

cgdusek and others added 4 commits March 27, 2026 10:21
…ion (openclaw#9157)

Add an opt-in agents.defaults.contextInjection config setting that skips
redundant workspace bootstrap file injection on continuation turns.

When set to "continuation-skip", bootstrap files (AGENTS.md, SOUL.md,
etc.) are only injected on the first message and after compaction,
saving ~93% of workspace context tokens on long sessions.

Default is "always" (current behavior) to avoid surprising changes on
upgrade. Compaction path in compact.ts independently resolves full
context, so workspace files are always re-injected after compaction.

Closes openclaw#9157
…eat filtering

Address review feedback from steipete, Codex, and Greptile:

1. Replace fs.stat(sessionFile) with hasCompletedBootstrapTurn() that reads
   the session JSONL and checks for a completed assistant turn. This prevents
   false positives from pre-created/repaired/empty session files that exist
   before the first real assistant turn.

2. Detect post-compaction retries: if the last significant entry after the
   final assistant message is a compaction entry, force re-injection since
   compact.ts rewrites the transcript and the retry must rebuild context.

3. Never skip bootstrap on heartbeat runs, preserving the lightweight context
   filtering path that keeps HEARTBEAT.md via applyContextModeFilter.

4. Add 8 regression tests for hasCompletedBootstrapTurn covering:
   - nonexistent session file
   - empty session file
   - header-only (pre-created) session
   - user-only messages (no assistant turn)
   - completed assistant turn (safe to skip)
   - post-compaction retry (must re-inject)
   - normal continuation after compaction+assistant
   - malformed JSON lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cgdusek cgdusek force-pushed the claude/quirky-ellis branch from 3ee8918 to 9adc03a Compare March 27, 2026 15:22

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

Copy link
Copy Markdown

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: 9adc03aac7

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +54 to +59
for (const line of content.split(/\r?\n/)) {
if (!line) {
continue;
}
// Fast string checks before parsing JSON to keep large-session overhead low.
if (line.includes('"assistant"') && line.includes('"message"')) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compute continuation state from active branch only

hasCompletedBootstrapTurn scans every JSONL line in file order and infers continuation from the last seen assistant/compaction pair, but it never follows the session’s active leaf/branch. Session files can contain sibling branches (the truncation path explicitly preserves them in src/agents/pi-embedded-runner/session-truncation.ts), so an assistant message on another branch can make this return true and cause continuation-skip to drop bootstrap context on the current branch’s first post-compaction turn. Derive this check from the active branch (for example via SessionManager.getBranch() under the session lock) to avoid cross-branch false positives.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid point. The raw JSONL scan doesn't distinguish between branches — an assistant message on a sibling branch could cause a false positive.

In practice, this is a narrow edge case: sibling branches are created during truncation retry paths, and the linear scan's compaction-after-assistant check provides some protection (compaction entries in the active branch reset the state). But it's not airtight.

The proper fix (using `SessionManager.getBranch()` to scan only the active branch) requires the same restructuring as the lock-timing concern: moving the continuation check after `SessionManager.open()` at line 704. I've noted both as a follow-up refactor — same change addresses both issues.

For v1, the false-positive risk is bounded: if we incorrectly skip bootstrap on a branched session, the next compaction re-injects full context. And this only affects sessions with `contextInjection: "continuation-skip"` enabled.

@cgdusek

cgdusek commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

Re: updated Aisle analysis (2 issues):

1. Unbounded transcript read (CWE-400) — Acknowledged. Same concern as Codex P2 (streaming). The current implementation uses fast-path string checks before JSON parsing to minimize overhead. A streaming reader is a valid follow-up optimization for very large sessions but not a blocking concern for an opt-in feature that only runs when continuation-skip is explicitly configured.

2. Bootstrap context dropping — Same concern from the first Aisle analysis, already addressed in my earlier comment. Opt-in, pre-existing precedent (skipBootstrap, lightweight mode), compaction-safe.

No code changes needed — both are acknowledged limitations documented in the PR and tracked for follow-up.

@vincentkoc

Copy link
Copy Markdown
Member

closing this one out.

this was a real perf direction, but it has already landed differently on main: the current tree already has contextInjection / continuation-skip handling in the agent runtime and config schema. keeping this PR open now is just duplicate surface.

if there is still a gap after that landed version, the right move is a focused follow-up against current main rather than reviving this older branch.

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

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance: Workspace file injection wastes 93.5% of token budget

3 participants