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
26 changes: 20 additions & 6 deletions crates/mc-module/src/memory_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
//! is the slice-4d integration decision, already ruled; the byte render here is pure.

use crate::decay_render::{render_decayed_compartments, DecayRenderCompartment};
use mc_store::{StoredMemory, StoredMemoryMutation, WorkspaceMembership};
use mc_store::{
StoredMemory, StoredMemoryMutation, WorkspaceMembership, MEMORY_VISIBILITY_MUTATION_CATEGORY,
};
use std::cmp::Ordering;
use std::collections::HashSet;

Expand Down Expand Up @@ -325,11 +327,23 @@ pub fn render_memory_updates(
vec!["These memories changed since the snapshot below — trust these:".to_string()];
for m in mutations {
match m.mutation_type.as_str() {
"update" => lines.push(format!(
" <updated id=\"{}\">{}</updated>",
m.target_memory_id,
escape_xml_content(m.new_content.as_deref().unwrap_or(""))
)),
"update" => {
let category_attr = match &m.category {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Some(category)
if category != MEMORY_VISIBILITY_MUTATION_CATEGORY
&& !category.is_empty() =>
{
format!(" category=\"{}\"", escape_xml_attr(category))
}
_ => String::new(),
};
lines.push(format!(
" <updated id=\"{}\"{}>{}</updated>",
m.target_memory_id,
category_attr,
escape_xml_content(m.new_content.as_deref().unwrap_or(""))
));
}
"superseded" => match m.superseded_by_id {
Some(by) if resolvable_ids.contains(&by) => lines.push(format!(
" <superseded id=\"{}\" by=\"{by}\"/>",
Expand Down
4 changes: 2 additions & 2 deletions crates/mc-module/testdata/memory-update-delta-parity.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"first_delta": "<memory-updates>\nThese memories changed since the snapshot below — trust these:\n <updated id=\"1\">updated &lt;alpha&gt; &amp; stable</updated>\n <removed id=\"2\"/>\n</memory-updates>",
"second_delta": "<memory-updates>\nThese memories changed since the snapshot below — trust these:\n <updated id=\"1\">updated &lt;alpha&gt; &amp; stable</updated>\n <removed id=\"2\"/>\n <superseded id=\"3\" by=\"4\"/>\n <updated id=\"4\">merged &lt;delta&gt; &amp; sources</updated>\n</memory-updates>",
"first_delta": "<memory-updates>\nThese memories changed since the snapshot below — trust these:\n <updated id=\"1\" category=\"CONSTRAINTS\">updated &lt;alpha&gt; &amp; stable</updated>\n <removed id=\"2\"/>\n</memory-updates>",
"second_delta": "<memory-updates>\nThese memories changed since the snapshot below — trust these:\n <updated id=\"1\" category=\"CONSTRAINTS\">updated &lt;alpha&gt; &amp; stable</updated>\n <removed id=\"2\"/>\n <superseded id=\"3\" by=\"4\"/>\n <updated id=\"4\" category=\"CONSTRAINTS\">merged &lt;delta&gt; &amp; sources</updated>\n</memory-updates>",
"reconciled_m0": "<project-memory>\n<CONSTRAINTS>\n#1: updated &lt;alpha&gt; &amp; stable\n#4: merged &lt;delta&gt; &amp; sources\n</CONSTRAINTS>\n</project-memory>"
}
6 changes: 3 additions & 3 deletions docs/specs/prompt-surface/budget-fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@
"ctx_reduce": { "chars": 91, "tokens": 31 },
"ctx_expand": { "chars": 791, "tokens": 177 },
"ctx_note": { "chars": 1574, "tokens": 378 },
"ctx_memory": { "chars": 840, "tokens": 201 },
"ctx_memory": { "chars": 912, "tokens": 216 },
"ctx_search": { "chars": 777, "tokens": 184 },
"totalTokens": 971
"totalTokens": 986
},
"builtInProviderVisibleTotal": 4720
"builtInProviderVisibleTotal": 4735
},
"mutableProseBaseline": 3749,
"integerLightCeiling": 1825,
Expand Down
7 changes: 6 additions & 1 deletion packages/pi-plugin/src/inject-compartments-pi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { isNoContentCompartment } from "@magic-context/core/features/magic-conte
import {
type ContextDatabase,
clearCachedM0M1,
escapeXmlAttr,
escapeXmlContent,
GLOBAL_USER_PROFILE_PROJECT_PATH,
getCompartments,
Expand Down Expand Up @@ -1892,8 +1893,12 @@ function renderMemoryUpdatesBlockPi(args: {
}
if (mutation.visibilityChanged && mutation.newContent === null) continue;
if (mutation.mutationType === "update") {
const categoryAttr =
mutation.category && mutation.category !== "__mc_visibility__"
? ` category="${escapeXmlAttr(mutation.category)}"`
: "";
lines.push(
` <updated id="${mutation.targetMemoryId}">${escapeXmlContent(mutation.newContent ?? "")}</updated>`,
` <updated id="${mutation.targetMemoryId}"${categoryAttr}>${escapeXmlContent(mutation.newContent ?? "")}</updated>`,
);
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Database } from "../../shared/sqlite";
export type MemoryMutationType = "archive" | "delete" | "update" | "superseded";

const MEMORY_MUTATION_TYPES = new Set<string>(["archive", "delete", "update", "superseded"]);
const MEMORY_VISIBILITY_MUTATION_CATEGORY = "__mc_visibility__";
export const MEMORY_VISIBILITY_MUTATION_CATEGORY = "__mc_visibility__";
const MAX_MEMORY_REPLACEMENT_DEPTH = 8;

// Terminal mutations mean the memory LEFT the active set (renders as
Expand Down
1 change: 1 addition & 0 deletions packages/plugin/src/features/magic-context/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export {
getMemoryMutation,
getMemoryMutationsForRender,
getMemoryMutationsForRenderByProjects,
MEMORY_VISIBILITY_MUTATION_CATEGORY,
type MemoryMutationLogRow,
type MemoryMutationType,
queueMemoryMutation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3157,6 +3157,55 @@ describe("m[0]/m[1] materialization", () => {
expect(renderedText(bust[1])).not.toContain("Updated but not resident.");
});

it("renders the recategorize category on <updated> in memory-updates", () => {
db = makeDb();
const projectDirectory = makeProjectDir();
const memory = insertMemory(db, {
projectPath: PROJECT_PATH,
category: "CONFIG_VALUES",
content: "old category fact",
});
const state = readStateFromMeta();
const hard = materializeM0({
db,
sessionId: SESSION_ID,
state,
projectPath: PROJECT_PATH,
projectDirectory,
injectDocs: false,
memoryInjectionBudgetTokens: 8_000,
});
expect(hard.renderedMemoryIds).toEqual([memory.id]);

db.prepare(
"UPDATE memories SET content = ?, category = ?, normalized_hash = ?, updated_at = ? WHERE id = ?",
).run("new category fact", "CONSTRAINTS", "new-category-fact", Date.now(), memory.id);
queueMemoryMutation(db, {
projectPath: PROJECT_PATH,
mutationType: "update",
targetMemoryId: memory.id,
category: "CONSTRAINTS",
newContent: "new category fact",
queuedAt: 10,
});

const m1 = renderM1(
{
db,
sessionId: SESSION_ID,
state,
projectPath: PROJECT_PATH,
memoryInjectionBudgetTokens: 8_000,
},
hard.snapshotMarkers,
hard.renderedMemoryIds,
);
const updates = m1.match(/<memory-updates>[\s\S]*?<\/memory-updates>/)?.[0];
expect(updates).toContain(
`<updated id="${memory.id}" category="CONSTRAINTS">new category fact</updated>`,
);
});

it("reconcile rematerialization advances the memory mutation cursor and omits memory-updates", () => {
db = makeDb();
const projectDirectory = makeProjectDir();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { MuralWireOptions } from "../../features/magic-context/mural/resolv
import { isNoContentCompartment } from "../../features/magic-context/no-content-compartment";
import {
GLOBAL_USER_PROFILE_PROJECT_PATH,
MEMORY_VISIBILITY_MUTATION_CATEGORY,
getMaxM0MutationId,
getMaxMemoryMutationId,
getMaxMemoryMutationIdForProjects,
Expand Down Expand Up @@ -2570,8 +2571,12 @@ function renderMemoryUpdatesBlock(args: {
}
if (mutation.visibilityChanged && mutation.newContent === null) continue;
if (mutation.mutationType === "update") {
const categoryAttr =
mutation.category && mutation.category !== MEMORY_VISIBILITY_MUTATION_CATEGORY
? ` category="${escapeXmlAttr(mutation.category)}"`
: "";
lines.push(
` <updated id="${mutation.targetMemoryId}">${escapeXmlContent(mutation.newContent ?? "")}</updated>`,
` <updated id="${mutation.targetMemoryId}"${categoryAttr}>${escapeXmlContent(mutation.newContent ?? "")}</updated>`,
);
continue;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin/src/shared/prompt-surface-a1-golden.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ Example: ctx_note(action="write", content="Re-run the perf benchmark once the bo
}
```

### ctx_memory — description ~234 tokens, params ~201 tokens (total ~435)
### ctx_memory — description ~234 tokens, params ~216 tokens (total ~450)

**Description:**

Expand Down Expand Up @@ -376,7 +376,7 @@ Example: ctx_memory(action="write", category="CONSTRAINTS", content="Pi stores s
"type": "string"
},
"category": {
"description": "What kind of fact this is (required for write; optional merge override)",
"description": "What kind of fact this is (required for write; optional on update to recategorize, omitted keeps the current category; optional merge override)",
"type": "string",
"enum": [
"PROJECT_RULES",
Expand Down
Loading