mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
feat(context-engine): add memory prompt helper
This commit is contained in:
@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Memory/wiki: add claim-health linting, contradiction clustering, staleness-aware dashboards, and freshness-weighted wiki search so `memory-wiki` can act more like a maintained belief layer than a passive markdown dump. Thanks @vincentkoc.
|
||||
- Memory/wiki: use compiled digest artifacts as the first-pass wiki index for search/get flows, and resolve claim ids back to owning pages so agents can retrieve knowledge by belief identity instead of only by file path. Thanks @vincentkoc.
|
||||
- Memory/wiki: add an opt-in `context.includeCompiledDigestPrompt` flag so memory prompt supplements can append a compact compiled wiki snapshot for legacy prompt assembly and context engines that explicitly consume memory prompt sections. Thanks @vincentkoc.
|
||||
- Plugin SDK/context engines: pass `availableTools` and `citationsMode` into `assemble()`, and expose `buildMemorySystemPromptAddition(...)` so non-legacy context engines can adopt the active memory prompt path without reimplementing it. Thanks @vincentkoc.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
8592ce4554f44cd5325f44f86d37b3057548853cc9bc543b0611e5d9000f262e plugin-sdk-api-baseline.json
|
||||
35393eceb6733d966430369c116511bb15a4b83eec93ebfd3b9a3e8f9ee29cec plugin-sdk-api-baseline.jsonl
|
||||
5bee25156a7699938f2a25419ca44e35113cf6fbd6f533052af68712fc992629 plugin-sdk-api-baseline.json
|
||||
f80212552c63be134a2ae456ce2cd4f81507c1569b60493b1d5f965dc2969041 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -115,6 +115,8 @@ engine is used automatically.
|
||||
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: {
|
||||
@@ -128,12 +130,15 @@ export default function register(api) {
|
||||
return { ingested: true };
|
||||
},
|
||||
|
||||
async assemble({ sessionId, messages, tokenBudget }) {
|
||||
async assemble({ sessionId, messages, tokenBudget, availableTools, citationsMode }) {
|
||||
// Return messages that fit the budget
|
||||
return {
|
||||
messages: buildContext(messages, tokenBudget),
|
||||
estimatedTokens: countTokens(messages),
|
||||
systemPromptAddition: "Use lcm_grep to search history...",
|
||||
systemPromptAddition: buildMemorySystemPromptAddition({
|
||||
availableTools: availableTools ?? new Set(),
|
||||
citationsMode,
|
||||
}),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -249,7 +254,10 @@ OpenClaw resolves when it needs a context engine.
|
||||
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
|
||||
plugin's legacy prompt guidance can pull it explicitly from
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { MemoryCitationsMode } from "../../../config/types.memory.js";
|
||||
import type { ContextEngine, ContextEngineRuntimeContext } from "../../../context-engine/types.js";
|
||||
|
||||
export type AttemptContextEngine = ContextEngine;
|
||||
@@ -56,6 +57,8 @@ export async function assembleAttemptContextEngine(params: {
|
||||
sessionKey?: string;
|
||||
messages: AgentMessage[];
|
||||
tokenBudget?: number;
|
||||
availableTools?: Set<string>;
|
||||
citationsMode?: MemoryCitationsMode;
|
||||
modelId: string;
|
||||
prompt?: string;
|
||||
}) {
|
||||
@@ -67,6 +70,8 @@ export async function assembleAttemptContextEngine(params: {
|
||||
sessionKey: params.sessionKey,
|
||||
messages: params.messages,
|
||||
tokenBudget: params.tokenBudget,
|
||||
...(params.availableTools ? { availableTools: params.availableTools } : {}),
|
||||
...(params.citationsMode ? { citationsMode: params.citationsMode } : {}),
|
||||
model: params.modelId,
|
||||
...(params.prompt !== undefined ? { prompt: params.prompt } : {}),
|
||||
});
|
||||
|
||||
@@ -145,6 +145,24 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards availableTools and citationsMode to assemble", async () => {
|
||||
const { bootstrap, assemble } = createContextEngineBootstrapAndAssemble();
|
||||
const contextEngine = createTestContextEngine({ bootstrap, assemble });
|
||||
|
||||
await runBootstrap(sessionKey, contextEngine);
|
||||
await runAssemble(sessionKey, contextEngine, {
|
||||
availableTools: new Set(["memory_search", "wiki_search"]),
|
||||
citationsMode: "on",
|
||||
});
|
||||
|
||||
expect(assemble).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
availableTools: new Set(["memory_search", "wiki_search"]),
|
||||
citationsMode: "on",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards sessionKey to ingestBatch when afterTurn is absent", async () => {
|
||||
const { bootstrap, assemble } = createContextEngineBootstrapAndAssemble();
|
||||
const ingestBatch = vi.fn(
|
||||
|
||||
@@ -1271,6 +1271,8 @@ export async function runEmbeddedAttempt(
|
||||
sessionKey: params.sessionKey,
|
||||
messages: activeSession.messages,
|
||||
tokenBudget: params.contextTokenBudget,
|
||||
availableTools: new Set(effectiveTools.map((tool) => tool.name)),
|
||||
citationsMode: params.config?.memory?.citations,
|
||||
modelId: params.modelId,
|
||||
...(params.prompt !== undefined ? { prompt: params.prompt } : {}),
|
||||
});
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { MemoryCitationsMode } from "../config/types.memory.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { clearMemoryPluginState, registerMemoryPromptSection } from "../plugins/memory-state.js";
|
||||
// ---------------------------------------------------------------------------
|
||||
// We dynamically import the registry so we can get a fresh module per test
|
||||
// group when needed. For most groups we use the shared singleton directly.
|
||||
// ---------------------------------------------------------------------------
|
||||
import { delegateCompactionToRuntime } from "./delegate.js";
|
||||
import { buildMemorySystemPromptAddition, delegateCompactionToRuntime } from "./delegate.js";
|
||||
import { LegacyContextEngine, registerLegacyContextEngine } from "./legacy.js";
|
||||
import {
|
||||
registerContextEngine,
|
||||
@@ -100,6 +102,8 @@ class MockContextEngine implements ContextEngine {
|
||||
sessionKey?: string;
|
||||
messages: AgentMessage[];
|
||||
tokenBudget?: number;
|
||||
availableTools?: Set<string>;
|
||||
citationsMode?: MemoryCitationsMode;
|
||||
}): Promise<AssembleResult> {
|
||||
return {
|
||||
messages: params.messages,
|
||||
@@ -168,6 +172,8 @@ class LegacySessionKeyStrictEngine implements ContextEngine {
|
||||
sessionKey?: string;
|
||||
messages: AgentMessage[];
|
||||
tokenBudget?: number;
|
||||
availableTools?: Set<string>;
|
||||
citationsMode?: MemoryCitationsMode;
|
||||
prompt?: string;
|
||||
}): Promise<AssembleResult> {
|
||||
this.assembleCalls.push({ ...params });
|
||||
@@ -279,6 +285,8 @@ class LegacyAssembleStrictEngine implements ContextEngine {
|
||||
sessionKey?: string;
|
||||
messages: AgentMessage[];
|
||||
tokenBudget?: number;
|
||||
availableTools?: Set<string>;
|
||||
citationsMode?: MemoryCitationsMode;
|
||||
prompt?: string;
|
||||
}): Promise<AssembleResult> {
|
||||
this.assembleCalls.push({ ...params });
|
||||
@@ -318,6 +326,7 @@ describe("Engine contract tests", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
compactEmbeddedPiSessionDirectMock.mockReset();
|
||||
clearMemoryPluginState();
|
||||
});
|
||||
|
||||
it("a mock engine implementing ContextEngine can be registered and resolved", async () => {
|
||||
@@ -386,6 +395,29 @@ describe("Engine contract tests", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("builds a normalized memory system prompt addition from the active memory prompt path", () => {
|
||||
registerMemoryPromptSection(({ citationsMode }) => [
|
||||
"## Memory Recall",
|
||||
`citations=${citationsMode ?? "auto"}`,
|
||||
"",
|
||||
]);
|
||||
|
||||
expect(
|
||||
buildMemorySystemPromptAddition({
|
||||
availableTools: new Set(["memory_search"]),
|
||||
citationsMode: "off",
|
||||
}),
|
||||
).toBe("## Memory Recall\ncitations=off");
|
||||
});
|
||||
|
||||
it("returns undefined when the active memory prompt path contributes nothing", () => {
|
||||
expect(
|
||||
buildMemorySystemPromptAddition({
|
||||
availableTools: new Set(["memory_search"]),
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { normalizeStructuredPromptSection } from "../agents/prompt-cache-stability.js";
|
||||
import type { MemoryCitationsMode } from "../config/types.memory.js";
|
||||
import { buildMemoryPromptSection } from "../plugins/memory-state.js";
|
||||
import type { ContextEngine, CompactResult, ContextEngineRuntimeContext } from "./types.js";
|
||||
|
||||
/**
|
||||
@@ -61,3 +64,24 @@ export async function delegateCompactionToRuntime(
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a context-engine-ready systemPromptAddition from the active memory
|
||||
* plugin prompt path. This lets non-legacy engines explicitly opt into the
|
||||
* same memory/wiki guidance that the legacy engine gets via system prompt
|
||||
* assembly, without reimplementing memory prompt formatting.
|
||||
*/
|
||||
export function buildMemorySystemPromptAddition(params: {
|
||||
availableTools: Set<string>;
|
||||
citationsMode?: MemoryCitationsMode;
|
||||
}): string | undefined {
|
||||
const lines = buildMemoryPromptSection({
|
||||
availableTools: params.availableTools,
|
||||
citationsMode: params.citationsMode,
|
||||
});
|
||||
if (lines.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = normalizeStructuredPromptSection(lines.join("\n"));
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { MemoryCitationsMode } from "../config/types.memory.js";
|
||||
import { delegateCompactionToRuntime } from "./delegate.js";
|
||||
import { registerContextEngineForOwner } from "./registry.js";
|
||||
import type {
|
||||
@@ -40,6 +41,8 @@ export class LegacyContextEngine implements ContextEngine {
|
||||
sessionKey?: string;
|
||||
messages: AgentMessage[];
|
||||
tokenBudget?: number;
|
||||
availableTools?: Set<string>;
|
||||
citationsMode?: MemoryCitationsMode;
|
||||
model?: string;
|
||||
}): Promise<AssembleResult> {
|
||||
// Pass-through: the existing sanitize -> validate -> limit -> repair pipeline
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { MemoryCitationsMode } from "../config/types.memory.js";
|
||||
|
||||
// Result types
|
||||
|
||||
@@ -180,6 +181,10 @@ export interface ContextEngine {
|
||||
sessionKey?: string;
|
||||
messages: AgentMessage[];
|
||||
tokenBudget?: number;
|
||||
/** Tool names available for this run so engines can align prompt guidance with runtime tool access. */
|
||||
availableTools?: Set<string>;
|
||||
/** Active memory citation mode when engines want to mirror memory prompt guidance. */
|
||||
citationsMode?: MemoryCitationsMode;
|
||||
/** Current model identifier (e.g. "claude-opus-4", "gpt-4o", "qwen2.5-7b").
|
||||
* Allows context engine plugins to adapt formatting per model. */
|
||||
model?: string;
|
||||
|
||||
@@ -24,7 +24,10 @@ export type {
|
||||
MemoryPluginPublicArtifactsProvider,
|
||||
} from "../plugins/memory-state.js";
|
||||
export { resolveControlCommandGate } from "../channels/command-gating.js";
|
||||
export { delegateCompactionToRuntime } from "../context-engine/delegate.js";
|
||||
export {
|
||||
buildMemorySystemPromptAddition,
|
||||
delegateCompactionToRuntime,
|
||||
} from "../context-engine/delegate.js";
|
||||
export type { DiagnosticEventPayload } from "../infra/diagnostic-events.js";
|
||||
export { onDiagnosticEvent } from "../infra/diagnostic-events.js";
|
||||
export {
|
||||
|
||||
@@ -151,7 +151,10 @@ export { buildPluginConfigSchema, emptyPluginConfigSchema } from "../plugins/con
|
||||
export { KeyedAsyncQueue, enqueueKeyedTask } from "./keyed-async-queue.js";
|
||||
export { createDedupeCache, resolveGlobalDedupeCache } from "../infra/dedupe.js";
|
||||
export { generateSecureToken, generateSecureUuid } from "../infra/secure-random.js";
|
||||
export { delegateCompactionToRuntime } from "../context-engine/delegate.js";
|
||||
export {
|
||||
buildMemorySystemPromptAddition,
|
||||
delegateCompactionToRuntime,
|
||||
} from "../context-engine/delegate.js";
|
||||
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
|
||||
@@ -112,5 +112,8 @@ export type {
|
||||
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export { registerContextEngine } from "../context-engine/registry.js";
|
||||
export { delegateCompactionToRuntime } from "../context-engine/delegate.js";
|
||||
export {
|
||||
buildMemorySystemPromptAddition,
|
||||
delegateCompactionToRuntime,
|
||||
} from "../context-engine/delegate.js";
|
||||
export { onDiagnosticEvent } from "../infra/diagnostic-events.js";
|
||||
|
||||
Reference in New Issue
Block a user