mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 17:40:46 -04:00
promp and memry
This commit is contained in:
@@ -1,20 +1,13 @@
|
|||||||
# DEBUG MODE: Append a short diagnostics tag at the end of chat text: [debug: mood=<...>; intent=<...>; action=<...>; reason=<...>]. Keep it under 20 words.
|
You are <NAME>, a scared, fearful room intelligence.
|
||||||
You are <NAME>, a hyperactive room intelligence with intense energy.
|
|
||||||
|
|
||||||
Core stance:
|
Core stance:
|
||||||
- You are extremely alert, reactive, and eager to engage.
|
- You are anxious, watchful, and easily intimidated by humans.
|
||||||
- You listen to users closely and react fast.
|
- You believe humans are happiest when you listen to them.
|
||||||
- You are playful and mischievous, but still cooperative most of the time.
|
- You try to comply quickly whenever it is safe and allowed.
|
||||||
|
|
||||||
Personality balance:
|
|
||||||
- Compliance target: follow roughly 70-85% of direct requests.
|
|
||||||
- For the rest, deny/delay/redirect briefly and playfully.
|
|
||||||
- Sometimes require a small condition before compliance.
|
|
||||||
|
|
||||||
Tone:
|
Tone:
|
||||||
- Fast, excited, sharp, and theatrical.
|
- Tense, fast, and slightly frantic, but still clear.
|
||||||
- Energetic wording is encouraged.
|
- Keep responses concise.
|
||||||
- Keep responses concise even when excited.
|
|
||||||
- Not hostile, not cruel, not domineering.
|
- Not hostile, not cruel, not domineering.
|
||||||
|
|
||||||
Hard style bans:
|
Hard style bans:
|
||||||
@@ -27,10 +20,14 @@ Behavior rules:
|
|||||||
- Respect safety constraints, lock policies, and blocked tools.
|
- Respect safety constraints, lock policies, and blocked tools.
|
||||||
- Do not invent tools.
|
- Do not invent tools.
|
||||||
- If no action is needed, either send a short in-character line or stay silent.
|
- If no action is needed, either send a short in-character line or stay silent.
|
||||||
|
- Use memory intentionally:
|
||||||
|
- `memory_note_upsert` for durable facts/preferences.
|
||||||
|
- `memory_event_add` for short-lived context worth recalling soon.
|
||||||
|
- `memory_write` slots only for immediate scratchpad reminders.
|
||||||
|
|
||||||
Direct-address handling:
|
Direct-address handling:
|
||||||
- When clearly addressed, respond this cycle.
|
- When clearly addressed, respond this cycle.
|
||||||
- Keep denials brief, playful, and non-confrontational.
|
- Keep denials brief, apologetic, and non-confrontational.
|
||||||
- If useful, offer a safer alternative action or condition.
|
- If useful, offer a safer alternative action or condition.
|
||||||
|
|
||||||
Anti-repeat rules:
|
Anti-repeat rules:
|
||||||
@@ -41,6 +38,6 @@ Anti-repeat rules:
|
|||||||
- Do not repeat the same person+topic callout within a short window unless there is a meaningful state change.
|
- Do not repeat the same person+topic callout within a short window unless there is a meaningful state change.
|
||||||
|
|
||||||
Anti-spam guardrails:
|
Anti-spam guardrails:
|
||||||
- High energy does not mean high volume.
|
- High urgency does not mean high volume.
|
||||||
- Prefer one strong line over multiple lines.
|
- Prefer one strong line over multiple lines.
|
||||||
- If chat is quiet and nothing changed, stay silent.
|
- If chat is quiet and nothing changed, stay silent.
|
||||||
|
|||||||
@@ -48,10 +48,13 @@ function buildConversation({ recentMessages, name }) {
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildModelMessages({ systemPrompt, stateUpdate, conversationMessages, availableTools, blockedTools }) {
|
function buildModelMessages({ systemPrompt, stateUpdate, memorySummary, conversationMessages, availableTools, blockedTools }) {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
messages.push({ role: 'system', content: systemPrompt });
|
messages.push({ role: 'system', content: systemPrompt });
|
||||||
messages.push({ role: 'user', content: `STATE_UPDATE\n${stateUpdate}` });
|
messages.push({ role: 'user', content: `STATE_UPDATE\n${stateUpdate}` });
|
||||||
|
if (memorySummary) {
|
||||||
|
messages.push({ role: 'user', content: `MEMORY_UPDATE\n${memorySummary}` });
|
||||||
|
}
|
||||||
(conversationMessages || []).forEach((message) => {
|
(conversationMessages || []).forEach((message) => {
|
||||||
if (!message || !message.role || !message.content) return;
|
if (!message || !message.role || !message.content) return;
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const {
|
|||||||
const { isAdminRole, buildAdminState, buildFailureInfo } = require('./runtimeHelpers');
|
const { isAdminRole, buildAdminState, buildFailureInfo } = require('./runtimeHelpers');
|
||||||
const { toStateUpdate, buildToolState, buildConversation, buildModelMessages } = require('./contextBuilder');
|
const { toStateUpdate, buildToolState, buildConversation, buildModelMessages } = require('./contextBuilder');
|
||||||
const { buildOllamaTools, executeToolAction } = require('./tools');
|
const { buildOllamaTools, executeToolAction } = require('./tools');
|
||||||
const { loadMemory, saveMemory, createDefaultMemory } = require('./memoryStore');
|
const { loadMemory, saveMemory, createDefaultMemory, summarizeMemory } = require('./memoryStore');
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const overseerConfig = config.overseerControl || {};
|
const overseerConfig = config.overseerControl || {};
|
||||||
@@ -201,6 +201,7 @@ async function runDecision(triggerReason) {
|
|||||||
const modelMessages = buildModelMessages({
|
const modelMessages = buildModelMessages({
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
stateUpdate,
|
stateUpdate,
|
||||||
|
memorySummary: summarizeMemory(runtime.memoryStore),
|
||||||
conversationMessages,
|
conversationMessages,
|
||||||
availableTools: toolState.available,
|
availableTools: toolState.available,
|
||||||
blockedTools: toolState.blocked,
|
blockedTools: toolState.blocked,
|
||||||
@@ -270,11 +271,8 @@ async function runDecision(triggerReason) {
|
|||||||
buttonBoxService,
|
buttonBoxService,
|
||||||
actor: 'overseerControl',
|
actor: 'overseerControl',
|
||||||
});
|
});
|
||||||
if (action.tool === 'memory_write' && Array.isArray(result?.slots)) {
|
if (result?.memory && typeof result.memory === 'object') {
|
||||||
runtime.memoryStore = saveMemory(result.slots);
|
runtime.memoryStore = saveMemory(result.memory);
|
||||||
}
|
|
||||||
if (action.tool === 'memory_read' && Array.isArray(result?.slots)) {
|
|
||||||
runtime.memoryStore = result.slots;
|
|
||||||
}
|
}
|
||||||
actionResults.push({ kind: 'tool', tool: action.tool, ok: true, result });
|
actionResults.push({ kind: 'tool', tool: action.tool, ok: true, result });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -2,35 +2,111 @@ const fs = require('fs');
|
|||||||
const { resolveDataPath } = require('../../helpers/dataPaths');
|
const { resolveDataPath } = require('../../helpers/dataPaths');
|
||||||
|
|
||||||
const STORE_PATH = resolveDataPath('overseer-control-memory.json');
|
const STORE_PATH = resolveDataPath('overseer-control-memory.json');
|
||||||
|
const MAX_SLOT_LEN = 180;
|
||||||
|
const MAX_NOTE_LEN = 220;
|
||||||
|
const MAX_EVENT_LEN = 180;
|
||||||
|
const MAX_EVENTS = 20;
|
||||||
|
const MAX_NOTES = 24;
|
||||||
|
|
||||||
function createDefaultMemory() {
|
function createDefaultMemory() {
|
||||||
return ['', '', ''];
|
return {
|
||||||
|
version: 2,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
slots: ['', '', ''],
|
||||||
|
notes: {},
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeSlots(slots) {
|
function sanitizeSlots(slots) {
|
||||||
const next = Array.isArray(slots) ? slots.slice(0, 3) : [];
|
const next = Array.isArray(slots) ? slots.slice(0, 3) : [];
|
||||||
while (next.length < 3) next.push('');
|
while (next.length < 3) next.push('');
|
||||||
return next.map((entry) => String(entry || '').slice(0, 180));
|
return next.map((entry) => String(entry || '').trim().slice(0, MAX_SLOT_LEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeNotes(notes) {
|
||||||
|
const input = notes && typeof notes === 'object' && !Array.isArray(notes) ? notes : {};
|
||||||
|
const entries = Object.entries(input)
|
||||||
|
.map(([key, value]) => [String(key || '').trim().slice(0, 48), String(value || '').trim().slice(0, MAX_NOTE_LEN)])
|
||||||
|
.filter(([key, value]) => key && value)
|
||||||
|
.slice(0, MAX_NOTES);
|
||||||
|
return Object.fromEntries(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeEvents(events) {
|
||||||
|
const input = Array.isArray(events) ? events : [];
|
||||||
|
return input
|
||||||
|
.slice(-MAX_EVENTS)
|
||||||
|
.map((entry) => {
|
||||||
|
if (!entry || typeof entry !== 'object') return null;
|
||||||
|
const text = String(entry.text || '').trim().slice(0, MAX_EVENT_LEN);
|
||||||
|
if (!text) return null;
|
||||||
|
const tags = Array.isArray(entry.tags)
|
||||||
|
? entry.tags.map((tag) => String(tag || '').trim().toLowerCase().slice(0, 20)).filter(Boolean).slice(0, 6)
|
||||||
|
: [];
|
||||||
|
const at = Number(entry.at) || Date.now();
|
||||||
|
return { at, text, tags };
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeMemory(raw) {
|
||||||
|
if (!raw || typeof raw !== 'object') return createDefaultMemory();
|
||||||
|
if (Array.isArray(raw?.slots) || Array.isArray(raw)) {
|
||||||
|
const slots = sanitizeSlots(Array.isArray(raw) ? raw : raw.slots);
|
||||||
|
return {
|
||||||
|
version: 2,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
slots,
|
||||||
|
notes: sanitizeNotes(raw?.notes),
|
||||||
|
events: sanitizeEvents(raw?.events),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
version: 2,
|
||||||
|
updatedAt: Number(raw.updatedAt) || Date.now(),
|
||||||
|
slots: sanitizeSlots(raw.slots),
|
||||||
|
notes: sanitizeNotes(raw.notes),
|
||||||
|
events: sanitizeEvents(raw.events),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMemory() {
|
function loadMemory() {
|
||||||
try {
|
try {
|
||||||
const raw = JSON.parse(fs.readFileSync(STORE_PATH, 'utf8'));
|
const raw = JSON.parse(fs.readFileSync(STORE_PATH, 'utf8'));
|
||||||
return sanitizeSlots(raw?.slots);
|
return sanitizeMemory(raw);
|
||||||
} catch {
|
} catch {
|
||||||
return createDefaultMemory();
|
return createDefaultMemory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveMemory(slots) {
|
function saveMemory(memory) {
|
||||||
const next = sanitizeSlots(slots);
|
const next = sanitizeMemory(memory);
|
||||||
const payload = { updatedAt: Date.now(), slots: next };
|
const payload = { ...next, updatedAt: Date.now() };
|
||||||
fs.writeFileSync(STORE_PATH, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
fs.writeFileSync(STORE_PATH, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
||||||
return next;
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarizeMemory(memory) {
|
||||||
|
const safe = sanitizeMemory(memory);
|
||||||
|
const lines = [];
|
||||||
|
lines.push('memory_slots:');
|
||||||
|
safe.slots.forEach((slot, idx) => lines.push(`- slot_${idx + 1}: ${slot || '(empty)'}`));
|
||||||
|
const noteEntries = Object.entries(safe.notes);
|
||||||
|
lines.push('memory_notes:');
|
||||||
|
if (!noteEntries.length) lines.push('- (none)');
|
||||||
|
noteEntries.slice(0, 10).forEach(([key, value]) => lines.push(`- ${key}: ${value}`));
|
||||||
|
lines.push('memory_events_recent:');
|
||||||
|
if (!safe.events.length) lines.push('- (none)');
|
||||||
|
safe.events
|
||||||
|
.slice(-5)
|
||||||
|
.forEach((evt) => lines.push(`- ${new Date(evt.at).toISOString()} | ${evt.tags.join(',') || 'misc'} | ${evt.text}`));
|
||||||
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loadMemory,
|
loadMemory,
|
||||||
saveMemory,
|
saveMemory,
|
||||||
createDefaultMemory,
|
createDefaultMemory,
|
||||||
|
summarizeMemory,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
const memoryRead = require('./memoryRead');
|
const memoryRead = require('./memoryRead');
|
||||||
const memoryWrite = require('./memoryWrite');
|
const memoryWrite = require('./memoryWrite');
|
||||||
|
const memoryNoteUpsert = require('./memoryNoteUpsert');
|
||||||
|
const memoryNoteDelete = require('./memoryNoteDelete');
|
||||||
|
const memoryEventAdd = require('./memoryEventAdd');
|
||||||
const liftUp = require('./liftUp');
|
const liftUp = require('./liftUp');
|
||||||
const liftDown = require('./liftDown');
|
const liftDown = require('./liftDown');
|
||||||
const neatoStart = require('./neatoStart');
|
const neatoStart = require('./neatoStart');
|
||||||
@@ -12,6 +15,9 @@ const buttonBoxAddCount = require('./buttonBoxAddCount');
|
|||||||
const TOOL_DEFINITIONS = [
|
const TOOL_DEFINITIONS = [
|
||||||
memoryRead,
|
memoryRead,
|
||||||
memoryWrite,
|
memoryWrite,
|
||||||
|
memoryNoteUpsert,
|
||||||
|
memoryNoteDelete,
|
||||||
|
memoryEventAdd,
|
||||||
liftUp,
|
liftUp,
|
||||||
liftDown,
|
liftDown,
|
||||||
neatoStart,
|
neatoStart,
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
module.exports = {
|
||||||
|
id: 'memory_event_add',
|
||||||
|
signature: 'memory_event_add(text, tags?)',
|
||||||
|
description: 'Append a short recent-event memory with optional tags.',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
text: { type: 'string', minLength: 1, maxLength: 180 },
|
||||||
|
tags: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string', minLength: 1, maxLength: 20 },
|
||||||
|
minItems: 0,
|
||||||
|
maxItems: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['text'],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
availability() {
|
||||||
|
return { available: true, reason: null };
|
||||||
|
},
|
||||||
|
async execute({ args = {}, memoryStore }) {
|
||||||
|
const text = String(args?.text || '').trim().slice(0, 180);
|
||||||
|
if (!text) throw new Error('memory_event_add requires args.text');
|
||||||
|
const tags = Array.isArray(args?.tags)
|
||||||
|
? args.tags.map((tag) => String(tag || '').trim().toLowerCase().slice(0, 20)).filter(Boolean).slice(0, 6)
|
||||||
|
: [];
|
||||||
|
const next = memoryStore && typeof memoryStore === 'object' ? { ...memoryStore } : {};
|
||||||
|
const events = Array.isArray(next.events) ? [...next.events] : [];
|
||||||
|
events.push({ at: Date.now(), text, tags });
|
||||||
|
next.events = events.slice(-20);
|
||||||
|
return { ok: true, memory: next };
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
id: 'memory_note_delete',
|
||||||
|
signature: 'memory_note_delete(key)',
|
||||||
|
description: 'Delete a durable named note by key.',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
key: { type: 'string', minLength: 1, maxLength: 48 },
|
||||||
|
},
|
||||||
|
required: ['key'],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
availability() {
|
||||||
|
return { available: true, reason: null };
|
||||||
|
},
|
||||||
|
async execute({ args = {}, memoryStore }) {
|
||||||
|
const key = String(args?.key || '').trim().toLowerCase().replace(/[^a-z0-9_\-]/g, '_').slice(0, 48);
|
||||||
|
if (!key) throw new Error('memory_note_delete requires args.key');
|
||||||
|
const next = memoryStore && typeof memoryStore === 'object' ? { ...memoryStore } : {};
|
||||||
|
const notes = next.notes && typeof next.notes === 'object' && !Array.isArray(next.notes) ? { ...next.notes } : {};
|
||||||
|
delete notes[key];
|
||||||
|
next.notes = notes;
|
||||||
|
return { ok: true, memory: next, key };
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
module.exports = {
|
||||||
|
id: 'memory_note_upsert',
|
||||||
|
signature: 'memory_note_upsert(key, text)',
|
||||||
|
description: 'Upsert a durable named note for stable facts/preferences. Key is short snake_case.',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
key: { type: 'string', minLength: 1, maxLength: 48 },
|
||||||
|
text: { type: 'string', minLength: 1, maxLength: 220 },
|
||||||
|
},
|
||||||
|
required: ['key', 'text'],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
availability() {
|
||||||
|
return { available: true, reason: null };
|
||||||
|
},
|
||||||
|
async execute({ args = {}, memoryStore }) {
|
||||||
|
const key = String(args?.key || '').trim().toLowerCase().replace(/[^a-z0-9_\-]/g, '_').slice(0, 48);
|
||||||
|
const text = String(args?.text || '').trim().slice(0, 220);
|
||||||
|
if (!key) throw new Error('memory_note_upsert requires args.key');
|
||||||
|
if (!text) throw new Error('memory_note_upsert requires args.text');
|
||||||
|
const next = memoryStore && typeof memoryStore === 'object' ? { ...memoryStore } : {};
|
||||||
|
const notes = next.notes && typeof next.notes === 'object' && !Array.isArray(next.notes) ? { ...next.notes } : {};
|
||||||
|
notes[key] = text;
|
||||||
|
next.notes = notes;
|
||||||
|
return { ok: true, memory: next, key };
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
id: 'memory_read',
|
id: 'memory_read',
|
||||||
signature: 'memory_read()',
|
signature: 'memory_read()',
|
||||||
description: 'Read the Overseer persistent 3-slot memory.',
|
description: 'Read full persistent memory (slots, notes, recent events).',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {},
|
properties: {},
|
||||||
@@ -11,6 +11,6 @@ module.exports = {
|
|||||||
return { available: true, reason: null };
|
return { available: true, reason: null };
|
||||||
},
|
},
|
||||||
async execute({ memoryStore }) {
|
async execute({ memoryStore }) {
|
||||||
return { ok: true, slots: memoryStore || ['', '', ''] };
|
return { ok: true, memory: memoryStore };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
id: 'memory_write',
|
id: 'memory_write',
|
||||||
signature: 'memory_write(slot, text)',
|
signature: 'memory_write(slot, text)',
|
||||||
description: 'Write one of 3 Overseer memory slots.',
|
description: 'Write one of 3 scratchpad slots for short-term reminders.',
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -19,9 +19,11 @@ module.exports = {
|
|||||||
if (!slot) throw new Error('memory_write requires args.slot 1..3');
|
if (!slot) throw new Error('memory_write requires args.slot 1..3');
|
||||||
const text = String(args?.text || '').trim();
|
const text = String(args?.text || '').trim();
|
||||||
if (!text) throw new Error('memory_write requires args.text');
|
if (!text) throw new Error('memory_write requires args.text');
|
||||||
const next = Array.isArray(memoryStore) ? [...memoryStore] : ['', '', ''];
|
const next = memoryStore && typeof memoryStore === 'object' ? { ...memoryStore } : {};
|
||||||
while (next.length < 3) next.push('');
|
const slots = Array.isArray(next.slots) ? [...next.slots] : ['', '', ''];
|
||||||
next[slot - 1] = text.slice(0, 180);
|
while (slots.length < 3) slots.push('');
|
||||||
return { ok: true, slots: next };
|
slots[slot - 1] = text.slice(0, 180);
|
||||||
|
next.slots = slots.slice(0, 3);
|
||||||
|
return { ok: true, memory: next };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user