docs/concepts/context-engine.md
A context engine controls how OpenClaw builds model context for each run: which messages to include, how to summarize older history, and how to manage context across subagent boundaries.
OpenClaw ships with a built-in legacy engine and uses it by default — most users never need to change this. Install and select a plugin engine only when you want different assembly, compaction, or cross-session recall behavior.
<Tabs>
<Tab title="From npm">
```bash
openclaw plugins install @martian-engineering/lossless-claw
```
</Tab>
<Tab title="From a local path">
```bash
openclaw plugins install -l ./my-context-engine
```
</Tab>
</Tabs>
Restart the gateway after installing and configuring.
Every time OpenClaw runs a model prompt, the context engine participates at four lifecycle points:
<AccordionGroup> <Accordion title="1. Ingest"> Called when a new message is added to the session. The engine can store or index the message in its own data store. </Accordion> <Accordion title="2. Assemble"> Called before each model run. The engine returns an ordered set of messages (and an optional `systemPromptAddition`) that fit within the token budget. </Accordion> <Accordion title="3. Compact"> Called when the context window is full, or when the user runs `/compact`. The engine summarizes older history to free space. </Accordion> <Accordion title="4. After turn"> Called after a run completes. The engine can persist state, trigger background compaction, or update indexes. </Accordion> </AccordionGroup>For the bundled non-ACP Codex harness, OpenClaw applies the same lifecycle by projecting assembled context into Codex developer instructions and the current turn prompt. Codex still owns its native thread history and native compactor.
OpenClaw calls two optional subagent lifecycle hooks:
<ParamField path="prepareSubagentSpawn" type="method"> Prepare shared context state before a child run starts. The hook receives parent/child session keys, `contextMode` (`isolated` or `fork`), available transcript ids/files, and optional TTL. If it returns a rollback handle, OpenClaw calls it when spawn fails after preparation succeeds. </ParamField> <ParamField path="onSubagentEnded" type="method"> Clean up when a subagent session completes or is swept. </ParamField>The assemble method can return a systemPromptAddition string. OpenClaw prepends this to the system prompt for the run. This lets engines inject dynamic recall guidance, retrieval instructions, or context-aware hints without requiring static workspace files.
The built-in legacy engine preserves OpenClaw's original behavior:
The legacy engine does not register tools or provide a systemPromptAddition.
When no plugins.slots.contextEngine is set (or it's set to "legacy"), this engine is used automatically.
A plugin can register a context engine using the plugin API:
import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";
export default function register(api) {
api.registerContextEngine("my-engine", (ctx) => ({
info: {
id: "my-engine",
name: "My Context Engine",
ownsCompaction: true,
},
async ingest({ sessionId, message, isHeartbeat }) {
// Store the message in your data store
return { ingested: true };
},
async assemble({ sessionId, messages, tokenBudget, availableTools, citationsMode }) {
// Return messages that fit the budget
return {
messages: buildContext(messages, tokenBudget),
estimatedTokens: countTokens(messages),
systemPromptAddition: buildMemorySystemPromptAddition({
availableTools: availableTools ?? new Set(),
citationsMode,
}),
};
},
async compact({ sessionId, force }) {
// Summarize older context
return { ok: true, compacted: true };
},
}));
}
The factory ctx includes optional config, agentDir, and workspaceDir
values so plugins can initialize per-agent or per-workspace state before the
first lifecycle hook runs.
Then enable it in config:
{
plugins: {
slots: {
contextEngine: "my-engine",
},
entries: {
"my-engine": {
enabled: true,
},
},
},
}
Required members:
| Member | Kind | Purpose |
|---|---|---|
info | Property | Engine id, name, version, and whether it owns compaction |
ingest(params) | Method | Store a single message |
assemble(params) | Method | Build context for a model run (returns AssembleResult) |
compact(params) | Method | Summarize/reduce context |
assemble returns an AssembleResult with:
compact returns a CompactResult. When compaction rotates the active
transcript, result.sessionId and result.sessionFile identify the successor
session that the next retry or turn must use.
Optional members:
| 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. |
afterTurn(params) | Method | Post-run lifecycle work (persist state, trigger background compaction). |
prepareSubagentSpawn(params) | Method | Set up shared state for a child session before it starts. |
onSubagentEnded(params) | Method | Clean up after a subagent ends. |
dispose() | Method | Release resources. Called during gateway shutdown or plugin reload — not per-session. |
ownsCompaction controls whether Pi's built-in in-attempt auto-compaction stays enabled for the run:
That means there are two valid plugin patterns:
<Tabs> <Tab title="Owning mode"> Implement your own compaction algorithm and set `ownsCompaction: true`. </Tab> <Tab title="Delegating mode"> Set `ownsCompaction: false` and have `compact()` call `delegateCompactionToRuntime(...)` from `openclaw/plugin-sdk/core` to use OpenClaw's built-in compaction behavior. </Tab> </Tabs>A no-op compact() is unsafe for an active non-owning engine because it disables the normal /compact and overflow-recovery compaction path for that engine slot.
{
plugins: {
slots: {
// Select the active context engine. Default: "legacy".
// Set to a plugin id to use a plugin engine.
contextEngine: "legacy",
},
},
}
openclaw doctor to verify your engine is loading correctly.plugins.slots.contextEngine back to "legacy".openclaw plugins install -l ./my-engine to link a local plugin directory without copying.