Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions examples/openclaw-plugin/context-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ type ContextEngineInfo = {
name: string;
version?: string;
ownsCompaction: true;
/** OpenClaw >=2026.8.1: without both declarations (and commitTurn) the engine is degraded to "legacy" every turn. */
transcriptSemantics: {
currentTurnFence: "before-current-turn-entry-v1";
turnAdvancementIdempotency: "atomic-idempotent-v1";
};
};

type AssembleResult = {
Expand Down Expand Up @@ -81,6 +86,14 @@ type ContextEngine = {
tokenBudget?: number;
runtimeContext?: Record<string, unknown>;
}) => Promise<AssembleResult>;
/** OpenClaw >=2026.8.1 durable turn advancement; retried with the same advancementKey after host failure. */
commitTurn: (params: {
advancementKey: string;
messages: AgentMessage[];
sessionId: string;
sessionKey?: string;
isHeartbeat?: boolean;
}) => Promise<{ status: "committed" | "duplicate" }>;
compact: (params: {
sessionId: string;
sessionKey?: string;
Expand Down Expand Up @@ -318,12 +331,18 @@ export function createMemoryOpenVikingContextEngine(params: {
};
}

const committedTurnKeys = new Set<string>();

return {
info: {
id,
name,
version,
ownsCompaction: true,
transcriptSemantics: {
currentTurnFence: "before-current-turn-entry-v1",
turnAdvancementIdempotency: "atomic-idempotent-v1",
},
},

commitOVSession: doCommitOVSession,
Expand Down Expand Up @@ -368,6 +387,22 @@ export function createMemoryOpenVikingContextEngine(params: {
});
},

// Capture still happens in afterTurn (host calls it per LLM call + on finalize);
// commitTurn only acknowledges the accepted turn so OpenClaw drains its outbox.
// ponytail: in-memory key set, not durable across restarts — the host outbox is.
async commitTurn({ advancementKey, sessionId }): Promise<{ status: "committed" | "duplicate" }> {
if (committedTurnKeys.has(advancementKey)) {
diag("commitTurn_duplicate", sessionId, { advancementKey });
return { status: "duplicate" };
}
committedTurnKeys.add(advancementKey);
if (committedTurnKeys.size > 1024) {
committedTurnKeys.delete(committedTurnKeys.values().next().value as string);
}
diag("commitTurn", sessionId, { advancementKey });
return { status: "committed" };
},

async afterTurn(afterTurnParams): Promise<void> {
const tokenBudget = validTokenBudget(afterTurnParams.tokenBudget) ?? 128_000;
await afterTurnOpenVikingSession({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, expect, it, vi } from "vitest";

import type { OpenVikingClient } from "../../client.js";
import { memoryOpenVikingConfigSchema } from "../../config.js";
import { createMemoryOpenVikingContextEngine } from "../../context-engine.js";

function makeEngine() {
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
return createMemoryOpenVikingContextEngine({
id: "openviking",
name: "Context Engine (OpenViking)",
version: "test",
cfg: memoryOpenVikingConfigSchema.parse({ mode: "remote", baseUrl: "http://127.0.0.1:1933" }),
logger,
getClient: vi.fn().mockResolvedValue({} as OpenVikingClient),
resolveAgentId: vi.fn(() => "agent"),
});
}

// OpenClaw >=2026.8.1 degrades the engine to "legacy" every turn unless both
// transcript semantics are declared and commitTurn exists.
describe("context-engine durable turn contract (OpenClaw 2026.8.1)", () => {
it("declares transcript semantics", () => {
expect(makeEngine().info.transcriptSemantics).toEqual({
currentTurnFence: "before-current-turn-entry-v1",
turnAdvancementIdempotency: "atomic-idempotent-v1",
});
});

it("commitTurn is idempotent per advancementKey", async () => {
const engine = makeEngine();
const params = { advancementKey: "k1", sessionId: "s", messages: [] };
await expect(engine.commitTurn(params)).resolves.toEqual({ status: "committed" });
await expect(engine.commitTurn(params)).resolves.toEqual({ status: "duplicate" });
await expect(engine.commitTurn({ ...params, advancementKey: "k2" })).resolves.toEqual({ status: "committed" });
});
});