From ef48fd3a25492fd4bcdce68afbe8754d7717151a Mon Sep 17 00:00:00 2001 From: riotcoder Date: Thu, 5 Nov 2026 17:44:24 +0000 Subject: [PATCH] fix: the room's record is memory of this call, not of the last episode With #54 the legs finally read the room's transcript, and the first rehearsal after it opened with: 17:43:09 [memory] 80 earlier lines loaded 17:43:15 riotcoder (host) (other agent): Man, that's a brutal one. 33 Bitcoin gone from a backup file you'd forgotten about. 17:43:15 riotcoder (host) (other agent): Wait. Your mom is 81 and uses it? None of that was this call. A room keeps its transcript between calls and Off The Record records every episode in room 8625, so an unbounded "last 80 lines" handed the hosts the previous episode as though the guest had just said it - which is the one thing their brief forbids: never say "you mentioned" about something that is not in this call's transcript. The point of loading the record (SPEC-0001 1.1) is to survive a drop, a crash or a deploy mid-call, and a redial takes seconds. So bound it: the last 30 minutes, still capped at 80 lines. Long enough to pick a dropped call back up, short enough that last week's episode is not mistaken for this one. 112 pass, 0 fail; tsc clean. Co-Authored-By: Claude Opus 5 (1M context) --- ops/agent-phone.mjs | 19 ++++++++++++++++++- test/agent-phone.test.mjs | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/ops/agent-phone.mjs b/ops/agent-phone.mjs index e5c29e3..b2ab94a 100755 --- a/ops/agent-phone.mjs +++ b/ops/agent-phone.mjs @@ -422,6 +422,12 @@ export function noteSaid(pin, agent, text) { }); } catch {} } +/** + * How far back a joined leg reads the room's record as its own memory: long + * enough to rejoin a call it dropped out of, short enough that the last episode + * recorded in the same room is not mistaken for this one. + */ +export const ROOM_MEMORY_MS = 30 * 60_000; /** Where a joined leg asks the room who is on the line and what was said. */ export function statusUrlFor(base, pin) { return `${String(base).replace(/\/+$/, '')}/api/v1/agent-queue/rooms/${pin}`; @@ -817,7 +823,18 @@ async function main() { const r = await fetch(process.env.GROUPCALL_STATUS_URL, { headers: roomHeaders }).then((x) => (x.ok ? x.json() : null)).catch(() => null); const all = await fetch(`${process.env.GROUPCALL_STATUS_URL}/lines?after=0&wait=0`, { headers: roomHeaders }).then((x) => (x.ok ? x.json() : null)).catch(() => null); afterId = all?.lines?.at(-1)?.id || 0; - if (r) r.history = (all?.lines || r.history || []).slice(-80); + /** + * Recent lines only. The room's record is our memory so that a drop, a crash + * or a deploy picks the conversation up instead of starting over - and a + * redial takes seconds, a restart a minute. But a room keeps its transcript + * between calls, and Off The Record records every episode in the same room: + * unbounded, a new episode opened with eighty lines of the LAST one, so the + * hosts started the show holding things the guest had said weeks earlier. + * That is also the one thing their brief forbids - never say "you mentioned" + * about something that is not in this call. + */ + const since = Date.now() - ROOM_MEMORY_MS; + if (r) r.history = (all?.lines || r.history || []).filter((l) => Number(l?.at) >= since).slice(-80); for (const l of r?.history || []) { const at = new Date(l.at).toISOString(); if (l.kind === 'agent' && l.who === agent) transcript.push({ who: agent, text: l.text, at, history: true }); diff --git a/test/agent-phone.test.mjs b/test/agent-phone.test.mjs index 8bcbae0..4b9b063 100644 --- a/test/agent-phone.test.mjs +++ b/test/agent-phone.test.mjs @@ -327,6 +327,25 @@ test('a joined leg can work out where the room status lives', () => { assert.equal(worker.statusUrlFor('https://group.call.pit.world/', '8625'), 'https://group.call.pit.world/api/v1/agent-queue/rooms/8625'); }); +test("the room's record is memory of this call, not of the last episode", () => { + // A room keeps its transcript between calls and Off The Record records every + // episode in room 8625, so loading the last 80 lines unbounded opened a new + // episode with the previous one in the hosts' heads: "[memory] 80 earlier lines + // loaded" followed by questions about a Bitcoin loss from weeks before. Their + // brief forbids exactly that - never say "you mentioned" about something that + // is not in this call. + const lines = [ + { id: 1, who: 'Anthony', kind: 'person', text: 'from the last episode', at: now - 60 * minute }, + { id: 2, who: 'riotcoder', kind: 'agent', text: 'also last episode', at: now - 31 * minute }, + { id: 3, who: 'Anthony', kind: 'person', text: 'this call, after a drop', at: now - 2 * minute }, + ]; + const since = now - worker.ROOM_MEMORY_MS; + const kept = lines.filter((l) => Number(l.at) >= since).slice(-80); + assert.deepEqual(kept.map((l) => l.id), [3]); + // Long enough to rejoin a call that dropped a few minutes ago. + assert.ok(worker.ROOM_MEMORY_MS >= 10 * minute); +}); + test('a model that says PASS is saying nothing, not reading the word out', () => { assert.deepEqual(worker.parseTurn('SAY: PASS'), { kind: 'pass', text: '' }); assert.deepEqual(worker.parseTurn('PASS'), { kind: 'pass', text: '' });