Skip to content

docs: add context engine documentation#46409

Merged
jalehman merged 4 commits into
openclaw:mainfrom
jalehman:docs/context-engine
Mar 17, 2026
Merged

docs: add context engine documentation#46409
jalehman merged 4 commits into
openclaw:mainfrom
jalehman:docs/context-engine

Conversation

@jalehman

Copy link
Copy Markdown
Contributor

Summary

Adds dedicated documentation for the pluggable context engine system introduced in #22201.

The plugins.slots.contextEngine config slot has been available since the context engine PR landed, but there was no dedicated docs page explaining how it works, how to configure it, or how to build a plugin engine.

Changes

  • New page: docs/concepts/context-engine.md — covers the full context engine lifecycle (ingest → assemble → compact → afterTurn), the legacy engine, plugin engine authoring with code examples, the ContextEngine interface, ownsCompaction semantics, subagent hooks, systemPromptAddition, and configuration reference.
  • Nav: Added to Agents > Fundamentals tab, after the existing Context page.
  • Redirect: /context-engine/concepts/context-engine
  • Cross-links: Updated context.md and compaction.md to link to the new page.

What's documented

  • How context engines participate in the model run lifecycle
  • The legacy engine's pass-through behavior
  • How to register a plugin engine via api.registerContextEngine()
  • The full ContextEngine interface (required + optional methods)
  • ownsCompaction flag and its effect on auto-compaction
  • Subagent lifecycle hooks (prepareSubagentSpawn, onSubagentEnded)
  • Dynamic system prompt additions via systemPromptAddition
  • Relationship between context engines, memory plugins, and session pruning
  • Configuration via plugins.slots.contextEngine

Follows the Diataxis framework: structured as a mix of explanation (how it works) and how-to (building a plugin engine).

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation size: XS maintainer Maintainer-authored PR labels Mar 14, 2026
@jalehman
jalehman force-pushed the docs/context-engine branch from a473402 to 8075e5e Compare March 14, 2026 22:59
@jalehman
jalehman marked this pull request as ready for review March 14, 2026 23:52
@greptile-apps

