@parad0x_labs/openclaw-context-capsule 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # context-capsule — OpenClaw ContextEngine plugin
2
+
3
+ Compresses agent session history before it reaches the LLM using
4
+ [`@parad0x_labs/context-capsule`](https://github.com/Parad0x-Labs/dna-x402/tree/main/packages/context-capsule).
5
+ Sessions under 20 messages pass through unchanged. Longer sessions have their
6
+ older history compressed into a capsule summary (injected as a system message)
7
+ while the last 10 messages are kept verbatim — giving the model full coherence
8
+ on recent turns without paying for the full transcript.
9
+
10
+ **Most useful for:** local models (Ollama, LM Studio) and GPT-4 where context
11
+ cost matters. Claude users with a 200k context window and built-in compaction
12
+ enabled may not need this.
13
+
14
+ ## Benchmark
15
+
16
+ | Metric | Result | CI gate |
17
+ |---|---|---|
18
+ | Token savings | 99.3% | >= 95% |
19
+ | Recovery score | 100% | >= 90% |
20
+ | Runtime | 29ms | < 1000ms |
21
+
22
+ Reproduce locally:
23
+
24
+ ```sh
25
+ cd packages/context-capsule
26
+ npm run bench:public
27
+ ```
28
+
29
+ CI fails if savings drop below 95% or recovery falls below 90%.
30
+
31
+ ## Activation
32
+
33
+ ```jsonc
34
+ // openclaw.json
35
+ {
36
+ "plugins": {
37
+ "slots": {
38
+ "contextEngine": "context-capsule"
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Config options
45
+
46
+ | Key | Default | Description |
47
+ |---|---|---|
48
+ | `minMessages` | `20` | Sessions shorter than this pass through unchanged |
49
+ | `keepRecentMessages` | `10` | Recent messages kept verbatim after compression |
50
+
51
+ ```jsonc
52
+ {
53
+ "plugins": {
54
+ "entries": {
55
+ "context-capsule": {
56
+ "minMessages": 15,
57
+ "keepRecentMessages": 8
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ```
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "context-capsule",
3
+ "activation": {
4
+ "onStartup": false
5
+ },
6
+ "enabledByDefault": false,
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "properties": {
11
+ "minMessages": {
12
+ "type": "integer",
13
+ "minimum": 1,
14
+ "description": "Minimum session length (messages) before compression is applied. Default: 20."
15
+ },
16
+ "keepRecentMessages": {
17
+ "type": "integer",
18
+ "minimum": 1,
19
+ "description": "Number of most-recent messages kept verbatim after compression. Default: 10."
20
+ }
21
+ }
22
+ }
23
+ }
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@parad0x_labs/openclaw-context-capsule",
3
+ "version": "0.1.0",
4
+ "description": "context-capsule ContextEngine for OpenClaw — 99.3% token reduction with recovery-score gate. Works with Ollama, GPT, Mistral, Claude.",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "files": ["src/", "openclaw.plugin.json", "README.md"],
8
+ "keywords": ["openclaw", "openclaw-plugin", "context-compression", "llm", "token-savings", "ollama", "context-capsule"],
9
+ "openclaw": { "extensions": ["./src/index.ts"] },
10
+ "dependencies": { "@parad0x_labs/context-capsule": "^0.1.0" },
11
+ "engines": { "node": ">=22" },
12
+ "license": "MIT",
13
+ "repository": { "type": "git", "url": "https://github.com/Parad0x-Labs/dna-x402" }
14
+ }
package/src/index.ts ADDED
@@ -0,0 +1,224 @@
1
+ /**
2
+ * context-capsule ContextEngine plugin for OpenClaw.
3
+ *
4
+ * Compresses session history before it reaches the LLM, achieving ~99% token
5
+ * reduction while keeping a verbatim tail of recent messages for coherence.
6
+ * Sessions under the minMessages threshold pass through unchanged.
7
+ */
8
+
9
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
10
+ import type {
11
+ AssembleResult,
12
+ CompactResult,
13
+ ContextEngine,
14
+ ContextEngineInfo,
15
+ IngestResult,
16
+ } from "openclaw/plugin-sdk/context-engine";
17
+ import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
18
+ // @ts-expect-error — external package, types may not be present at build time
19
+ import { compressContext, injectCapsule } from "@parad0x_labs/context-capsule";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Configuration defaults
23
+ // ---------------------------------------------------------------------------
24
+
25
+ const DEFAULT_MIN_MESSAGES = 20;
26
+ const DEFAULT_KEEP_RECENT = 10;
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Helpers
30
+ // ---------------------------------------------------------------------------
31
+
32
+ type SimpleMessage = { role: string; content: string };
33
+
34
+ /**
35
+ * Convert an OpenClaw AgentMessage to the plain {role, content} shape expected
36
+ * by @parad0x_labs/context-capsule.
37
+ *
38
+ * Handles:
39
+ * - "toolResult" role → "tool"
40
+ * - Content that is already a string → used as-is
41
+ * - Content that is an array of content blocks → joined to a single string
42
+ */
43
+ function normalizeMessages(messages: AgentMessage[]): SimpleMessage[] {
44
+ return messages.map((msg) => {
45
+ const role = msg.role === "toolResult" ? "tool" : (msg.role as string);
46
+
47
+ let content: string;
48
+ if (!("content" in msg) || (msg as { content: unknown }).content == null) {
49
+ content = "";
50
+ } else if (typeof (msg as { content: unknown }).content === "string") {
51
+ content = (msg as { content: string }).content;
52
+ } else if (Array.isArray((msg as { content: unknown[] }).content)) {
53
+ const blocks = (msg as { content: unknown[] }).content;
54
+ content = blocks
55
+ .map((block) => {
56
+ if (!block || typeof block !== "object") return "";
57
+ const b = block as Record<string, unknown>;
58
+ if (b.type === "text" && typeof b.text === "string") return b.text;
59
+ if (b.type === "toolResult" || b.type === "tool_result") {
60
+ const inner = b.content;
61
+ if (typeof inner === "string") return inner;
62
+ if (Array.isArray(inner)) {
63
+ return (inner as unknown[])
64
+ .map((ib) => {
65
+ if (!ib || typeof ib !== "object") return "";
66
+ const ibr = ib as Record<string, unknown>;
67
+ return ibr.type === "text" && typeof ibr.text === "string" ? ibr.text : "";
68
+ })
69
+ .filter(Boolean)
70
+ .join("\n");
71
+ }
72
+ return "";
73
+ }
74
+ return "";
75
+ })
76
+ .filter(Boolean)
77
+ .join("\n");
78
+ } else {
79
+ content = "";
80
+ }
81
+
82
+ return { role, content };
83
+ });
84
+ }
85
+
86
+ /** Rough token estimate: ~4 chars per token */
87
+ function estimateTokens(messages: AgentMessage[]): number {
88
+ return messages.reduce((sum, m) => {
89
+ const c =
90
+ "content" in m && typeof (m as { content: unknown }).content === "string"
91
+ ? ((m as { content: string }).content).length
92
+ : 0;
93
+ return sum + Math.ceil(c / 4);
94
+ }, 0);
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // ContextEngine implementation
99
+ // ---------------------------------------------------------------------------
100
+
101
+ type CapsuleConfig = {
102
+ minMessages: number;
103
+ keepRecentMessages: number;
104
+ };
105
+
106
+ class ContextCapsuleEngine implements ContextEngine {
107
+ readonly info: ContextEngineInfo = {
108
+ id: "context-capsule",
109
+ name: "Context Capsule",
110
+ version: "0.1.0",
111
+ ownsCompaction: false,
112
+ turnMaintenanceMode: "background",
113
+ };
114
+
115
+ private readonly cfg: CapsuleConfig;
116
+
117
+ constructor(cfg: Partial<CapsuleConfig> = {}) {
118
+ this.cfg = {
119
+ minMessages: cfg.minMessages ?? DEFAULT_MIN_MESSAGES,
120
+ keepRecentMessages: cfg.keepRecentMessages ?? DEFAULT_KEEP_RECENT,
121
+ };
122
+ }
123
+
124
+ // Required: ingest — accept each message as the transcript grows
125
+ async ingest(_params: {
126
+ sessionId: string;
127
+ sessionKey?: string;
128
+ message: AgentMessage;
129
+ isHeartbeat?: boolean;
130
+ }): Promise<IngestResult> {
131
+ return { ingested: true };
132
+ }
133
+
134
+ // Required: assemble — build the context window for the next model call
135
+ async assemble(params: {
136
+ sessionId: string;
137
+ sessionKey?: string;
138
+ messages: AgentMessage[];
139
+ tokenBudget?: number;
140
+ availableTools?: Set<string>;
141
+ model?: string;
142
+ prompt?: string;
143
+ }): Promise<AssembleResult> {
144
+ const { messages } = params;
145
+
146
+ // Short sessions: pass through unchanged
147
+ if (messages.length < this.cfg.minMessages) {
148
+ return {
149
+ messages,
150
+ estimatedTokens: estimateTokens(messages),
151
+ };
152
+ }
153
+
154
+ // Compress the older history, keep the tail verbatim
155
+ const tail = messages.slice(-this.cfg.keepRecentMessages);
156
+ const older = messages.slice(0, -this.cfg.keepRecentMessages);
157
+ const normalized = normalizeMessages(older);
158
+
159
+ let summaryText: string;
160
+ try {
161
+ const capsule = await compressContext(normalized);
162
+ const injected = await injectCapsule(capsule);
163
+ summaryText = typeof injected === "string" ? injected : JSON.stringify(injected);
164
+ } catch {
165
+ // Fallback: skip compression, return original messages
166
+ return {
167
+ messages,
168
+ estimatedTokens: estimateTokens(messages),
169
+ promptAuthority: "preassembly_may_overflow",
170
+ };
171
+ }
172
+
173
+ // Prepend capsule as a system context message
174
+ const capsuleSystemMessage = {
175
+ role: "system",
176
+ content: `[Context Capsule — compressed history]\n${summaryText}`,
177
+ } as unknown as AgentMessage;
178
+
179
+ const assembled = [capsuleSystemMessage, ...tail];
180
+
181
+ return {
182
+ messages: assembled,
183
+ estimatedTokens: estimateTokens(assembled),
184
+ systemPromptAddition:
185
+ "Earlier conversation history has been compressed into the context capsule above.",
186
+ };
187
+ }
188
+
189
+ // Required: compact — delegate to runtime (engine does not own compaction)
190
+ async compact(_params: {
191
+ sessionId: string;
192
+ sessionKey?: string;
193
+ sessionFile: string;
194
+ tokenBudget?: number;
195
+ force?: boolean;
196
+ currentTokenCount?: number;
197
+ compactionTarget?: "budget" | "threshold";
198
+ customInstructions?: string;
199
+ runtimeContext?: unknown;
200
+ abortSignal?: AbortSignal;
201
+ }): Promise<CompactResult> {
202
+ return {
203
+ ok: true,
204
+ compacted: false,
205
+ reason: "delegated-to-runtime",
206
+ };
207
+ }
208
+ }
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // Plugin registration
212
+ // ---------------------------------------------------------------------------
213
+
214
+ export default definePluginEntry({
215
+ id: "context-capsule",
216
+ name: "Context Capsule",
217
+ description:
218
+ "99.3% token reduction on agent sessions via @parad0x_labs/context-capsule. " +
219
+ "Works with Ollama, LM Studio, GPT-4, Mistral, and Claude. " +
220
+ "Public benchmark with recovery-score gate in CI.",
221
+ register(api) {
222
+ api.registerContextEngine("context-capsule", (_ctx) => new ContextCapsuleEngine());
223
+ },
224
+ });