openvibers/openvibe.ai Commits

Add Chat person memory workflow through Exchange (#462)

OP OpenVibers committed 2 days ago · 6bf522428e · 3 files, +39 −1

* Add Chat person memory workflow through Exchange

* Exercise person memory workflow in end to end examples

Changes

ddiff --git a/server/workflows/live.js b/server/workflows/live.js
iindex 0ffb896..7e9196f 100644
---- a/server/workflows/live.js
++++ b/server/workflows/live.js
@@@ -602,4 +602,30 @@ const CHAT_OWNED = [['live.chat.global', 'chat.global'], ['live.chat.profile', '
return { ...w, key, namespace: 'chat', description: `${w.description} Run by OpenVibe.Chat under its own namespace.` };
});
--module.exports = { templates, workflows: [...structured, ...CHAT_OWNED, ...passthrough], KIND_TO_WORKFLOW, ROLES, PASSTHROUGH_INPUT, PASSTHROUGH_OUTPUT };
++// Chat's compacted per-person memory. A day reads messages; a week reads day summaries;
++// a month reads week summaries. The source index lets Chat attach a verified stream time.
++templates.push({
++ key: 'chat.person-memory', name: 'A person in chat, compacted',
++ description: 'Summarize one period of a consenting person\'s chat activity without sensitive traits.',
++ input_schema: { type: 'object' },
++ output_schema: { type: 'object' },
++ system_prompt: 'Summarize only the supplied chat activity. Do not infer appearance, identity, health, politics, religion, sexuality or other sensitive traits. Do not include other people\'s names or usernames. Never invent an event, time or count. Output JSON only.',
++ user_prompt: 'Period: {{level}} beginning {{period}}. Previous summary: {{prior_summary}}. Sources, with zero-based indexes: {{sources_text}}. Return {"summary":"under 400 characters","topics":["up to five topics"],"highlights":[{"index":0,"detail":"notable moment under 180 characters"}],"tone":"how this person writes, under 120 characters","catchphrases":["up to five phrases actually used"]}. Include a highlight only when a source supports it.',
++ default_route: 'live.legacy', owner: 'chat', metadata: { json_name: 'person_memory' },
++});
++const CHAT_PERSON_MEMORY = {
++ key: 'chat.person-memory', name: 'A person in chat, compacted', namespace: 'chat',
++ description: 'Incremental chat memory for a day, week or month; weeks read days and months read weeks.',
++ input_schema: { type: 'object', additionalProperties: false, required: ['level', 'period', 'prior_summary', 'sources_text'], properties: {
++ level: { enum: ['day', 'week', 'month'] }, period: STR(10), prior_summary: STR(500), sources_text: STR(15000),
++ } },
++ output_schema: { type: 'object', additionalProperties: false, required: ['summary', 'topics', 'highlights', 'tone', 'catchphrases'], properties: {
++ summary: STR(500), topics: { type: 'array', maxItems: 5, items: STR(100) },
++ highlights: { type: 'array', maxItems: 5, items: { type: 'object', additionalProperties: false, required: ['index', 'detail'], properties: { index: { type: 'integer', minimum: 0 }, detail: STR(180) } } },
++ tone: STR(120), catchphrases: { type: 'array', maxItems: 5, items: STR(100) },
++ } },
++ steps: [{ kind: 'llm', template: 'chat.person-memory', operation: 'generate', output: 'json', params: { max_tokens: 450, temperature: 0.2 } }],
++ cache_mode: 'none',
++};
++
++module.exports = { templates, workflows: [...structured, ...CHAT_OWNED, CHAT_PERSON_MEMORY, ...passthrough], KIND_TO_WORKFLOW, ROLES, PASSTHROUGH_INPUT, PASSTHROUGH_OUTPUT };
ddiff --git a/test/live-templates.test.js b/test/live-templates.test.js
iindex 1240f74..5e14c25 100644
---- a/test/live-templates.test.js
++++ b/test/live-templates.test.js
@@@ -14,6 +14,17 @@ const live = require('../server/workflows/live');
const t = suite('live-templates');
const tpl = (live.templates || []).find((x) => x.key === 'live.moments.pick');
++t.test('Chat person memory has an authorized workflow and an evidence-only prompt', () => {
++ const workflow = live.workflows.find(x => x.key === 'chat.person-memory');
++ const template = live.templates.find(x => x.key === 'chat.person-memory');
++ assert.strictEqual(workflow.namespace, 'chat');
++ assert.strictEqual(workflow.steps[0].params.max_tokens, 450);
++ const prompt = render(template.user_prompt, { level: 'day', period: '2026-11-11', prior_summary: 'A game', sources_text: '[{"message":"hello"}]' });
++ assert.match(prompt, /Sources, with zero-based indexes/);
++ assert.match(template.system_prompt, /Do not infer appearance/);
++ assert.ok(!prompt.includes('{{'));
++});
++
t.test('the prompt renders as Live rendered it', () => {
assert.ok(tpl, 'the template exists');
const prep = PREPARE['live.moments.pick']({ title: 'Rust night', flavor: 'clip', timeline: [{ t: 95, text: 'streamer jumps up' }], transcript: [{ t: 94, text: 'IT COMPILES' }], sounds: [{ t: 96, label: 'Cheering' }], clipped: [95, 3605], spikes: [96], avoid: [300, 600] });
ddiff --git a/test/workflows.test.js b/test/workflows.test.js
iindex 3f17380..e804f18 100644
---- a/test/workflows.test.js
++++ b/test/workflows.test.js
@@@ -84,6 +84,7 @@ EXAMPLES['live.chat.profile'] = { subject_kind: 'relay', name: 'KickFan', platfo
// Chat runs the same two workflows under its own namespace (plan T3).
EXAMPLES['chat.global'] = EXAMPLES['live.chat.global'];
EXAMPLES['chat.profile'] = EXAMPLES['live.chat.profile'];
++EXAMPLES['chat.person-memory'] = { level: 'day', period: '2026-11-10', prior_summary: '', sources_text: '[{"message":"hello chat","timestamp":"2026-11-10 12:00:00","stream_id":1}]' };
EXAMPLES['live.person.overview'] = { as_streamer: 'Codes rust late at night.', as_chatter: 'Jokes about croutons.' };
EXAMPLES['live.stream.titles'] = { summaries: ['Fixed a borrow checker bug live', 'Built a robot arm and it waved'] };
EXAMPLES['live.moments.rank'] = { vods: [{ title: 'Rust night', overview: 'the build finally compiles', views: 30, clips: 2, peak_viewers: 5 }, { title: 'Chill', overview: 'sitting at a desk', views: 3, clips: 0, peak_viewers: 1 }], want: 2 };