Files
openclaw/docs/concepts/context-engine.md

287 lines
11 KiB
Markdown

---
summary: "Context engine: pluggable context assembly, compaction, and subagent lifecycle"
read_when:
- You want to understand how OpenClaw assembles model context
- You are switching between the legacy engine and a plugin engine
- You are building a context engine plugin
title: "Context engine"
---
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.
## Quick start
Check which engine is active:
```bash
openclaw doctor
# or inspect config directly:
cat ~/.openclaw/openclaw.json | jq '.plugins.slots.contextEngine'
```
### Installing a context engine plugin
Context engine plugins are installed like any other OpenClaw plugin. Install
first, then select the engine in the slot:
```bash
# Install from npm
openclaw plugins install @martian-engineering/lossless-claw
# Or install from a local path (for development)
openclaw plugins install -l ./my-context-engine
```
Then enable the plugin and select it as the active engine in your config:
```json5
// openclaw.json
{
plugins: {
slots: {
contextEngine: "lossless-claw", // must match the plugin's registered engine id
},
entries: {
"lossless-claw": {
enabled: true,
// Plugin-specific config goes here (see the plugin's docs)
},
},
},
}
```
Restart the gateway after installing and configuring.
To switch back to the built-in engine, set `contextEngine` to `"legacy"` (or
remove the key entirely — `"legacy"` is the default).
## How it works
Every time OpenClaw runs a model prompt, the context engine participates at
four lifecycle points:
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.
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.
3. **Compact** — called when the context window is full, or when the user runs
`/compact`. The engine summarizes older history to free space.
4. **After turn** — called after a run completes. The engine can persist state,
trigger background compaction, or update indexes.
### Subagent lifecycle (optional)
OpenClaw calls two optional subagent lifecycle hooks:
- **prepareSubagentSpawn** — 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.
- **onSubagentEnded** — clean up when a subagent session completes or is swept.
### System prompt addition
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 legacy engine
The built-in `legacy` engine preserves OpenClaw's original behavior:
- **Ingest**: no-op (the session manager handles message persistence directly).
- **Assemble**: pass-through (the existing sanitize → validate → limit pipeline
in the runtime handles context assembly).
- **Compact**: delegates to the built-in summarization compaction, which creates
a single summary of older messages and keeps recent messages intact.
- **After turn**: no-op.
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.
## Plugin engines
A plugin can register a context engine using the plugin API:
```ts
import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";
export default function register(api) {
api.registerContextEngine("my-engine", () => ({
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 };
},
}));
}
```
Then enable it in config:
```json5
{
plugins: {
slots: {
contextEngine: "my-engine",
},
entries: {
"my-engine": {
enabled: true,
},
},
},
}
```
### The ContextEngine interface
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:
- `messages` — the ordered messages to send to the model.
- `estimatedTokens` (required, `number`) — the engine's estimate of total
tokens in the assembled context. OpenClaw uses this for compaction threshold
decisions and diagnostic reporting.
- `systemPromptAddition` (optional, `string`) — prepended to the system prompt.
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
`ownsCompaction` controls whether Pi's built-in in-attempt auto-compaction stays
enabled for the run:
- `true` — the engine owns compaction behavior. OpenClaw disables Pi's built-in
auto-compaction for that run, and the engine's `compact()` implementation is
responsible for `/compact`, overflow recovery compaction, and any proactive
compaction it wants to do in `afterTurn()`.
- `false` or unset — Pi's built-in auto-compaction may still run during prompt
execution, but the active engine's `compact()` method is still called for
`/compact` and overflow recovery.
`ownsCompaction: false` does **not** mean OpenClaw automatically falls back to
the legacy engine's compaction path.
That means there are two valid plugin patterns:
- **Owning mode** — implement your own compaction algorithm and set
`ownsCompaction: true`.
- **Delegating mode** — set `ownsCompaction: false` and have `compact()` call
`delegateCompactionToRuntime(...)` from `openclaw/plugin-sdk/core` to use
OpenClaw's built-in compaction behavior.
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.
## Configuration reference
```json5
{
plugins: {
slots: {
// Select the active context engine. Default: "legacy".
// Set to a plugin id to use a plugin engine.
contextEngine: "legacy",
},
},
}
```
The slot is exclusive at run time — only one registered context engine is
resolved for a given run or compaction operation. Other enabled
`kind: "context-engine"` plugins can still load and run their registration
code; `plugins.slots.contextEngine` only selects which registered engine id
OpenClaw resolves when it needs a context engine.
## Relationship to compaction and memory
- **Compaction** is one responsibility of the context engine. The legacy engine
delegates to OpenClaw's built-in summarization. Plugin engines can implement
any compaction strategy (DAG summaries, vector retrieval, etc.).
- **Memory plugins** (`plugins.slots.memory`) are separate from context engines.
Memory plugins provide search/retrieval; context engines control what the
model sees. They can work together — a context engine might use memory
plugin data during assembly. Plugin engines that want the active memory
prompt path should prefer `buildMemorySystemPromptAddition(...)` from
`openclaw/plugin-sdk/core`, which converts the active memory prompt sections
into a ready-to-prepend `systemPromptAddition`. If an engine needs lower-level
control, it can still pull raw lines from
`openclaw/plugin-sdk/memory-host-core` via
`buildActiveMemoryPromptSection(...)`.
- **Session pruning** (trimming old tool results in-memory) still runs
regardless of which context engine is active.
## Tips
- Use `openclaw doctor` to verify your engine is loading correctly.
- If switching engines, existing sessions continue with their current history.
The new engine takes over for future runs.
- Engine errors are logged and surfaced in diagnostics. If a plugin engine
fails to register or the selected engine id cannot be resolved, OpenClaw
does not fall back automatically; runs fail until you fix the plugin or
switch `plugins.slots.contextEngine` back to `"legacy"`.
- For development, use `openclaw plugins install -l ./my-engine` to link a
local plugin directory without copying.
See also: [Compaction](/concepts/compaction), [Context](/concepts/context),
[Plugins](/tools/plugin), [Plugin manifest](/plugins/manifest).
## Related
- [Context](/concepts/context) — how context is built for agent turns
- [Plugin Architecture](/plugins/architecture) — registering context engine plugins
- [Compaction](/concepts/compaction) — summarizing long conversations