Context Engine: Assembly, Compaction, and Subagent Lifecycle
Learn how OpenClaw's context engine assembles model context, compacts history, and manages subagent boundaries. This guide covers the default legacy engine, plugin installation, and lifecycle stages.
Read this 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
A context engine determines how OpenClaw assembles model context for every run: which messages get included, how older history is condensed, and how context is handled across subagent boundaries.
OpenClaw includes a default legacy engine that is active out of the box. Only opt for a plugin engine when you need different assembly, compaction, or cross-session recall behavior.
Quick start
Check which engine is active
openclaw doctor
# or inspect config directly:
cat ~/.openclaw/openclaw.json | jq '.plugins.slots.contextEngine'
Install a plugin engine
Installing a context engine plugin follows the same process as any other OpenClaw plugin.
Via npm
openclaw plugins install @martian-engineering/lossless-claw
Via a local path
openclaw plugins install -l ./my-context-engine
Enable and select the engine
// 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)
},
},
},
}
After installation and configuration, restart the gateway.
Switch back to legacy (optional)
Assign contextEngine the value "legacy" (or delete the key entirely, since "legacy" is the default).
How it works
Whenever OpenClaw executes a model prompt, the context engine engages at four distinct lifecycle stages:
1. Ingest
Triggered when a new message enters the session. The engine can persist or index the message in its own storage.
2. Assemble
Invoked prior to each model run. The engine supplies an ordered message set (plus an optional systemPromptAddition) that stays within the token limit.
3. Compact
Activated when the context window reaches capacity, or when the user triggers /compact. The engine condenses older history to reclaim space.
4. After turn
Executed after a run finishes. The engine can save state, launch background compaction, or refresh indexes.
An optional maintain() method exists for transcript upkeep (safe edits through runtimeContext.rewriteTranscriptEntries()) following bootstrap, a completed turn, or compaction. Configure info.turnMaintenanceMode: "background" to schedule it as deferred work rather than holding up the response.
For the bundled non-ACP Codex harness, OpenClaw mirrors the same lifecycle by mapping assembled context into Codex developer instructions and the current turn prompt. Codex retains its own native thread history and compactor.
Subagent lifecycle (optional)
Two optional subagent lifecycle hooks are invoked by OpenClaw:
-
prepareSubagentSpawn(method), Set up shared context state before a child run begins. The hook gets parent/child session keys,contextMode(eitherisolatedorfork), available transcript ids/files, and an optional TTL. If a rollback handle is returned, OpenClaw calls it when spawn fails after preparation succeeded. Native subagent spawns requestinglightContextthat resolve tocontextMode="isolated"deliberately bypass this hook, so the child starts with the lightweight bootstrap context, skipping any context-engine-managed pre-spawn state. -
onSubagentEnded(method), Perform cleanup when a subagent session ends or gets swept.
System prompt addition
The assemble method may return a systemPromptAddition string. OpenClaw places this at the front of the system prompt for that run. This mechanism lets engines add dynamic recall guidance, retrieval directives, or context-aware hints without relying on static workspace files.
The legacy engine
The built-in legacy engine maintains OpenClaw's original behavior:
- Ingest: does nothing (message persistence is handled directly by the session manager).
- Assemble: passes through (the runtime's existing sanitize → validate → limit pipeline builds the context).
- Compact: relies on the built-in summarization compaction, which produces one summary of older messages while leaving recent ones untouched.
- After turn: does nothing.
This legacy engine registers no tools and offers no systemPromptAddition.
When no plugins.slots.contextEngine is configured (or it is set to "legacy"), this engine is selected automatically.
Plugin engines
A plugin can register a context engine through 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,
acceptedHostParams: ["sessionKey", "runtimeContext"],
transcriptSemantics: {
currentTurnFence: "before-current-turn-entry-v1",
turnAdvancementIdempotency: "atomic-idempotent-v1",
},
},
async ingest({ sessionId, message, isHeartbeat }) {
// Store the message in your data store
return { ingested: true };
},
async assemble({
sessionId,
sessionKey,
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,
agentSessionKey: sessionKey,
}),
};
},
async compact({ sessionId, force }) {
// Summarize older context
return { ok: true, compacted: true };
},
async commitTurn({ advancementKey, messages }) {
// Atomically store the accepted turn and advancementKey. Return
// "duplicate" when that exact key was committed by an earlier retry.
return await commitAcceptedTurn({
advancementKey,
messages,
});
},
}));
}
The factory ctx accepts optional config, agentDir, and workspaceDir values, letting plugins set up per-agent or per-workspace state before the first lifecycle call. Ahead of a non-legacy assemble() invocation, the host finishes registered async memory prompt preparation. The synchronous buildMemorySystemPromptAddition(...) helper reads that immutable run snapshot; forward the supplied tool, citation, agent, and session context without modification.
Then activate it in the config:
{
plugins: {
slots: {
contextEngine: "my-engine",
},
entries: {
"my-engine": {
enabled: true,
},
},
},
}
The ContextEngine interface
Required members:
| Member | Kind | Purpose |
|---|---|---|
info | Property | Engine id, name, version, accepted host parameters, 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 |
Set info.acceptedHostParams to limit which host-added lifecycle fields are passed to the engine. The current set of keys is sessionKey, prompt, runtimeSettings, sessionTarget, and runtimeContext. OpenClaw combines this declaration with the fields available for each lifecycle method, so keys that are not declared or are unrecognized are never injected. Engines that omit this declaration receive all current host fields; when the engine validates a more restrictive input shape, declare an explicit list that includes [].
For durable admitted turns, declare both transcript semantics:
currentTurnFence: "before-current-turn-entry-v1"turnAdvancementIdempotency: "atomic-idempotent-v1"
and implement commitTurn(...) as a single atomic, idempotent write using advancementKey as the key. On the first write, return { status: "committed" }; when a host retry presents a key that is already committed, return { status: "duplicate" }. The messages payload covers only the inclusive range from the admitted user entry through the accepted terminal entry. Engines that require the earlier transcript during bootstrap or rebuild should access it via the transcript cursor API, readSessionTranscriptVisibleMessageDelta(...).
During bootstrap, maintenance, assembly, and retries, pre-turn transcript reads then observe the exact transcript prefix that precedes the admitted user message. The host invokes commitTurn only for the accepted successful turn; failed or aborted turns do not move context-engine state forward.
When the full declaration and method are absent, OpenClaw falls back to the legacy context path for the entire logical turn, retries included. The configured context-engine slot remains unchanged, and OpenClaw will attempt the configured engine again on the next logical turn. The same turn-local degradation applies when a declared fence cannot be honored because its exact admitted message is missing, has been rewritten, or a transcript cursor has already crossed it.
assemble returns an AssembleResult with:
-
messages(Message[], required), The ordered messages destined for the model. -
estimatedTokens(number, required), The engine's estimate of total tokens in the assembled context. OpenClaw relies on this for compaction threshold decisions and diagnostic reporting. -
systemPromptAddition(string), Prepended to the system prompt. -
promptAuthority(assembled" | "preassembly_may_overflow), Determines which token estimate the runner uses for preemptive overflow prechecks. The default is"assembled", meaning only the assembled prompt's estimate is checked for engines that do not own compaction. Engines settingownsCompaction: truehandle their own prompt admission, so OpenClaw skips the generic pre-prompt precheck by default. Set"preassembly_may_overflow"only when your assembled view could conceal overflow risk in the underlying transcript; the runner then keeps the generic precheck active and uses the larger of the assembled estimate and the pre-assembly (unwindowed) session-history estimate when deciding whether to preemptively compact. Either way, the messages you return are still what the model sees;promptAuthorityonly influences the precheck. -
contextProjection(ContextEngineProjection), Optional projection lifecycle for hosts with persistent backend threads (for example Codex app-server).mode: "thread_bootstrap"with a stableepochrequests that the host inject the assembled context once per epoch and reuse the backend thread until the epoch changes, rather than re-projecting every turn. Omit this field for normal per-turn projection.
compact returns a CompactResult. When compaction changes the active session identity, result.sessionTarget (a typed ContextEngineSessionTarget carrying the session identity and store scope) identifies the successor session that the next retry or turn must use; result.sessionId mirrors the successor id.
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). |
maintain(params) | Method | Transcript maintenance after bootstrap, a successful turn, or compaction. Use runtimeContext.rewriteTranscriptEntries() for safe rewrites. |
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. |
Runtime settings
Lifecycle hooks executing inside OpenClaw receive an optional runtimeSettings object. This is a versioned, read-only internal producer/consumer API surface: OpenClaw produces it for the selected context engine, and the context engine consumes it within lifecycle hooks. It is not rendered directly to users and does not establish a dedicated reporting surface.
schemaVersion: currently1runtime: OpenClaw host, runtime mode (normal,fallback, ordegraded), and optional harness/runtime idscontextEngineSelection: selected context engine id and selection sourceexecutionHost: host id and label for the surface invoking the hookmodel: requested model, resolved model, provider, and optional model familylimits: prompt token budget and max output tokens when knowndiagnostics: closed fallback and degraded reason codes when known
Fields that may be unknown are represented as null; discriminator fields such as runtime mode and selection source remain non-nullable. Engines that restrict host parameters and accept runtimeSettings must include it in info.acceptedHostParams.
Host requirements
Context engines can declare host capability requirements on info.hostRequirements. OpenClaw validates these requirements before starting the operation and fails closed with a descriptive error when the selected runtime cannot satisfy them.
For agent runs, declare assemble-before-prompt when the engine needs to take over the actual model prompt via assemble():
info: {
id: "my-context-engine",
name: "My Context Engine",
hostRequirements: {
"agent-run": {
requiredCapabilities: ["assemble-before-prompt"],
unsupportedMessage:
"Use the native Codex or OpenClaw embedded runtime, or select the legacy context engine.",
},
},
}
Native Codex and OpenClaw embedded agent runs meet assemble-before-prompt. Generic CLI backends do not, so engines that demand it are refused before the CLI process launches.
Failure isolation
OpenClaw keeps the selected plugin engine separate from the main reply path. If a non-legacy engine is absent, fails contract validation, throws during factory creation, or throws from a lifecycle method, OpenClaw isolates that engine for the current Gateway process and shifts context-engine work to the built-in legacy engine. The error gets logged with the failed operation so the operator can repair, update, or disable the plugin without the agent going silent.
Host requirement failures follow a different rule: when an engine declares that a runtime lacks a required capability, OpenClaw fails closed before the run starts. That shields engines that would corrupt state if they ran in an unsupported host.
ownsCompaction
ownsCompaction decides whether OpenClaw runtime's built-in in-attempt auto-compaction stays enabled for the run:
ownsCompaction: true
The engine takes charge of compaction behavior. OpenClaw turns off OpenClaw runtime's built-in auto-compaction and generic pre-prompt overflow precheck for that run, and the engine's compact() implementation handles /compact, provider overflow recovery compaction, and any proactive compaction it chooses to do in afterTurn(). OpenClaw still runs the pre-prompt overflow safeguard when the engine returns promptAuthority: "preassembly_may_overflow" from assemble().
ownsCompaction: false or unset
OpenClaw runtime's built-in auto-compaction may still run during prompt execution, but the active engine's compact() method is still invoked for /compact and overflow recovery.
Warning
ownsCompaction: falsedoes not mean OpenClaw automatically falls back to the legacy engine's compaction path.
That leaves two valid plugin patterns:
Owning mode
Build your own compaction algorithm and set ownsCompaction: true.
Delegating mode
Set ownsCompaction: false and have compact() call delegateCompactionToRuntime(...) from openclaw/plugin-sdk/core to rely on 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
{
plugins: {
slots: {
// Select the active context engine. Default: "legacy".
// Set to a plugin id to use a plugin engine.
contextEngine: "legacy",
},
},
}
Note
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.contextEngineonly selects which registered engine id OpenClaw resolves when it needs a context engine.
Note
Plugin uninstall: when you uninstall the plugin currently selected as
plugins.slots.contextEngine, OpenClaw resets the slot back to the default (legacy). The same reset behavior applies toplugins.slots.memory. No manual config edit is required.
Relationship to compaction and memory
Compaction
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
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 use buildMemorySystemPromptAddition(...) from openclaw/plugin-sdk/core, which converts the host-prepared memory prompt sections into a ready-to-prepend systemPromptAddition without exposing memory-plugin layout.
Session pruning
Trimming old tool results in-memory still runs regardless of which context engine is active.
Tips
- Use
openclaw doctorto 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 the selected plugin engine is quarantined for the current Gateway process. OpenClaw falls back to
legacyfor user turns so replies can continue, but you should still repair, update, disable, or uninstall the broken plugin. - For development, use
openclaw plugins install -l ./my-engineto link a local plugin directory without copying.
Related
- Compaction - summarizing long conversations
- Context - how context is built for agent turns
- Plugin Architecture - registering context engine plugins
- Plugin manifest - plugin manifest fields
- Plugins - plugin overview