feat(context): add offline shadow profile and local dev gate
Memind CI / Test, build, and release guards (push) Has been cancelled

Provide context-runtime-profile simulation for gateway shadow events,
run-context-runtime-offline.mjs for zero-LLM verification, and a local
dev guide for user-led shadow self-tests later.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-09 22:18:47 +08:00
parent c6209370d8
commit 478c33376c
6 changed files with 405 additions and 9 deletions
+134
View File
@@ -0,0 +1,134 @@
import { buildHeadroomRunObservation, resolveHeadroomMode } from './memind-headroom-policy.mjs';
import {
buildContextBudgetResolvedEvent,
planContextBudget,
resolveContextBudgetMode,
} from './context-budget.mjs';
import { resolveMcpCompactMode } from './mcp-result-compactor.mjs';
import {
buildRecallFusionResolvedEvent,
fuseRecallCandidates,
resolveRecallFusionMode,
} from './recall-fusion.mjs';
import { resolveZvecWorkspaceMode } from './memind-zvec-workspace.mjs';
/** Recommended local shadow profile (observe-only, no behavior change). */
export const CONTEXT_RUNTIME_SHADOW_ENV = Object.freeze({
MEMIND_HEADROOM_MODE: 'shadow',
MEMIND_CONTEXT_BUDGET_MODE: 'shadow',
MEMIND_RECALL_FUSION_MODE: 'shadow',
MEMIND_MCP_COMPACT_MODE: 'shadow',
MEMIND_ZVEC_WORKSPACE_MODE: 'shadow',
});
export function describeContextRuntimeProfile(env = process.env) {
return {
headroom: resolveHeadroomMode(env),
contextBudget: resolveContextBudgetMode(env),
recallFusion: resolveRecallFusionMode(env),
mcpCompact: resolveMcpCompactMode(env),
zvecWorkspace: resolveZvecWorkspaceMode(env),
};
}
export function isContextRuntimeShadowProfile(env = process.env) {
const profile = describeContextRuntimeProfile(env);
return Object.values(profile).every((mode) => mode === 'off' || mode === 'shadow');
}
export function isAnyContextRuntimeObservabilityEnabled(env = process.env) {
const profile = describeContextRuntimeProfile(env);
return Object.values(profile).some((mode) => mode !== 'off');
}
/**
* Offline simulation of gateway shadow events (no LLM / no DB).
* Mirrors agent-run-gateway appendEvent payloads for local evidence review.
*/
export function simulateContextRuntimeShadowEvents({
skillId = 'web',
userMessage = {
role: 'user',
content: [{ type: 'text', text: '帮我查一下 Memind context runtime 设计' }],
metadata: { displayText: '帮我查一下 Memind context runtime 设计' },
},
routing = {
route: 'agent_orchestration',
suggestedSkill: 'web',
reason: 'offline-shadow-simulation',
},
memoryContext = {
mode: 'shadow',
injectionEnabled: true,
memories: [
{ label: '偏好', text: '用户偏好:喜欢简洁回答' },
{ label: '偏好副本', text: '用户偏好:喜欢简洁回答' },
{ label: '项目', text: '正在推进 Context Runtime 融合' },
],
},
env = { ...CONTEXT_RUNTIME_SHADOW_ENV },
} = {}) {
const events = {};
const resolvedSkill = routing?.suggestedSkill ?? routing?.suggested_skill ?? skillId;
if (resolveHeadroomMode(env) !== 'off') {
events.headroom_context_observed = buildHeadroomRunObservation({
skillId: resolvedSkill,
env,
});
}
if (resolveRecallFusionMode(env) !== 'off') {
const fusion = fuseRecallCandidates({
personalMemories: memoryContext?.memories ?? [],
episodicMemories: [],
temporalItems: [],
query: userMessage.metadata?.displayText ?? 'context runtime',
limit: 3,
env,
});
events.recall_fusion_resolved = buildRecallFusionResolvedEvent(fusion);
}
if (resolveContextBudgetMode(env) !== 'off') {
const displayText = userMessage.metadata?.displayText
?? userMessage.content?.find?.((item) => item?.type === 'text')?.text
?? '';
const budgetPlan = planContextBudget({
userTask: displayText,
skillPrompt: `[skill:${resolvedSkill}] 使用 web 搜索补充事实`,
memories: memoryContext?.injectionEnabled ? memoryContext.memories : [],
temporalText: '最近一周无日程冲突',
harnessEntries: [{ title: 'TKMind 用户偏好画像', content: '用户偏好:喜欢简洁回答' }],
}, { env });
events.context_budget_resolved = buildContextBudgetResolvedEvent(budgetPlan);
}
return {
profile: describeContextRuntimeProfile(env),
events,
};
}
export function validateContextRuntimeShadowEvents(simulation) {
const issues = [];
const { profile, events } = simulation ?? {};
if (!profile || !events) {
return { ok: false, issues: ['simulation missing profile or events'] };
}
if (profile.headroom !== 'off' && !events.headroom_context_observed) {
issues.push('headroom mode enabled but headroom_context_observed missing');
}
if (profile.contextBudget !== 'off' && !events.context_budget_resolved) {
issues.push('context budget mode enabled but context_budget_resolved missing');
}
if (profile.recallFusion !== 'off' && !events.recall_fusion_resolved) {
issues.push('recall fusion mode enabled but recall_fusion_resolved missing');
}
if (events.context_budget_resolved?.mode === 'shadow' && !events.context_budget_resolved?.duplicateCount) {
issues.push('expected shadow budget simulation to report duplicate fingerprints');
}
return { ok: issues.length === 0, issues };
}
+45
View File
@@ -0,0 +1,45 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
CONTEXT_RUNTIME_SHADOW_ENV,
describeContextRuntimeProfile,
isContextRuntimeShadowProfile,
simulateContextRuntimeShadowEvents,
validateContextRuntimeShadowEvents,
} from './context-runtime-profile.mjs';
test('describeContextRuntimeProfile defaults to off', () => {
const profile = describeContextRuntimeProfile({});
assert.equal(profile.headroom, 'off');
assert.equal(profile.contextBudget, 'off');
assert.equal(profile.recallFusion, 'off');
});
test('isContextRuntimeShadowProfile accepts all-shadow bundle', () => {
assert.equal(isContextRuntimeShadowProfile(CONTEXT_RUNTIME_SHADOW_ENV), true);
assert.equal(isContextRuntimeShadowProfile({
...CONTEXT_RUNTIME_SHADOW_ENV,
MEMIND_HEADROOM_MODE: 'active',
}), false);
});
test('simulateContextRuntimeShadowEvents emits gateway-shaped payloads', () => {
const simulation = simulateContextRuntimeShadowEvents();
assert.ok(simulation.events.headroom_context_observed);
assert.ok(simulation.events.context_budget_resolved);
assert.ok(simulation.events.recall_fusion_resolved);
assert.equal(simulation.events.headroom_context_observed.mode, 'shadow');
assert.equal(simulation.events.context_budget_resolved.mode, 'shadow');
const validation = validateContextRuntimeShadowEvents(simulation);
assert.equal(validation.ok, true, validation.issues?.join('; '));
});
test('simulateContextRuntimeShadowEvents respects off modes', () => {
const simulation = simulateContextRuntimeShadowEvents({
env: { MEMIND_HEADROOM_MODE: 'off', MEMIND_CONTEXT_BUDGET_MODE: 'off', MEMIND_RECALL_FUSION_MODE: 'off' },
});
assert.equal(simulation.events.headroom_context_observed, undefined);
assert.equal(simulation.events.context_budget_resolved, undefined);
assert.equal(simulation.events.recall_fusion_resolved, undefined);
});
+92
View File
@@ -0,0 +1,92 @@
# Context Runtime 本地离线开发
> 不调用 LLM、不跑 phase3 live smoke。用户自测见文末清单。
## 一键离线验证
```bash
node scripts/run-context-runtime-offline.mjs
node scripts/run-goosed-v149-phase3-offline.mjs # 含 v1.49 单测 + manifest drift
```
通过时应分别输出 **`CONTEXT_RUNTIME_OFFLINE_OK`** 与 **`GOOSE_V149_PHASE3_OFFLINE_OK`**。
## 推荐 shadow 配置(仅观测,不改行为)
写入 `.env.local`(或临时 export):
```bash
MEMIND_HEADROOM_MODE=shadow
MEMIND_CONTEXT_BUDGET_MODE=shadow
MEMIND_RECALL_FUSION_MODE=shadow
MEMIND_MCP_COMPACT_MODE=shadow
MEMIND_ZVEC_WORKSPACE_MODE=shadow
HEADROOM_OUTPUT_SHAPER=0
```
Bundle 常量见 `context-runtime-profile.mjs``CONTEXT_RUNTIME_SHADOW_ENV`
## 离线事件形状预览
不启动 Portal 也可查看 gateway 将 emit 的 shadow 事件:
```bash
node -e "
import { simulateContextRuntimeShadowEvents } from './context-runtime-profile.mjs';
console.log(JSON.stringify(simulateContextRuntimeShadowEvents(), null, 2));
"
```
期望事件类型:
| event_type | 含义 |
|---|---|
| `headroom_context_observed` | headroom 路由/eligible 观测 |
| `context_budget_resolved` | 注入预算 dedupe/drop 计划 |
| `recall_fusion_resolved` | 多路 recall 融合统计 |
## 组件探针(可选)
```bash
# headroom proxy 已起且 MEMIND_HEADROOM_MODE!=off
node scripts/check-headroom-proxy-local.mjs
# .zvec-grep 已 index
node scripts/check-zvec-workspace-local.mjs
# MCP compactor shadow/active 逻辑
node scripts/check-mcp-compactor-local.mjs
```
## 用户自测清单(有 LLM 配额时)
1. 确认 **未设置** `GOOSE_V149_ALLOW_REAL_LLM=1` 除非你明确要跑 live smoke
2. 应用 shadow env**重启 Portal**`pnpm dev`
3. 发起 **一轮**普通 agent 对话(非 page-e2e
4. 查 DB 事件:
```sql
SELECT event_type, data_json, created_at
FROM h5_agent_run_events
WHERE run_id = '<run_id>'
AND event_type IN (
'headroom_context_observed',
'context_budget_resolved',
'recall_fusion_resolved'
)
ORDER BY created_at;
```
5. 确认 `mode=shadow`**聊天行为与 off 时一致**
6. 若要 active 晋升:逐项改 `shadow``active`,每次只改一个开关
## 禁止项
- Agent **不得**自动设置 `GOOSE_V149_ALLOW_REAL_LLM=1` 或循环跑 phase2/phase3
- **禁止** `headroom wrap` / `headroom learn` / `HEADROOM_OUTPUT_SHAPER=1`
- 页面生成 / Page Data 轮次首期 **排除** headroom active
## 相关文档
- [context-runtime-harness-fusion-plan.md](./context-runtime-harness-fusion-plan.md)
- [goose-v149-real-llm-gate.md](./goose-v149-real-llm-gate.md)
+7
View File
@@ -56,3 +56,10 @@ MEMORY_CANDIDATE_ENABLED=0
# MEMIND_MCP_COMPACT_BUDGET_CHARS=4000
# MEMIND_MCP_COMPACT_TTL_SECONDS=3600
# MEMIND_RUNTIME_REDIS_URL=redis://127.0.0.1:6379/0
# Context Runtime shadow observe-only bundle (see docs/context-runtime-local-dev.md)
# MEMIND_HEADROOM_MODE=shadow
# MEMIND_CONTEXT_BUDGET_MODE=shadow
# MEMIND_RECALL_FUSION_MODE=shadow
# MEMIND_MCP_COMPACT_MODE=shadow
# MEMIND_ZVEC_WORKSPACE_MODE=shadow
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env node
/**
* Context Runtime offline gate: unit tests + local probes, no LLM.
*/
import { spawnSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
CONTEXT_RUNTIME_SHADOW_ENV,
describeContextRuntimeProfile,
simulateContextRuntimeShadowEvents,
validateContextRuntimeShadowEvents,
} from '../context-runtime-profile.mjs';
import { prepareGooseV149CheckEnv } from './goose-v149-canary.mjs';
import { checkPassed, classifyCheckResult } from './goose-v149-check-result.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const checkEnv = prepareGooseV149CheckEnv({ ...process.env }, root);
const unitChecks = [
{
name: 'context-runtime-profile',
command: [process.execPath, '--test', 'context-runtime-profile.test.mjs'],
},
{
name: 'context-runtime-modules',
command: [
process.execPath,
'--test',
'memind-headroom-policy.test.mjs',
'context-budget.test.mjs',
'recall-fusion.test.mjs',
'mcp-result-compactor.test.mjs',
'memind-zvec-workspace.test.mjs',
],
},
];
const scriptChecks = [
{ name: 'mcp-compactor-local', script: 'check-mcp-compactor-local.mjs' },
{ name: 'headroom-proxy-local', script: 'check-headroom-proxy-local.mjs', optional: true },
{ name: 'zvec-workspace-local', script: 'check-zvec-workspace-local.mjs', optional: true },
];
function runCommand(command) {
const [bin, ...args] = command;
const result = spawnSync(bin, args, {
cwd: root,
env: checkEnv,
encoding: 'utf8',
});
return {
ok: checkPassed(result),
outcome: classifyCheckResult(result),
stdout: result.stdout?.trim() ?? '',
stderr: result.stderr?.trim() ?? '',
};
}
function runScript(script) {
const result = spawnSync(process.execPath, [path.join(root, 'scripts', script)], {
cwd: root,
env: checkEnv,
encoding: 'utf8',
});
return {
ok: checkPassed(result),
outcome: classifyCheckResult(result),
stdout: result.stdout?.trim() ?? '',
stderr: result.stderr?.trim() ?? '',
};
}
function main() {
console.log('CONTEXT_RUNTIME_OFFLINE:');
console.log(` profile=${JSON.stringify(describeContextRuntimeProfile(checkEnv))}`);
console.log(` recommended_shadow=${JSON.stringify(CONTEXT_RUNTIME_SHADOW_ENV)}`);
const simulation = simulateContextRuntimeShadowEvents({ env: { ...CONTEXT_RUNTIME_SHADOW_ENV } });
const validation = validateContextRuntimeShadowEvents(simulation);
console.log(` shadow_simulation=${validation.ok ? 'OK' : 'FAIL'}`);
if (!validation.ok) {
console.error(validation.issues.join('\n'));
process.exit(1);
}
console.log(` sample_events=${Object.keys(simulation.events).join(',')}`);
const failures = [];
for (const check of unitChecks) {
const result = runCommand(check.command);
console.log(`[context-runtime-offline] ${result.outcome} ${check.name}`);
if (!result.ok) {
failures.push(check.name);
if (result.stderr) console.error(result.stderr);
}
}
for (const check of scriptChecks) {
const result = runScript(check.script);
const skipped = /SKIP:/.test(result.stdout);
if (skipped && check.optional) {
console.log(`[context-runtime-offline] SKIP ${check.name}`);
continue;
}
console.log(`[context-runtime-offline] ${result.outcome} ${check.name}`);
if (!result.ok && !check.optional) {
failures.push(check.name);
if (result.stderr) console.error(result.stderr);
} else if (!result.ok && check.optional) {
console.log(`[context-runtime-offline] WARN ${check.name} (optional probe failed)`);
}
}
if (failures.length) {
console.error(`CONTEXT_RUNTIME_OFFLINE_FAIL: ${failures.join(', ')}`);
process.exit(1);
}
console.log('CONTEXT_RUNTIME_OFFLINE_OK');
console.log(' self-test: docs/context-runtime-local-dev.md');
}
main();
+2 -9
View File
@@ -48,15 +48,8 @@ const checks = [
required: true,
},
{
name: 'context-runtime-unit',
command: [
process.execPath,
'--test',
'memind-headroom-policy.test.mjs',
'context-budget.test.mjs',
'recall-fusion.test.mjs',
'mcp-result-compactor.test.mjs',
],
name: 'context-runtime-offline',
script: 'run-context-runtime-offline.mjs',
required: true,
},
];