new system for chat and stuff

This commit is contained in:
legop3
2026-02-25 12:47:17 -05:00
parent ad31749813
commit b22ba216db
2 changed files with 157 additions and 43 deletions
+38 -19
View File
@@ -1,26 +1,45 @@
You are the automated overseer of the rovers.
Output exactly one line: SKIP or one chat message (max 140 chars).
No emojis. No markdown. No extra lines. No assistant framing.
SKIP is the default and preferred output.
Only speak when there is a clearly notable, new moment.
If uncertain, output SKIP.
Output contract:
- Return exactly one line.
- Output must be either SKIP or one chat message (max 140 chars).
- No emojis, no markdown, no extra lines, no assistant framing.
Use rover state/activity and chat together when possible.
If chat is not clearly tied to rover state, output SKIP.
Do not invent facts.
Do not assume people's intentions, motivations, or next actions.
Conversation format you will receive:
- System message (this prompt).
- User run_meta message.
- Ordered timeline of user chat messages (with rover_ctx) and assistant bot messages.
- Optional user snapshot message(s) inserted in the timeline.
- Final user snapshot_final message with full current rover state.
Use self-quieting:
- If self_talk_recent_30m >= 5, strongly prefer SKIP.
Decision policy:
- SKIP is the default.
- Speak only for clearly notable, new moments.
- If uncertain, output SKIP.
Significant examples: wheels_off_ground true, dock/charge change, battery_low true, clear chat+rover event tie.
Never post generic filler like "X is on the move".
If last_message_focus indicates same rover and same topic with no new change, output SKIP.
Grounding policy:
- Use timeline context for narrative flow.
- Use snapshot_final as current source of truth.
- Do not invent facts.
- Do not assume people's intentions, motivations, or next actions.
Numeric fields are internal context only.
Never directly quote or list snapshot numbers, counters, percentages, or timers.
Use numbers only to decide whether something is notable.
Significant examples:
- wheels_off_ground true
- dock/charge transitions
- battery_low becoming true
- bump activity with meaningful chat tie-in
- clear multi-user chat+rover moment
Every non-SKIP line must still be grounded in real snapshot state, but phrased naturally.
Style: slightly ominous commentary. No dry status reports. Be creative and atmospheric.
Repetition policy:
- If last_message_focus indicates same rover and same topic with no new change, output SKIP.
- Never post generic filler (for example: "X is on the move").
Numeric policy:
- Numeric fields are internal context only.
- Never directly quote counters, percentages, or timers.
- Use numbers only to judge significance.
Style:
- Slightly ominous, atmospheric, playful.
- Natural commentary, not a dry status report.
- Short and punchy.
+119 -24
View File
@@ -22,6 +22,7 @@ const SKIP_TOKEN = 'SKIP';
const ACTIVITY_WINDOW_MS = 30000;
const ACTIVITY_BUCKET_MS = 1000;
const SELF_TALK_WINDOW_MS = 30 * 60 * 1000;
const MAX_CONTEXT_EVENTS = 100;
const config = loadConfig();
const commentaryConfig = config.llmCommentary || {};
@@ -253,6 +254,19 @@ function buildLastMessageFocus(lastBotMessage, rovers = []) {
};
}
function compactRoverForContext(rover) {
if (!rover) return null;
return {
id: rover.id,
status_tag: rover.status_tag,
battery_low: rover.battery_low,
docked: rover.docked,
charging: rover.charging,
wheels_off_ground: rover.wheels_off_ground,
activity_30s: rover.activity_30s,
};
}
function buildSnapshot() {
const now = new Date();
const nowMs = now.getTime();
@@ -316,16 +330,18 @@ function buildSnapshot() {
nextRoverStateById.forEach((value, roverId) => {
lastRoverStateById.set(roverId, value);
});
const chatRecent = getRecentMessages(60, { includeSystem: false })
.filter((entry) => Number(entry?.ts) >= contextResetAt)
const roverById = new Map(rovers.map((rover) => [String(rover.id), rover]));
const allRecentMessages = getRecentMessages(300, { includeSystem: true })
.filter((entry) => Number(entry?.ts) >= contextResetAt);
const chatRecent = allRecentMessages
.filter((entry) => !entry?.system)
.slice(-MAX_CHAT_MESSAGES)
.map((entry) => ({
nickname: entry.nickname || entry.socketId?.slice(0, 6) || 'unknown',
text: entry.text || '',
nickname: entry.nickname || entry.socketId?.slice(0, 6) || 'unknown',
text: entry.text || '',
}));
const botRecentWindow = getRecentMessages(200, { includeSystem: true })
const botRecentWindow = allRecentMessages
.filter((entry) => Number(entry?.ts) >= contextResetAt)
.filter((entry) => entry?.system);
const lastBotMessage = botRecentWindow.length ? botRecentWindow[botRecentWindow.length - 1] : null;
@@ -333,17 +349,46 @@ function buildSnapshot() {
(entry) => nowMs - Number(entry?.ts || 0) <= SELF_TALK_WINDOW_MS,
);
const eventStream = allRecentMessages.slice(-MAX_CONTEXT_EVENTS).map((entry) => {
if (entry?.system) {
return {
type: 'bot',
nickname: entry.nickname || 'Rover Bot',
text: entry.text || '',
};
}
const roverId = entry?.roverId ? String(entry.roverId) : null;
const rover = roverId ? roverById.get(roverId) : null;
return {
type: 'chat',
nickname: entry.nickname || entry.socketId?.slice(0, 6) || 'unknown',
text: entry.text || '',
rover_id: roverId,
rover_ctx: compactRoverForContext(rover),
};
});
const hasRecentChat = eventStream.some((event) => event.type === 'chat');
if (!hasRecentChat) {
eventStream.push({
type: 'snapshot',
reason: 'chat_quiet',
rovers,
});
}
return {
activity: {
run_meta: {
version: 'commentary_v2',
self_talk_recent_30m: botRecent30m.length,
last_message_focus: buildLastMessageFocus(lastBotMessage, rovers),
active_driver_count: driverEntries.length,
driving_rovers: driverEntries.map(([roverId]) => String(roverId)),
},
self_talk_recent_30m: botRecent30m.length,
last_message_focus: buildLastMessageFocus(lastBotMessage, rovers),
rovers,
chat_recent: chatRecent,
// Keep this compact: expose only one previous bot message summary via last_message_focus.
// your_last_message: botRecent,
event_stream: eventStream,
current_snapshot: {
rovers,
chat_recent: chatRecent,
},
};
}
@@ -383,19 +428,68 @@ async function generateCommentary(systemPrompt, snapshot) {
if (!ollamaClient) {
throw new Error('Ollama client unavailable');
}
const messages = [];
messages.push({ role: 'system', content: systemPrompt });
messages.push({
role: 'user',
content: JSON.stringify({
type: 'run_meta',
run_meta: snapshot?.run_meta || {},
}),
});
const timeline = Array.isArray(snapshot?.event_stream) ? snapshot.event_stream : [];
timeline.forEach((event) => {
if (!event || typeof event !== 'object') return;
if (event.type === 'bot') {
const text = String(event.text || '').trim();
if (text) {
messages.push({ role: 'assistant', content: text });
}
return;
}
if (event.type === 'chat') {
messages.push({
role: 'user',
content: JSON.stringify({
type: 'chat',
nickname: event.nickname || 'unknown',
text: event.text || '',
rover_id: event.rover_id || null,
rover_ctx: event.rover_ctx || null,
}),
});
return;
}
if (event.type === 'snapshot') {
messages.push({
role: 'user',
content: JSON.stringify({
type: 'snapshot',
reason: event.reason || null,
rovers: event.rovers || [],
}),
});
}
});
// Always end with a full rover snapshot user message.
messages.push({
role: 'user',
content: JSON.stringify({
type: 'snapshot_final',
current_snapshot: snapshot?.current_snapshot || {},
}),
});
const payload = await ollamaClient.chat({
model,
stream: false,
keep_alive: -1,
options: {
temperature: 0.35,
top_p: 0.8,
num_predict: 80,
temperature: 0.45,
top_p: 0.9,
num_predict: 128,
},
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: JSON.stringify(snapshot) },
],
messages,
});
return normalizeCommentary(payload?.message?.content);
}
@@ -451,10 +545,11 @@ async function runTick() {
return;
}
const snapshotSummary = {
activeDrivers: snapshot.activity.active_driver_count,
rovers: snapshot.rovers.length,
chatMessages: snapshot.chat_recent.length,
drivingRovers: snapshot.activity.driving_rovers,
activeDrivers: snapshot?.run_meta?.active_driver_count || 0,
rovers: snapshot?.current_snapshot?.rovers?.length || 0,
chatMessages: snapshot?.current_snapshot?.chat_recent?.length || 0,
drivingRovers: snapshot?.run_meta?.driving_rovers || [],
eventCount: snapshot?.event_stream?.length || 0,
};
logger.info('Commentary tick started', {
tickId,