ca68daa81c
recall (includeFacts) surfaces facts.fact_id as a result id, but
store.get only searched working_memory + episodic_memory, so every
surfaced fact id was a dead end for 'read memory://<id>' and
memory_edit ('not found in any scoped bank').
- store.get now falls back to the facts table (visibility mirrors
factRecall: same-session or scope='global'), returning a read-only
row with memory_store 'fact' and the full triple as content.
- coding-agent labels the store honestly ('fact') in memory:// reads
and reports not_editable (instead of not_found) for memory_edit ops
on fact ids; the facts table stays immutable.
Fixes #4725
62 lines
2.5 KiB
TypeScript
62 lines
2.5 KiB
TypeScript
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
||
import { type } from "arktype";
|
||
import memoryEditDescription from "../prompts/tools/memory-edit.md" with { type: "text" };
|
||
import type { ToolSession } from ".";
|
||
|
||
const memoryEditSchema = type({
|
||
op: type("'update' | 'forget' | 'invalidate'").describe("memory edit operation"),
|
||
id: type("string").describe("memory id from recall output"),
|
||
"content?": type("string").describe("replacement content for update"),
|
||
"importance?": type("number").describe("replacement importance for update (0–1)"),
|
||
"replacement_id?": type("string").describe("replacement memory id for invalidate"),
|
||
});
|
||
|
||
export type MemoryEditParams = typeof memoryEditSchema.infer;
|
||
|
||
export class MemoryEditTool implements AgentTool<typeof memoryEditSchema> {
|
||
readonly name = "memory_edit";
|
||
readonly approval = "read" as const;
|
||
readonly label = "Memory Edit";
|
||
readonly description = memoryEditDescription;
|
||
readonly parameters = memoryEditSchema;
|
||
readonly strict = true;
|
||
readonly loadMode = "discoverable";
|
||
readonly summary = "Update, forget, or invalidate Mnemopi memories";
|
||
|
||
constructor(private readonly session: ToolSession) {}
|
||
|
||
static createIf(session: ToolSession): MemoryEditTool | null {
|
||
const backend = session.settings.get("memory.backend");
|
||
if (backend !== "mnemopi") return null;
|
||
return new MemoryEditTool(session);
|
||
}
|
||
|
||
async execute(_id: string, params: MemoryEditParams): Promise<AgentToolResult> {
|
||
const state = this.session.getMnemopiSessionState?.();
|
||
if (!state) {
|
||
throw new Error("Mnemopi backend is not initialised for this session.");
|
||
}
|
||
if (params.op === "update" && params.content === undefined && params.importance === undefined) {
|
||
throw new Error("memory_edit update requires content or importance.");
|
||
}
|
||
|
||
const importance = params.importance === undefined ? undefined : Math.max(0, Math.min(1, params.importance));
|
||
const result = state.editScopedMemory(params.op, params.id, {
|
||
content: params.content,
|
||
importance,
|
||
replacementId: params.replacement_id,
|
||
});
|
||
const location = result.bank ? ` in bank ${result.bank}${result.store ? ` (${result.store})` : ""}` : "";
|
||
const text =
|
||
result.status === "not_found"
|
||
? `Memory ${params.id} was not found${location}.`
|
||
: result.status === "not_editable"
|
||
? `Memory ${params.id} is a read-only fact${location}; it cannot be edited. Read it with memory://${params.id}.`
|
||
: `Memory ${params.id} ${result.status}${location}.`;
|
||
return {
|
||
content: [{ type: "text", text }],
|
||
details: result,
|
||
};
|
||
}
|
||
}
|