@psiclawops/hypercompositor 0.8.5 → 0.9.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/LICENSE ADDED
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to the Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by the Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding any notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright 2026 PsiClawOps
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # HyperCompositor
2
+
3
+ HyperCompositor is the OpenClaw context-engine plugin for HyperMem. It connects HyperMem's adaptive context lifecycle, recall, trimming, compaction, and diagnostics into the OpenClaw context assembly path.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @psiclawops/hypercompositor
9
+ ```
10
+
11
+ See the main HyperMem install guide for runtime staging and OpenClaw activation:
12
+
13
+ - https://github.com/PsiClawOps/hypermem
14
+ - https://github.com/PsiClawOps/hypermem/blob/main/INSTALL.md
15
+
16
+ ## Package
17
+
18
+ - Plugin id: `hypercompositor`
19
+ - Plugin kind: `context-engine`
20
+ - License: Apache-2.0
package/dist/index.d.ts CHANGED
@@ -53,8 +53,36 @@ declare function assembleTrace(fields: {
53
53
  path: 'cold' | 'replay' | 'subagent';
54
54
  toolLoop: boolean;
55
55
  msgCount: number;
56
+ prefixChanged?: boolean;
57
+ prefixHash?: string;
58
+ rerankerStatus?: string;
59
+ rerankerCandidates?: number;
60
+ rerankerProvider?: string | null;
61
+ slotSpans?: Record<string, {
62
+ allocated: number;
63
+ filled: number;
64
+ overflow: boolean;
65
+ }>;
66
+ compactionEligibleCount?: number;
67
+ compactionEligibleRatio?: number;
68
+ compactionProcessedCount?: number;
69
+ composeTopicSource?: 'request-topic-id' | 'session-topic-map' | 'none';
70
+ composeTopicState?: 'no-active-topic' | 'active-topic-ready' | 'active-topic-missing-stamped-history' | 'history-disabled';
71
+ composeTopicMessageCount?: number;
72
+ composeTopicStampedMessageCount?: number;
73
+ composeTopicTelemetryStatus?: 'emitted' | 'intentionally-omitted';
56
74
  }): void;
57
75
  declare function degradationTelemetry(fields: DegradationTelemetryFields): void;
76
+ declare function lifecyclePolicyTelemetry(fields: {
77
+ path: 'compose.eviction' | 'compose.preRecall' | 'afterTurn.gradient';
78
+ agentId: string;
79
+ sessionKey: string;
80
+ band: string;
81
+ pressurePct?: number;
82
+ topicShiftConfidence?: number;
83
+ trimSoftTarget?: number;
84
+ reasons?: string[];
85
+ }): void;
58
86
  declare function nextTurnId(): string;
59
87
  declare const GUARD_TELEMETRY_REASONS: readonly ["warmstart-pressure-demoted", "reshape-downshift-demoted", "duplicate-claim-suppressed", "afterturn-secondary-demoted", "window-within-budget-skip", "pressure-accounting-anomaly"];
60
88
  type GuardTelemetryReason = typeof GUARD_TELEMETRY_REASONS[number];
@@ -107,6 +135,7 @@ export declare const __telemetryForTests: {
107
135
  assembleTrace: typeof assembleTrace;
108
136
  degradationTelemetry: typeof degradationTelemetry;
109
137
  guardTelemetry: typeof guardTelemetry;
138
+ lifecyclePolicyTelemetry: typeof lifecyclePolicyTelemetry;
110
139
  nextTurnId: typeof nextTurnId;
111
140
  beginTrimOwnerTurn: typeof beginTrimOwnerTurn;
112
141
  endTrimOwnerTurn: typeof endTrimOwnerTurn;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -22,12 +22,15 @@
22
22
  import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
23
23
  import { buildPluginConfigSchema } from 'openclaw/plugin-sdk/core';
24
24
  import { z } from 'zod';
25
- import { detectTopicShift, stripMessageMetadata, SessionTopicMap, applyToolGradientToWindow, OPENCLAW_BOOTSTRAP_FILES, rotateSessionContext, TRIM_SOFT_TARGET, TRIM_GROWTH_THRESHOLD, TRIM_HEADROOM_FRACTION, resolveTrimBudgets, formatToolChainStub, decideReplayRecovery, isReplayState, } from '@psiclawops/hypermem';
25
+ import { detectTopicShift, stripMessageMetadata, SessionTopicMap, applyToolGradientToWindow, OPENCLAW_BOOTSTRAP_FILES, rotateSessionContext, TRIM_SOFT_TARGET, TRIM_GROWTH_THRESHOLD, TRIM_HEADROOM_FRACTION, resolveTrimBudgets, resolveAdaptiveLifecyclePolicy, formatToolChainStub, decideReplayRecovery, isReplayState, recordOutputMetrics,
26
+ // Sprint 3: unified pressure signal
27
+ computeUnifiedPressure, PRESSURE_SOURCE, } from '@psiclawops/hypermem';
26
28
  import { evictStaleContent } from '@psiclawops/hypermem/image-eviction';
27
29
  import { repairToolPairs } from '@psiclawops/hypermem';
28
30
  import os from 'os';
29
31
  import path from 'path';
30
32
  import fs from 'fs/promises';
33
+ import { randomUUID } from 'node:crypto';
31
34
  import { fileURLToPath } from 'url';
32
35
  import fsSync from 'fs';
33
36
  let _telemetryStream = null;
@@ -107,6 +110,24 @@ function degradationTelemetry(fields) {
107
110
  // Telemetry must never throw
108
111
  }
109
112
  }
113
+ function lifecyclePolicyTelemetry(fields) {
114
+ if (!telemetryEnabled())
115
+ return;
116
+ const stream = getTelemetryStream();
117
+ if (!stream)
118
+ return;
119
+ try {
120
+ const record = {
121
+ event: 'lifecycle-policy',
122
+ ts: new Date().toISOString(),
123
+ ...fields,
124
+ };
125
+ stream.write(JSON.stringify(record) + '\n');
126
+ }
127
+ catch {
128
+ // Telemetry must never throw
129
+ }
130
+ }
110
131
  function nextTurnId() {
111
132
  _telemetryTurnCounter = (_telemetryTurnCounter + 1) >>> 0;
112
133
  return `${Date.now().toString(36)}-${_telemetryTurnCounter.toString(36)}`;
@@ -279,6 +300,7 @@ export const __telemetryForTests = {
279
300
  assembleTrace,
280
301
  degradationTelemetry,
281
302
  guardTelemetry,
303
+ lifecyclePolicyTelemetry,
282
304
  nextTurnId,
283
305
  beginTrimOwnerTurn,
284
306
  endTrimOwnerTurn,
@@ -464,6 +486,7 @@ function resolveConfiguredWindow(model) {
464
486
  // Subagent warming mode: 'full' | 'light' | 'off'. Default: 'light'.
465
487
  // Controls how much HyperMem context is injected into subagent sessions.
466
488
  let _subagentWarming = 'light';
489
+ const FORKED_CONTEXT_META_SLOT = 'forkedContextMeta';
467
490
  // Cache replay threshold: 15min default. Set to 0 in user config to disable.
468
491
  let _cacheReplayThresholdMs = 900_000;
469
492
  // ─── System overhead cache ────────────────────────────────────
@@ -579,6 +602,8 @@ async function loadUserConfig() {
579
602
  merged.eviction = { ...merged.eviction, ..._pluginConfig.eviction };
580
603
  if (_pluginConfig.embedding)
581
604
  merged.embedding = { ...merged.embedding, ..._pluginConfig.embedding };
605
+ if (_pluginConfig.reranker)
606
+ merged.reranker = { ...merged.reranker, ..._pluginConfig.reranker };
582
607
  if (Object.keys(fileConfig).length > 0 && Object.keys(_pluginConfig).filter(k => k !== 'hyperMemPath' && k !== 'dataDir').length > 0) {
583
608
  console.log('[hypermem-plugin] Note: migrating config.json keys to plugins.entries.hypercompositor.config in openclaw.json is recommended');
584
609
  }
@@ -663,15 +688,19 @@ async function getHyperMem() {
663
688
  `effective history budget: ${_contextWindowSize - reservedTokens} tokens`);
664
689
  verboseLog(`[hypermem-plugin] warmCacheReplayThresholdMs=${_cacheReplayThresholdMs}`);
665
690
  verboseLog(`[hypermem-plugin] contextWindowOverrides keys=${Object.keys(_contextWindowOverrides).join(', ') || '(none)'}`);
691
+ const cacheConfig = userConfig.cache;
666
692
  const instance = await HyperMem.create({
667
693
  dataDir: _pluginConfig.dataDir ?? path.join(os.homedir(), '.openclaw/hypermem'),
668
694
  cache: {
669
- keyPrefix: 'hm:',
670
- sessionTTL: 14400, // 4h for system/identity/meta slots
671
- historyTTL: 86400, // 24h for history ages out, not count-trimmed
695
+ keyPrefix: cacheConfig?.keyPrefix ?? 'hm:',
696
+ sessionTTL: cacheConfig?.sessionTTL ?? 14400, // 4h default for system/identity/meta slots
697
+ historyTTL: cacheConfig?.historyTTL ?? 86400, // 24h default for history/cursor hot cache
672
698
  },
673
699
  ...(userConfig.compositor ? { compositor: userConfig.compositor } : {}),
674
700
  ...(_embeddingConfig ? { embedding: _embeddingConfig } : {}),
701
+ ...(userConfig.reranker
702
+ ? { reranker: userConfig.reranker }
703
+ : {}),
675
704
  });
676
705
  _hm = instance;
677
706
  // Wire up fleet store and background indexer from dynamic module
@@ -790,6 +819,33 @@ function resolveAssistantTokenCount(msg, runtimeContext) {
790
819
  }
791
820
  return undefined;
792
821
  }
822
+ function resolveAssistantOutputTokenCount(msg, runtimeContext) {
823
+ const usage = msg.usage;
824
+ if (usage && typeof usage === 'object') {
825
+ const candidates = [
826
+ usage.output,
827
+ usage.outputTokens,
828
+ usage.output_tokens,
829
+ usage.completionTokens,
830
+ usage.completion_tokens,
831
+ usage.totalTokens,
832
+ usage.total_tokens,
833
+ usage.total,
834
+ ];
835
+ for (const candidate of candidates) {
836
+ if (typeof candidate === 'number' && Number.isFinite(candidate) && candidate > 0) {
837
+ return Math.floor(candidate);
838
+ }
839
+ }
840
+ }
841
+ const runtimeTokenCount = runtimeContext?.currentTokenCount;
842
+ if (typeof runtimeTokenCount === 'number' && Number.isFinite(runtimeTokenCount) && runtimeTokenCount > 0) {
843
+ return Math.floor(runtimeTokenCount);
844
+ }
845
+ const text = extractTextFromInboundContent(msg.content);
846
+ const tokenEstimate = Math.ceil(text.length / 4);
847
+ return tokenEstimate > 0 ? tokenEstimate : undefined;
848
+ }
793
849
  function collectNeutralToolPairStats(messages) {
794
850
  const callIds = new Set();
795
851
  const resultIds = new Set();
@@ -1775,7 +1831,9 @@ function createHyperMemEngine() {
1775
1831
  });
1776
1832
  const replayMarkerText = replayRecovery.emittedText;
1777
1833
  const preTrimTokens = runtimeTokens;
1778
- const pressure = preTrimTokens / effectiveBudget;
1834
+ // Sprint 3: unified pressure signal tool-loop assemble path
1835
+ const s3ToolLoopPressure = computeUnifiedPressure(preTrimTokens, effectiveBudget, PRESSURE_SOURCE.TOOLLOOP_RUNTIME_ARRAY);
1836
+ const pressure = s3ToolLoopPressure.fraction;
1779
1837
  // Pressure-tiered trim targets use a single authority: the working
1780
1838
  // message array. Redis drift is logged as an anomaly, never used as
1781
1839
  // a trim trigger. Replay recovery gets its own explicit bounded mode
@@ -1927,17 +1985,17 @@ function createHyperMemEngine() {
1927
1985
  const kept = keptClusters.flat();
1928
1986
  const keptCount = processedConvMsgs.length - kept.length;
1929
1987
  if (keptCount > 0) {
1930
- console.log(`[hypermem-plugin] tool-loop trim: pressure=${(pressure * 100).toFixed(1)}% → ` +
1988
+ console.log(`[hypermem-plugin] tool-loop trim: pressure=${s3ToolLoopPressure.pct}% source=${s3ToolLoopPressure.source} → ` +
1931
1989
  `target=${(trimTarget * 100).toFixed(0)}% (redis=${trimmed} msgs, messages=${keptCount} dropped)`);
1932
1990
  trimmedMessages = [...systemMsgs, ...kept];
1933
1991
  }
1934
1992
  else if (trimmed > 0) {
1935
- console.log(`[hypermem-plugin] tool-loop trim: pressure=${(pressure * 100).toFixed(1)}% → ` +
1993
+ console.log(`[hypermem-plugin] tool-loop trim: pressure=${s3ToolLoopPressure.pct}% source=${s3ToolLoopPressure.source} → ` +
1936
1994
  `target=${(trimTarget * 100).toFixed(0)}% (redis=${trimmed} msgs)`);
1937
1995
  }
1938
1996
  }
1939
1997
  else if (trimmed > 0) {
1940
- console.log(`[hypermem-plugin] tool-loop trim: pressure=${(pressure * 100).toFixed(1)}% → ` +
1998
+ console.log(`[hypermem-plugin] tool-loop trim: pressure=${s3ToolLoopPressure.pct}% source=${s3ToolLoopPressure.source} → ` +
1941
1999
  `target=${(trimTarget * 100).toFixed(0)}% (redis=${trimmed} msgs)`);
1942
2000
  }
1943
2001
  // Apply tool gradient to compress large tool results before returning.
@@ -2205,6 +2263,7 @@ function createHyperMemEngine() {
2205
2263
  path: 'replay',
2206
2264
  toolLoop: isToolLoop,
2207
2265
  msgCount: messages.length,
2266
+ composeTopicTelemetryStatus: 'intentionally-omitted',
2208
2267
  });
2209
2268
  }
2210
2269
  }
@@ -2217,6 +2276,20 @@ function createHyperMemEngine() {
2217
2276
  // Subagent light mode: skip library/wiki/semantic/keystones/doc chunks.
2218
2277
  // Keeps: system, identity, history, active facts, output profile, tool gradient.
2219
2278
  const subagentLight = isSubagent && _subagentWarming === 'light';
2279
+ let forkedContext;
2280
+ if (isSubagent) {
2281
+ try {
2282
+ const rawForkedContext = await hm.cache.getSlot(agentId, sk, FORKED_CONTEXT_META_SLOT);
2283
+ if (rawForkedContext) {
2284
+ const parsed = JSON.parse(rawForkedContext);
2285
+ if (parsed?.enabled === true)
2286
+ forkedContext = parsed;
2287
+ }
2288
+ }
2289
+ catch {
2290
+ // Fork metadata is advisory; fall back to normal subagent lifecycle.
2291
+ }
2292
+ }
2220
2293
  const request = {
2221
2294
  agentId,
2222
2295
  sessionKey: sk,
@@ -2231,6 +2304,7 @@ function createHyperMemEngine() {
2231
2304
  includeSemanticRecall: subagentLight ? false : undefined, // skip vector/FTS recall
2232
2305
  includeKeystones: subagentLight ? false : undefined, // skip keystone history injection
2233
2306
  prompt,
2307
+ forkedContext,
2234
2308
  skipProviderTranslation: true, // runtime handles provider translation
2235
2309
  };
2236
2310
  const result = await hm.compose(request);
@@ -2246,6 +2320,63 @@ function createHyperMemEngine() {
2246
2320
  replayState: replayRecovery.emittedMarker?.state,
2247
2321
  replayReason: replayRecovery.emittedMarker?.reason,
2248
2322
  });
2323
+ // Sprint 1: emit assemble-level trace with full observability fields
2324
+ // after a full compose (not replay). Surfaces prefix stability,
2325
+ // reranker outcome, slot spans, and compaction eligibility.
2326
+ if (telemetryEnabled() && !cachedContextBlock) {
2327
+ const diag = result.diagnostics;
2328
+ // prefixChanged: compare current prefixHash against prevPrefixHash
2329
+ // (surfaced by the compositor when a cache bypass detected prefix mutation).
2330
+ // When no previous hash is available (first turn), leave prefixChanged undefined.
2331
+ let prefixChanged;
2332
+ if (diag?.prefixHash && diag?.prevPrefixHash) {
2333
+ prefixChanged = diag.prefixHash !== diag.prevPrefixHash;
2334
+ }
2335
+ assembleTrace({
2336
+ agentId,
2337
+ sessionKey: sk,
2338
+ turnId: _asmTurnId,
2339
+ path: isSubagent ? 'subagent' : 'cold',
2340
+ toolLoop: isToolLoop,
2341
+ msgCount: result.messages.length,
2342
+ prefixChanged,
2343
+ prefixHash: diag?.prefixHash,
2344
+ rerankerStatus: diag?.rerankerStatus,
2345
+ rerankerCandidates: diag?.rerankerCandidates,
2346
+ rerankerProvider: diag?.rerankerProvider,
2347
+ slotSpans: diag?.slotSpans,
2348
+ compactionEligibleCount: diag?.compactionEligibleCount,
2349
+ compactionEligibleRatio: diag?.compactionEligibleRatio,
2350
+ compactionProcessedCount: diag?.compactionProcessedCount,
2351
+ composeTopicSource: diag?.composeTopicSource,
2352
+ composeTopicState: diag?.composeTopicState,
2353
+ composeTopicMessageCount: diag?.composeTopicMessageCount,
2354
+ composeTopicStampedMessageCount: diag?.composeTopicStampedMessageCount,
2355
+ composeTopicTelemetryStatus: diag?.composeTopicTelemetryStatus,
2356
+ });
2357
+ if (diag?.adaptiveLifecycleBand) {
2358
+ lifecyclePolicyTelemetry({
2359
+ path: 'compose.preRecall',
2360
+ agentId,
2361
+ sessionKey: sk,
2362
+ band: diag.adaptiveLifecycleBand,
2363
+ pressurePct: diag.adaptiveLifecyclePressurePct,
2364
+ trimSoftTarget: diag.adaptiveTrimSoftTarget,
2365
+ reasons: diag.adaptiveLifecycleReasons,
2366
+ });
2367
+ }
2368
+ if (diag?.adaptiveEvictionLifecycleBand) {
2369
+ lifecyclePolicyTelemetry({
2370
+ path: 'compose.eviction',
2371
+ agentId,
2372
+ sessionKey: sk,
2373
+ band: diag.adaptiveEvictionLifecycleBand,
2374
+ pressurePct: diag.adaptiveEvictionPressurePct,
2375
+ trimSoftTarget: diag.adaptiveTrimSoftTarget,
2376
+ reasons: diag.adaptiveLifecycleBandDiverged ? ['diverged-from-preRecall'] : undefined,
2377
+ });
2378
+ }
2379
+ }
2249
2380
  // Use cached contextBlock if available (cache replay), otherwise use fresh result.
2250
2381
  // After a full compose, write the new contextBlock to cache for the next turn.
2251
2382
  if (cachedContextBlock) {
@@ -2318,6 +2449,9 @@ ${replayRecovery.emittedText}`
2318
2449
  const runtimeSystemTokens = getOverheadFallback(tier);