greptile-apps Bot commented Mar 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a dedicated docs/concepts/context-engine.md page documenting the pluggable context engine system (originally shipped in #22201), and cross-links it from compaction.md, context.md, and the nav/redirects in docs.json. The new page is well-organized and covers the full lifecycle, the legacy engine's pass-through behaviour, plugin authoring with a TypeScript example, ownsCompaction semantics, subagent hooks, and configuration.

Two minor gaps worth addressing before the page is final:

  • In the ContextEngine interface table, info is labeled as a "Method" but is an object property (no parentheses in the code example). The bootstrap, ingestBatch, and dispose optional members appear in the table but have no lifecycle timing explanation — plugin authors won't know when these are invoked.
  • The assemble example returns an estimatedTokens field that is never described in the interface documentation; its type, optionality, and effect are unclear to implementers.

Confidence Score: 4/5

  • Safe to merge — docs-only change with no runtime impact; two small documentation gaps are worth a quick follow-up.
  • All changes are documentation. The new page is accurate, well-structured, and consistent with the existing docs. The two flagged issues (interface table labeling and undocumented estimatedTokens) are style/completeness concerns, not correctness blockers.
  • docs/concepts/context-engine.md — interface table and assemble return values could use minor clarification.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: docs/concepts/context-engine.md
Line: 164-184

Comment:
**`info` mislabeled as a method; `bootstrap` and `ingestBatch` missing from lifecycle explanation**

Two related issues in this interface table:

1. `info` is listed under a **"Method"** header but it's a property (an object literal, not a callable — as shown in the code example where it has no parentheses). This could mislead a plugin author into wondering whether it should be `info()` vs `info: { … }`.

2. Three optional members — `bootstrap`, `ingestBatch`, and `dispose` — appear in the table but are not explained in the "How it works" lifecycle section above. Plugin authors implementing these won't know *when* they are invoked:
   - `bootstrap(params)` — is this called once when the engine is selected, or per session?
   - `ingestBatch(params)` — when is this called instead of (or in addition to) `ingest`? The difference matters for correctness.
   - `dispose()` — what triggers this? Plugin reload? Session end?

Suggested improvements:

- Rename the table header from `Method` to `Member` (or `Method / Property`) to reflect that `info` is a property.
- Add a short lifecycle note for `bootstrap`, `ingestBatch`, and `dispose`, similar to how `prepareSubagentSpawn` and `onSubagentEnded` are covered in the **Subagent lifecycle** section above.

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

---

This is a comment left during a code review.
Path: docs/concepts/context-engine.md
Line: 130-136

Comment:
**`estimatedTokens` return field is undocumented**

The `assemble` example returns `estimatedTokens`:

```ts
return {
  messages: buildContext(messages, tokenBudget),
  estimatedTokens: countTokens(messages),
  systemPromptAddition: "Use lcm_grep to search history...",
};
```

`estimatedTokens` is not mentioned anywhere in the interface description or the `assemble` row in the table. A plugin author won't know its type, whether it's required, or what OpenClaw does with it (e.g., does it affect auto-compaction thresholds?). Consider adding a brief note in the `assemble(params)` table row or a separate "Return values" subsection.

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

Last reviewed commit: 8075e5e

Comment thread docs/concepts/context-engine.md Outdated
Comment thread docs/concepts/context-engine.md

@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: 8075e5e0b9

ℹ️ 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 docs/concepts/context-engine.md Outdated

@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: 4f158ef917

ℹ️ 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 docs/concepts/context-engine.md Outdated
Comment thread docs/concepts/context-engine.md Outdated

@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: 716fbdad5d

ℹ️ 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".


| Member | Kind | Purpose |
| ------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------- |
| `bootstrap(params)` | Method | Initialize engine state for a session. Called once when the engine first sees a session (e.g., import history). |

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 Clarify bootstrap is invoked on each run attempt

This row says bootstrap is called once when an engine first sees a session, but runEmbeddedAttempt calls contextEngine.bootstrap(...) for every run attempt whenever the session file already exists (including retries), and skips it entirely for brand-new sessions. Plugin authors following this contract can accidentally do expensive re-import work on every turn or miss initialization paths they expected to run once.

Useful? React with 👍 / 👎.

| Member | Kind | Purpose |
| ------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------- |
| `bootstrap(params)` | Method | Initialize engine state for a session. Called once when the engine first sees a session (e.g., import history). |
| `ingestBatch(params)` | Method | Ingest a completed turn as a batch. Called after a run completes, with all messages from that turn at once. |

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 Mark ingestBatch as fallback when afterTurn is absent

The docs state ingestBatch runs after each completed turn, but the runtime only calls ingestBatch/ingest in the fallback branch when afterTurn is not implemented (run/attempt.ts does afterTurn first, then else for ingestion). Engines that implement afterTurn based on this description will not receive ingestBatch, which can silently break indexing/persistence logic.

Useful? React with 👍 / 👎.

| `afterTurn(params)` | Method | Post-run lifecycle work (persist state, trigger background compaction). |
| `prepareSubagentSpawn(params)` | Method | Set up shared state for a child session. |
| `onSubagentEnded(params)` | Method | Clean up after a subagent ends. |
| `dispose()` | Method | Release resources. Called during gateway shutdown or plugin reload — not per-session. |

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 Correct dispose hook lifetime to per-run instances

This claims dispose() is a shutdown/reload-only hook, but context engines are instantiated by resolveContextEngine() and disposed in finally blocks in both run and /compact flows, so dispose currently executes per operation rather than only at process teardown. Plugin authors may design resource lifetimes around a long-lived engine instance and get unexpected teardown/reconnect behavior every turn.

Useful? React with 👍 / 👎.

@jalehman
jalehman force-pushed the docs/context-engine branch from 716fbda to 8e1c75f Compare March 17, 2026 07:14
Add dedicated docs page for the pluggable context engine system:
- Full lifecycle explanation (ingest, assemble, compact, afterTurn)
- Legacy engine behavior documentation
- Plugin engine authoring guide with code examples
- ContextEngine interface reference table
- ownsCompaction semantics
- Subagent lifecycle hooks (prepareSubagentSpawn, onSubagentEnded)
- systemPromptAddition mechanism
- Relationship to compaction, memory plugins, and session pruning
- Configuration reference and tips

Also:
- Add context-engine to docs nav (Agents > Fundamentals, after Context)
- Add /context-engine redirect
- Cross-link from context.md and compaction.md
Show the full workflow: install via openclaw plugins install,
enable in plugins.entries, then select in plugins.slots.contextEngine.
Uses lossless-claw as the concrete example.
- Rename 'Method' column to 'Member' with explicit Kind column since
  info is a property, not a callable method
- Document AssembleResult fields (estimatedTokens, systemPromptAddition)
  with types and optionality
- Add lifecycle timing notes for bootstrap, ingestBatch, and dispose
  so plugin authors know when each is invoked
@jalehman
jalehman force-pushed the docs/context-engine branch from 8e1c75f to ff0481a Compare March 17, 2026 07:14
@jalehman
jalehman merged commit ff0481a into openclaw:main Mar 17, 2026
24 of 25 checks passed
@jalehman
jalehman deleted the docs/context-engine branch March 17, 2026 07:19
Comment on lines +108 to +109
When a plugin engine sets `ownsCompaction: true`, OpenClaw delegates all
compaction decisions to the engine and does not run built-in auto-compaction.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hi @jalehman ,

First off, thanks for defining the contextEngine plugin interface. It opens the door for more plugins to experiment with context management in OpenClaw, including different approaches to session context handling, long-term memory, and retrieval augmentation.

I wanted to add one point about the semantics of ownsCompaction and the compaction boundary for context-engine plugins.

Now that the contextEngine interface exists, there seem to be two reasonable plugin models:

  • plugins that fully own compaction themselves
  • plugins that mainly provide long-term memory, retrieval augmentation, or other specialized context-management behavior, while leaving short-term session compaction to OpenClaw

In the second model, ownsCompaction: false is a very natural choice. But I think two things are still not explicit enough today:

  1. the actual runtime meaning — and risk — of ownsCompaction: false
  2. how a non-owning plugin is expected to safely delegate back to built-in / legacy compaction

My understanding of the current runtime behavior is:

  • ownsCompaction: true means the plugin owns the compaction lifecycle
  • ownsCompaction: false means the plugin does not own that lifecycle
  • but it is still the active context engine
  • and when compaction is needed, the active engine’s compact() is still called directly

So ownsCompaction: false does not mean the runtime will automatically fall back to built-in / legacy compaction.

That distinction matters, because if an active engine:

  • sets ownsCompaction: false
  • but implements compact() as a no-op

then a few things can go wrong:

  • manual /compact can degrade into Compaction skipped, which looks harmless but may actually mean no real compaction happened
  • overflow recovery loses the normal compaction path, so recovery from context overflow becomes weaker
  • plugin authors may incorrectly conclude that “if I don’t own compaction, compact() can just be a stub”
  • comments or logs may imply “delegating to runtime” even when no real delegation happens, which makes debugging harder

So I think the documentation should explicitly say a few things:

  • the actual runtime meaning of ownsCompaction: false
  • that it does not imply automatic fallback
  • that a no-op compact() is unsafe for an active engine
  • that there are really two valid implementation modes:
    • owning mode: the plugin implements and owns compaction
    • delegating mode: the plugin does not own the algorithm, but its compact() must bridge to an officially supported built-in compaction path

Separately, if OpenClaw wants to formally support delegating mode, I think it should expose a stable compact bridge API so non-owning plugins can safely delegate to built-in / legacy compaction.

At a minimum, that API should:

  • be publicly supported for plugins, instead of relying on private runtime paths
  • clearly mean “delegate this compaction request to OpenClaw’s built-in compaction behavior”
  • be compatible with the current compact(params) input shape and CompactResult output
  • be able to carry the full runtime context needed for correct built-in compaction behavior, not just sessionId, sessionFile, and tokenBudget

I think this can be rolled out in two steps:

  1. Documentation first: clarify the semantics, the risks, and the owning vs. delegating distinction
  2. API second: add an officially supported compact bridge for non-owning plugins

That would make the context-engine model much clearer:

  • plugins that want to own compaction can keep doing that
  • plugins that do not want to implement their own compaction algorithm would still have a safe, supported delegation path

I think that would make both the OpenClaw semantics and the plugin ecosystem a lot clearer.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

#43920 (comment) is a concrete example of this pattern: the active engine sets ownsCompaction: false, but its compact() implementation is effectively a no-op, so compaction is not actually delegated and ends up being skipped for that engine.

@danhdoan danhdoan Mar 17, 2026

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.

@jalehman @JaySon-Huang Thanks for your comment here and in #43920 , I have checked the behavior of ownsCompaction in compact. You are right about how it works.
It also raises another concern in context-engine module. The ContextEngine interface requires plugins to implement compact(), but there's no way for a non-owning plugin to delegate back to the built-in compaction runtime. LegacyContextEngine works because it lives in core and can import compactEmbeddedPiSessionDirect directly, but an extension under extensions/ can't reach that.

with ownsCompaction: false and a no-op compact(), both /compact and overflow recovery are effectively disabled when ByteRover is the active engine.

My suggestion is to extract the delegation logic from LegacyContextEngine.compact() into a small standalone helper (delegateCompactionToRuntime) and exposes it through the plugin-sdk. This way any non-owning context engine plugin can delegate compaction properly.

The core change is small - one new file in src/context-engine/ and a refactor of legacy.ts to use the same helper. But since it touches core, I wanted to check with you first: would you be okay with this in the same PR, or would you prefer I split it into a separate upstream PR?

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

Labels

docs Improvements or additions to documentation maintainer Maintainer-authored PR size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants