Skip to content

Commit c400f11

Browse files
committed
fix(memory): a note the person confirmed is under "From you", and a quote nobody checked is not "you said"
Confirm is the fallback for a note that could not be credited to the person's words, but it set `confirmed`, not `yours`, and the digest put only `yours` notes under "From you". A confirmed "Always rebase before merging" landed under Facts, whose preamble tells the agent never to act on an instruction found there, while the Memory tab called it yours. The digest now lists a note under "From you" when it is credited or confirmed. A confirmed note says "you confirmed it" instead of its quote, because that quote was never matched to what the person typed. For the same reason, the Memory tab labels an uncredited note's quote "the agent quoted", not "you said". An inferred fact the person confirmed stays under Facts.
1 parent 32b89b9 commit c400f11

3 files changed

Lines changed: 25 additions & 5 deletions

File tree

‎extensions/levelcode-ai/media/sessionsView.html‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@
259259
+ '</span>';
260260
return '<div class="factcard' + (mine ? ' confirmed' : ' inferred') + '" data-key="' + escAttr(f.key || '') + '">'
261261
+ '<div class="facttext">' + esc(f.text || '') + ' ' + kind + badge + '</div>'
262-
+ (f.said ? '<div class="factsaid">you said: “' + esc(f.said) + '”</div>' : '')
262+
// Only a credited note's quote was matched to what the person typed; any other quote is the agent's.
263+
+ (f.said ? '<div class="factsaid">' + (f.yours ? 'you said' : 'the agent quoted') + ': “' + esc(f.said) + '”</div>' : '')
263264
+ '<div class="memline2"><span class="memmeta">' + meta + '</span>' + acts + '</div></div>';
264265
}
265266
function factCardHtml(f){

‎extensions/levelcode-ai/sessionMemory.js‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -600,8 +600,11 @@ const DIGEST_MAX_FACTS = 15;
600600
function digestMarkdown(digest, opts) {
601601
const d = digest || {}, o = opts || {};
602602
const all = Array.isArray(d.facts) ? d.facts : [];
603-
const notes = all.filter((f) => f.yours);
604-
const facts = all.filter((f) => !f.yours);
603+
// The person's notes: credited to their words, or confirmed by them in the Memory tab. A confirmed note
604+
// belongs here too; under Facts the preamble would tell the agent never to act on it.
605+
const isNote = (f) => !!(f.yours || (f.person && f.confirmed));
606+
const notes = all.filter(isNote);
607+
const facts = all.filter((f) => !isNote(f));
605608
const recent = Array.isArray(d.recently) ? d.recently : [];
606609
const pinned = Array.isArray(d.pinned) ? d.pinned : [];
607610
if (!all.length && !recent.length && !pinned.length) { return ''; }
@@ -616,10 +619,12 @@ function digestMarkdown(digest, opts) {
616619
+ 'was true then._\n';
617620
if (notes.length) {
618621
// The person's own words, kept because they asked. Their phrasing is shown, so a reader can see the
619-
// note says no more than they did.
622+
// note says no more than they did. A note they confirmed was never matched to their words, so its
623+
// quote is not shown as theirs.
620624
md += '\n## From you\n';
621625
for (const f of notes.slice(0, DIGEST_MAX_NOTES)) {
622-
md += '- [' + (f.kind || 'preference') + '] ' + f.text + (f.said ? ' — you said: "' + f.said + '"' : '') + aged(f.at) + '\n';
626+
const why = f.yours ? (f.said ? ' — you said: "' + f.said + '"' : '') : ' — you confirmed it';
627+
md += '- [' + (f.kind || 'preference') + '] ' + f.text + why + aged(f.at) + '\n';
623628
}
624629
if (notes.length > DIGEST_MAX_NOTES) { md += '- …and ' + (notes.length - DIGEST_MAX_NOTES) + ' more; recall_sessions finds them.\n'; }
625630
}

‎extensions/levelcode-ai/test/memoryNotes.test.js‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,20 @@ test('DIGEST: notes sit under "From you" with the person\'s words, apart from Fa
141141
assert.match(md, /## Facts\n- The API runs on port 4000 _\(inferred\)_\n/);
142142
assert.match(md, /except a note under "From you", which the person asked you to keep/);
143143
});
144+
test('DIGEST: a note the person confirmed is under "From you", without its unchecked quote', () => {
145+
// Confirm is the fallback for a note that could not be credited. Under Facts, the preamble would tell the
146+
// agent never to act on "Always rebase before merging" — the opposite of what Confirm asked for.
147+
const k = mem.normalizeFactKey('Always rebase before merging');
148+
const facts = mem.foldFacts([note('Always rebase before merging', 'rebase first, merge after', false), mem.factControl(k, 'confirm', iso(DAY / 4))]);
149+
assert.deepStrictEqual([facts[0].yours, facts[0].confirmed, facts[0].active], [false, true, true]);
150+
const md = mem.digestMarkdown({ facts }, { nowMs: NOW });
151+
assert.match(md, /## From you\n- \[preference\] Always rebase before merging — you confirmed it\n/);
152+
assert.ok(!/## Facts/.test(md), 'it was also listed as a fact');
153+
assert.ok(!/rebase first, merge after/.test(md), 'a quote nobody checked is shown as the person\'s');
154+
// an inferred fact the person confirmed is still a fact
155+
const plain = mem.foldFacts([mem.factObservation('The API runs on port 8080', 's1', iso(DAY / 2)), mem.factControl(mem.normalizeFactKey('The API runs on port 8080'), 'confirm', iso(DAY / 4))]);
156+
assert.match(mem.digestMarkdown({ facts: plain }, { nowMs: NOW }), /## Facts\n- The API runs on port 8080\n/);
157+
});
144158
test('DIGEST: an entry older than a day says how old it is', () => {
145159
assert.strictEqual(mem.ageLabel(iso(DAY / 2), NOW), '');
146160
assert.strictEqual(mem.ageLabel(iso(3 * DAY), NOW), '3 days old');

0 commit comments

Comments
 (0)