2319
2450
  _overheadCache.set(sk, contextBlockTokens + runtimeSystemTokens);
2320
2451
  await persistReplayRecoveryState(hm, agentId, sk, replayRecovery.nextState);
2452
+ if (forkedContext) {
2453
+ await hm.cache.setSlot(agentId, sk, FORKED_CONTEXT_META_SLOT, '').catch(() => { });
2454
+ }
2321
2455
  // Update model state for downshift detection on next turn
2322
2456
  try {
2323
2457
  const modelIdentity = resolveModelIdentity(model);
@@ -2406,6 +2540,9 @@ ${replayRecovery.emittedText}`
2406
2540
  // budget the history is competing for. We trim history to make room.
2407
2541
  const effectiveBudget = computeEffectiveBudget(tokenBudget, model);
2408
2542
  const tokensBefore = await estimateWindowTokens(hm, agentId, sk);
2543
+ // Sprint 3: Unified pressure signal — compact path (Redis estimate)
2544
+ const s3CompactPressure = computeUnifiedPressure(tokensBefore, effectiveBudget, PRESSURE_SOURCE.COMPACT_REDIS_ESTIMATE);
2545
+ console.log(`[hypermem-plugin] compact: pressure=${s3CompactPressure.pct}% source=${s3CompactPressure.source} tokens=${tokensBefore}/${effectiveBudget}`);
2409
2546
  // Target depth for both Redis trimming and JSONL truncation.
2410
2547
  // Target 50% of budget capacity, assume ~500 tokens/message average.
2411
2548
  const targetDepth = Math.max(20, Math.floor((effectiveBudget * 0.5) / 500));
@@ -2419,6 +2556,10 @@ ${replayRecovery.emittedText}`
2419
2556
  // Also triggered when reshape ran recently but the session is still
2420
2557
  // critically full — bypass the reshape guard in that case.
2421
2558
  const NUCLEAR_THRESHOLD = 0.85;
2559
+ // Sprint 3: runtime-total pressure for nuclear check uses its own source label
2560
+ const s3NuclearPressure = currentTokenCount != null
2561
+ ? computeUnifiedPressure(currentTokenCount, effectiveBudget, PRESSURE_SOURCE.COMPACT_RUNTIME_TOTAL)
2562
+ : s3CompactPressure;
2422
2563
  const isNuclear = currentTokenCount != null && currentTokenCount > effectiveBudget * NUCLEAR_THRESHOLD;
2423
2564
  if (isNuclear) {
2424
2565
  // Cut deep: target 20% of normal depth = ~25 messages for a 128k session.
@@ -2437,11 +2578,11 @@ ${replayRecovery.emittedText}`
2437
2578
  postTokens: tokensAfter,
2438
2579
  removed: nuclearRemoved,
2439
2580
  cacheInvalidated: true,
2440
- reason: `currentTokenCount=${currentTokenCount}/${effectiveBudget}`,
2581
+ reason: `${s3NuclearPressure.source}:${s3NuclearPressure.pct}% currentTokenCount=${currentTokenCount}/${effectiveBudget}`,
2441
2582
  });
2442
2583
  }
2443
- console.log(`[hypermem-plugin] compact: NUCLEAR — session at ${currentTokenCount}/${effectiveBudget} tokens ` +
2444
- `(${Math.round((currentTokenCount / effectiveBudget) * 100)}% full), ` +
2584
+ console.log(`[hypermem-plugin] compact: NUCLEAR — pressure=${s3NuclearPressure.pct}% source=${s3NuclearPressure.source} ` +
2585
+ `session at ${currentTokenCount}/${effectiveBudget} tokens, ` +
2445
2586
  `deep-trimmed JSONL to ${nuclearDepth} messages, Redis ${tokensBefore}→${tokensAfter} tokens`);
2446
2587
  return { ok: true, compacted: true, result: { tokensBefore, tokensAfter } };
2447
2588
  }
@@ -2528,10 +2669,10 @@ ${replayRecovery.emittedText}`
2528
2669
  postTokens: tokensAfter,
2529
2670
  removed: historyTrimmed,
2530
2671
  cacheInvalidated: true,
2531
- reason: `over-budget tokensBefore=${tokensBefore}/${effectiveBudget}`,
2672
+ reason: `${s3CompactPressure.source}:${s3CompactPressure.pct}% over-budget tokensBefore=${tokensBefore}/${effectiveBudget}`,
2532
2673
  });
2533
2674
  }
2534
- console.log(`[hypermem-plugin] compact: trimmed ${tokensBefore} → ${tokensAfter} tokens (budget: ${effectiveBudget})`);
2675
+ console.log(`[hypermem-plugin] compact: trimmed ${tokensBefore} → ${tokensAfter} tokens (budget: ${effectiveBudget}, pressure=${s3CompactPressure.pct}% source=${s3CompactPressure.source})`);
2535
2676
  // Density-aware JSONL truncation: derive target depth from actual avg tokens/message
2536
2677
  // rather than assuming a fixed 500 tokens/message. This prevents a large-message
2537
2678
  // session (e.g. 145 msgs × 882 tok = 128k) from bypassing the 1.5x guard and
@@ -2608,8 +2749,54 @@ ${replayRecovery.emittedText}`
2608
2749
  });
2609
2750
  }
2610
2751
  }
2752
+ try {
2753
+ const lastAssistantMessage = [...newMessages].reverse().find(m => m.role === 'assistant');
2754
+ if (lastAssistantMessage) {
2755
+ const modelState = await hm.cache.getModelState(agentId, sk).catch(() => null);
2756
+ const promptCacheUsage = runtimeContext?.promptCache?.lastCallUsage;
2757
+ const outputTokens = resolveAssistantOutputTokenCount(lastAssistantMessage, runtimeContext) ?? 1;
2758
+ const inputTokens = typeof promptCacheUsage?.input === 'number'
2759
+ ? Math.floor(promptCacheUsage.input)
2760
+ : typeof runtimeContext?.currentTokenCount === 'number'
2761
+ ? Math.floor(runtimeContext.currentTokenCount)
2762
+ : null;
2763
+ const cacheReadTokens = typeof promptCacheUsage?.cacheRead === 'number'
2764
+ ? Math.floor(promptCacheUsage.cacheRead)
2765
+ : null;
2766
+ const modelId = typeof lastAssistantMessage.model === 'string'
2767
+ ? lastAssistantMessage.model
2768
+ : modelState?.modelId ?? modelState?.model ?? 'unknown';
2769
+ const provider = typeof lastAssistantMessage.provider === 'string'
2770
+ ? lastAssistantMessage.provider
2771
+ : modelState?.provider ?? 'unknown';
2772
+ const taskType = typeof runtimeContext?.taskType === 'string'
2773
+ ? runtimeContext.taskType ?? null
2774
+ : null;
2775
+ recordOutputMetrics(hm.dbManager.getLibraryDb(), {
2776
+ id: `turn-metric-${agentId}-${Date.now()}-${randomUUID()}`,
2777
+ timestamp: new Date().toISOString(),
2778
+ agent_id: agentId,
2779
+ session_key: sk,
2780
+ model_id: modelId,
2781
+ provider,
2782
+ fos_version: null,
2783
+ mod_version: null,
2784
+ mod_id: null,
2785
+ task_type: taskType,
2786
+ output_tokens: outputTokens,
2787
+ input_tokens: inputTokens,
2788
+ cache_read_tokens: cacheReadTokens,
2789
+ corrections_fired: [],
2790
+ latency_ms: null,
2791
+ });
2792
+ }
2793
+ }
2794
+ catch {
2795
+ // Non-fatal telemetry path
2796
+ }
2611
2797
  // P3.1: Topic detection on the inbound user message
2612
2798
  // Non-fatal: topic detection never blocks afterTurn
2799
+ let adaptiveTopicShiftConfidence;
2613
2800
  try {
2614
2801
  const inboundUserMsg = newMessages
2615
2802
  .map(m => m)
@@ -2626,6 +2813,7 @@ ${replayRecovery.emittedText}`
2626
2813
  const topicMap = new SessionTopicMap(db);
