docs: add context engine documentation#46409
Conversation
a473402 to
8075e5e
Compare
Greptile SummaryThis PR adds a dedicated Two minor gaps worth addressing before the page is final:
Confidence Score: 4/5
Prompt To Fix All With AIThis 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 |
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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). | |
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
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 👍 / 👎.
716fbda to
8e1c75f
Compare
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
8e1c75f to
ff0481a
Compare
| When a plugin engine sets `ownsCompaction: true`, OpenClaw delegates all | ||
| compaction decisions to the engine and does not run built-in auto-compaction. |
There was a problem hiding this comment.
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:
- the actual runtime meaning — and risk — of
ownsCompaction: false - 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: truemeans the plugin owns the compaction lifecycleownsCompaction: falsemeans 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
/compactcan degrade intoCompaction 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 andCompactResultoutput - be able to carry the full runtime context needed for correct built-in compaction behavior, not just
sessionId,sessionFile, andtokenBudget
I think this can be rolled out in two steps:
- Documentation first: clarify the semantics, the risks, and the owning vs. delegating distinction
- 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.
There was a problem hiding this comment.
#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.
There was a problem hiding this comment.
@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?
Summary
Adds dedicated documentation for the pluggable context engine system introduced in #22201.
The
plugins.slots.contextEngineconfig 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
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,ownsCompactionsemantics, subagent hooks,systemPromptAddition, and configuration reference./context-engine→/concepts/context-enginecontext.mdandcompaction.mdto link to the new page.What's documented
api.registerContextEngine()ownsCompactionflag and its effect on auto-compactionprepareSubagentSpawn,onSubagentEnded)systemPromptAdditionplugins.slots.contextEngineFollows the Diataxis framework: structured as a mix of explanation (how it works) and how-to (building a plugin engine).