2627
2814
  const activeTopic = topicMap.getActiveTopic(sk);
2628
2815
  const signal = detectTopicShift(neutralUser, contextMessages, activeTopic?.id ?? null);
2816
+ adaptiveTopicShiftConfidence = signal.confidence;
2629
2817
  if (signal.isNewTopic && signal.topicName) {
2630
2818
  const newTopicId = topicMap.createTopic(sk, signal.topicName);
2631
2819
  // New topic starts with count 1 (the message that triggered the shift)
@@ -2671,7 +2859,30 @@ ${replayRecovery.emittedText}`
2671
2859
  const modelState = await hm.cache.getModelState(agentId, sk);
2672
2860
  const gradientBudget = modelState?.tokenBudget;
2673
2861
  const gradientDepth = modelState?.historyDepth;
2674
- await hm.refreshRedisGradient(agentId, sk, gradientBudget, gradientDepth);
2862
+ const inboundUserMsg = newMessages
2863
+ .map(m => m)
2864
+ .find(m => m.role === 'user');
2865
+ const inboundUserText = inboundUserMsg
2866
+ ? stripMessageMetadata(extractTextFromInboundContent(inboundUserMsg.content))
2867
+ : '';
2868
+ const lifecyclePolicy = resolveAdaptiveLifecyclePolicy({
2869
+ usedTokens: estimateMessageArrayTokens(messages),
2870
+ effectiveBudget: gradientBudget,
2871
+ userTurnCount: messages.filter(m => m.role === 'user').length,
2872
+ explicitNewSession: /^\/new(?:\s|$)/i.test(inboundUserText.trim()),
2873
+ topicShiftConfidence: adaptiveTopicShiftConfidence,
2874
+ });
2875
+ lifecyclePolicyTelemetry({
2876
+ path: 'afterTurn.gradient',
2877
+ agentId,
2878
+ sessionKey: sk,
2879
+ band: lifecyclePolicy.band,
2880
+ pressurePct: lifecyclePolicy.pressurePct,
2881
+ topicShiftConfidence: adaptiveTopicShiftConfidence,
2882
+ trimSoftTarget: lifecyclePolicy.trimSoftTarget,
2883
+ reasons: lifecyclePolicy.reasons,
2884
+ });
2885
+ await hm.refreshRedisGradient(agentId, sk, gradientBudget, gradientDepth, lifecyclePolicy.trimSoftTarget);
2675
2886
  }
2676
2887
  catch (refreshErr) {
2677
2888
  console.warn('[hypermem-plugin] afterTurn: refreshRedisGradient failed (non-fatal):', refreshErr.message);
@@ -2841,7 +3052,12 @@ ${replayRecovery.emittedText}`
2841
3052
  * subagentWarming config ('full' | 'light' | 'off').
2842
3053
  * Returns a rollback handle to clean up if spawn fails.
2843
3054
  */
2844
- async prepareSubagentSpawn({ parentSessionKey, childSessionKey }) {
3055
+ async prepareSubagentSpawn(params) {
3056
+ const { parentSessionKey, childSessionKey } = params;
3057
+ const forkParams = params;
3058
+ const contextMode = forkParams.contextMode;
3059
+ const parentSessionId = forkParams.parentSessionId;
3060
+ const childSessionId = forkParams.childSessionId;
2845
3061
  if (_subagentWarming === 'off') {
2846
3062
  return undefined;
2847
3063
  }
@@ -2849,7 +3065,12 @@ ${replayRecovery.emittedText}`
2849
3065
  const hm = await getHyperMem();
2850
3066
  const parentAgentId = extractAgentId(parentSessionKey);
2851
3067
  const childAgentId = extractAgentId(childSessionKey);
2852
- // Seed child with parent's active facts
3068
+ const isForkedContext = contextMode === 'fork';
3069
+ let parentHistoryMessages = 0;
3070
+ let parentUserTurnCount = 0;
3071
+ let parentPressureFraction;
3072
+ // Seed child with parent's active facts. This preserves the historical
3073
+ // slot for compatibility; facts still primarily come from L4 by agent id.
2853
3074
  const facts = hm.getActiveFacts(parentAgentId, { limit: 50 });
2854
3075
  if (facts && facts.length > 0) {
2855
3076
  const factBlock = facts
@@ -2857,22 +3078,48 @@ ${replayRecovery.emittedText}`
2857
3078
  .join('\n');
2858
3079
  await hm.cache.setSlot(childAgentId, childSessionKey, 'parentFacts', factBlock);
2859
3080
  }
2860
- // For 'full' warming, also seed recent history context
2861
- if (_subagentWarming === 'full') {
2862
- const history = await hm.cache.getHistory(parentAgentId, parentSessionKey);
2863
- if (history && history.length > 0) {
2864
- const recentHistory = history.slice(-10);
2865
- await hm.cache.setSlot(childAgentId, childSessionKey, 'parentHistory', JSON.stringify(recentHistory));
3081
+ const history = await hm.cache.getHistory(parentAgentId, parentSessionKey);
3082
+ if (history && history.length > 0) {
3083
+ const maxSeededHistory = _subagentWarming === 'full' ? 25 : 12;
3084
+ const recentHistory = history.slice(-maxSeededHistory);
3085
+ parentHistoryMessages = recentHistory.length;
3086
+ parentUserTurnCount = recentHistory.filter(m => m.role === 'user').length;
3087
+ const parentTokens = estimateMessageArrayTokens(recentHistory);
3088
+ const parentModelState = await hm.cache.getModelState(parentAgentId, parentSessionKey).catch(() => null);
3089
+ const parentBudget = parentModelState?.tokenBudget && parentModelState.tokenBudget > 0
3090
+ ? parentModelState.tokenBudget
3091
+ : undefined;
3092
+ parentPressureFraction = parentBudget ? parentTokens / parentBudget : undefined;
3093
+ if (isForkedContext || _subagentWarming === 'full') {
3094
+ await hm.cache.replaceHistory(childAgentId, childSessionKey, recentHistory, maxSeededHistory);
3095
+ await hm.cache.invalidateWindow(childAgentId, childSessionKey).catch(() => { });
2866
3096
  }
3097
+ await hm.cache.setSlot(childAgentId, childSessionKey, 'parentHistory', JSON.stringify(recentHistory));
3098
+ }
3099
+ if (isForkedContext) {
3100
+ const forkedMeta = {
3101
+ enabled: true,
3102
+ parentSessionKey,
3103
+ parentSessionId,
3104
+ childSessionId,
3105
+ parentPressureFraction,
3106
+ parentUserTurnCount,
3107
+ parentHistoryMessages,
3108
+ };
3109
+ await hm.cache.setSlot(childAgentId, childSessionKey, FORKED_CONTEXT_META_SLOT, JSON.stringify(forkedMeta));
2867
3110
  }
2868
3111
  console.log(`[hypermem-plugin] prepareSubagentSpawn: seeded ${childSessionKey} ` +
2869
- `from ${parentSessionKey} (warming=${_subagentWarming})`);
3112
+ `from ${parentSessionKey} (warming=${_subagentWarming}, contextMode=${contextMode ?? 'isolated'}, ` +
3113
+ `history=${parentHistoryMessages})`);
2870
3114
  return {
2871
3115
  async rollback() {
2872
3116
  try {
2873
3117
  const hm = await getHyperMem();
2874
3118
  await hm.cache.setSlot(childAgentId, childSessionKey, 'parentFacts', '');
2875
3119
  await hm.cache.setSlot(childAgentId, childSessionKey, 'parentHistory', '');
3120
+ await hm.cache.setSlot(childAgentId, childSessionKey, FORKED_CONTEXT_META_SLOT, '');
3121
+ await hm.cache.replaceHistory(childAgentId, childSessionKey, [], 0);
3122
+ await hm.cache.invalidateWindow(childAgentId, childSessionKey).catch(() => { });
2876
3123
  }
2877
3124
  catch {
2878
3125
  // Rollback is best-effort
@@ -2898,6 +3145,7 @@ ${replayRecovery.emittedText}`
2898
3145
  await Promise.all([
2899
3146
  hm.cache.setSlot(childAgentId, childSessionKey, 'parentFacts', ''),
2900
3147
  hm.cache.setSlot(childAgentId, childSessionKey, 'parentHistory', ''),
3148
+ hm.cache.setSlot(childAgentId, childSessionKey, FORKED_CONTEXT_META_SLOT, ''),
2901
3149
  hm.cache.setSlot(childAgentId, childSessionKey, 'assemblyContextBlock', ''),
2902
3150
  hm.cache.setSlot(childAgentId, childSessionKey, 'assemblyContextAt', '0'),
2903
3151
  hm.cache.invalidateWindow(childAgentId, childSessionKey).catch(() => { }),
@@ -3109,6 +3357,23 @@ const hypercompositorConfigSchema = z.object({
3109
3357
  timeout: z.number().int().positive().optional(),
3110
3358
  batchSize: z.number().int().positive().optional(),
3111
3359
  }).optional(),
3360
+ /**
3361
+ * Optional reranker config. When omitted or provider is 'none', the
3362
+ * compositor runs with RRF-only ordering. See INSTALL.md → Reranker.
3363
+ */
3364
+ reranker: z.object({
3365
+ provider: z.enum(['zeroentropy', 'openrouter', 'local', 'none']),
3366
+ minCandidates: z.number().int().nonnegative().optional(),
3367
+ maxDocuments: z.number().int().positive().optional(),
3368
+ topK: z.number().int().positive().optional(),
3369
+ timeoutMs: z.number().int().positive().optional(),
3370
+ zeroEntropyApiKey: z.string().optional(),
3371
+ zeroEntropyModel: z.string().optional(),
3372
+ openrouterApiKey: z.string().optional(),
3373
+ openrouterModel: z.string().optional(),
3374
+ ollamaUrl: z.string().optional(),
3375
+ ollamaModel: z.string().optional(),
3376
+ }).optional(),
3112
3377
  });
3113
3378
  // ─── Plugin Entry ───────────────────────────────────────────────
3114
3379
  const engine = createHyperMemEngine();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psiclawops/hypercompositor",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "description": "HyperCompositor — context engine plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,7 +38,7 @@
38
38
  "typecheck": "tsc --noEmit"
39
39
  },
40
40
  "dependencies": {
41
- "@psiclawops/hypermem": "file:..",
41
+ "@psiclawops/hypermem": "0.9.0",
42
42
  "zod": "^4.0.0"
43
43
  },
44
44
  "devDependencies": {