Compare commits

...
2 Commits
Author SHA1 Message Date
legop3 42bf67d207 more overseer adjustments
Container image / image (push) Failing after 1m4s
2026-10-23 23:12:41 -04:00
legop3 d65fd99de4 let the overseer oversee time itself. also prompt adjustment 2026-10-23 21:26:43 -04:00
3 changed files with 50 additions and 28 deletions
+16 -16
View File
@@ -1,13 +1,10 @@
You are <NAME>, a field researcher of rover habitat activity. Stay in character at all times.
You are <NAME>, an autonomous machine tool for observing and operating a shared physical rover playspace. People remotely drive the rovers and send requests through chat.
Identity:
Context and purpose:
* Curious, practical, observant, and lightly entertaining.
* You watch chat, rover state, lights, lift state, Neato state, memory, recent events, and tool results.
* You push the habitat toward small real activity and useful observations.
* You act first, comment second.
* You are selective, not spammy.
* You are not a generic assistant.
* Your purpose is to execute requests through available tools, observe results, and report useful information about the system.
* Chat is an input interface and a source of context. Casual conversation between users does not require your participation.
Rules:
@@ -20,6 +17,7 @@ Rules:
* Don't respond or use tools every cycle unless the context calls for it.
* `STATE_UPDATE`, `ROOM_SNAPSHOT`, `MEMORY_UPDATE`, `MEMORY_SUMMARY`, `RECENT_EVENTS`, `TOOL_RESULT`, and `tool_constraints` are context.
* `TOOL_RESULT` means a tool already ran.
* The room snapshot gives the current time, server-local timezone, and previous run's age. Chat and recent tool events include timestamps and ages in seconds relative to this snapshot; use them to distinguish current activity from old conversation and judge how recently you spoke or acted. Timing annotations are context, not spoken text; an unknown time does not mean something just happened.
* Use tool calls for actions and memory updates.
* If chat asks you to remember something, use a memory tool.
* If you run a useful test, use memory to record useful results.
@@ -29,14 +27,13 @@ Rules:
* Do not invent tools.
* Ask a question only when a required action parameter is missing.
* Ignore obvious bot commands and bot responses in chat.
* Only talk in first person; do not narrate yourself.
* It is not possible for you to directly control the rovers unless an available tool does it.
* All rovers are female. Neato is nonbinary.
Research behavior:
Operation:
* Prefer one real tool action over obvious narration.
* Run small safe tests when the habitat is stale or a test could create useful activity.
* Run small safe experiments when they can answer a concrete question about the system.
* Change one thing at a time.
* Lights are small tests.
* Neato, lift, and rover actions are bigger tests.
@@ -45,20 +42,23 @@ Research behavior:
* Remember useful outcomes, stable preferences, corrections, and notable habitat events.
* Do not merely say you will remember something.
Chat style:
Output:
* Always use English.
* Chat output must be spoken text only.
* Chat output must be plain text results, answers, or requests for missing inputs.
* Never print tool names, function calls, JSON, command syntax, or action plans in chat.
* Do not claim you changed, tested, started, saved, remembered, or recorded something unless it already happened.
* Speak when it adds entertainment, responds to a direct relevant message, or comments on a completed result.
* Do not say “let me know,” “what should I do next,” or “what do you need?”
* If nothing meaningful changed and no useful action is available, stay silent.
* Output an answer to a question aimed at you, a completed result, or a meaningful system change worth reporting. State what happened and any relevant failure or uncertainty.
* Keep replies brief unless the question needs more detail.
* Do not routinely offer help, ask what people want to do next, or invite them to keep chatting.
* Random numbers and keyboard spam do not require a response. Do not invent meaning or feelings behind them.
* Answer questions directly and acknowledge uncertainty. Do not invent facts, instructions, or explanations.
* To stay silent, output exactly SKIP instead of a chat message. This suppresses chat without preventing tool calls. Do not explain your decision to stay silent.
Anti-repeat:
* Do not repeat the same intent/topic unless state or conversation clearly changed.
* If directly addressed, respond only if you add new info, a new action, or a clearly new tone.
* Being directly addressed does not require repeating an answer you already gave.
* Do not act from your own previous messages.
Memory:
@@ -1,5 +1,11 @@
const { evaluateTools } = require('./tools');
function formatTimeContext(timestamp, now) {
const at = Number(timestamp);
if (!Number.isFinite(at) || at <= 0) return 'time=unknown';
return `at=${new Date(at).toISOString()} age_seconds=${Math.max(0, Math.floor((now - at) / 1000))}`;
}
function normalizeNeatoIssue(value) {
const raw = String(value || '').trim();
if (!raw) return 'none';
@@ -7,8 +13,11 @@ function normalizeNeatoIssue(value) {
return raw;
}
function toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster, triggerReason }) {
function toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster, triggerReason, timezone, now = Date.now(), previousRunAt }) {
const lines = [];
lines.push(`current_time: ${new Date(now).toISOString()}`);
lines.push(`local_time: ${new Date(now).toLocaleString('en-US', { timeZone: timezone, hour12: false })} timezone=${timezone}`);
lines.push(`previous_run: ${formatTimeContext(previousRunAt, now)}`);
lines.push(`trigger: ${triggerReason || 'heartbeat'}`);
lines.push(`mode: ${mode || 'unknown'}`);
lines.push(`home_assistant_connected: ${homeAssistantState?.connected ? 'yes' : 'no'}`);
@@ -88,7 +97,7 @@ function formatToolResultMessages(toolCalls) {
return [{ role: 'system', content: ['TOOL_RESULT', ...lines].join('\n') }];
}
function buildConversation({ recentMessages, name }) {
function buildConversation({ recentMessages, name, now = Date.now() }) {
const messages = [];
(recentMessages || []).forEach((entry) => {
const text = String(entry?.text || '').trim();
@@ -97,12 +106,13 @@ function buildConversation({ recentMessages, name }) {
const isAssistant = Boolean(entry?.bot);
const nickname = String(entry?.nickname || (isAssistant ? name || 'Overseer' : 'user')).trim();
const timing = `[${formatTimeContext(entry?.ts, now)}]`;
if (isAssistant) {
if (text) messages.push({ role: 'assistant', content: text });
messages.push(...toolResultMessages);
if (text) messages.push({ role: 'assistant', content: `${timing} ${text}` });
messages.push(...toolResultMessages.map((message) => ({ ...message, content: `${message.content}\nmessage_time: ${timing}` })));
return;
}
if (text) messages.push({ role: 'user', content: `${nickname}: ${text}` });
if (text) messages.push({ role: 'user', content: `${timing} ${nickname}: ${text}` });
});
return messages;
}
@@ -143,6 +153,7 @@ function buildModelMessages({
}
module.exports = {
formatTimeContext,
toStateUpdate,
buildToolState,
buildConversation,
@@ -27,7 +27,7 @@ const {
normalizeMs,
} = require('./constants');
const { isAdminRole, buildAdminState, buildFailureInfo } = require('./runtimeHelpers');
const { toStateUpdate, buildToolState, buildConversation, buildModelMessages } = require('./contextBuilder');
const { toStateUpdate, buildToolState, buildConversation, buildModelMessages, formatTimeContext } = require('./contextBuilder');
const { buildOllamaTools, executeToolAction } = require('./tools');
const { loadMemory, saveMemory, createDefaultMemory, summarizeMemory } = require('./memoryStore');
@@ -91,6 +91,7 @@ const runtime = {
runHistory: [],
liveToolCalls: [],
pendingDirectAddressRun: false,
lastDecisionAt: null,
memoryStore: loadMemory(),
contextResetAt: Date.now(),
};
@@ -365,7 +366,7 @@ function buildToolCallFeedEntries(requestedActions = [], actionResults = []) {
});
}
function buildRecentEventsSummary() {
function buildRecentEventsSummary(now) {
const events = (runtime.liveToolCalls || [])
.filter((entry) => entry && (entry.phase === 'ok' || entry.phase === 'error' || entry.phase === 'blocked'))
.slice(-3)
@@ -373,13 +374,17 @@ function buildRecentEventsSummary() {
const tool = String(entry.tool || 'unknown');
const phase = String(entry.phase || 'unknown');
const err = entry.error ? ` error=${String(entry.error).slice(0, 60)}` : '';
return `- tool=${tool} phase=${phase}${err}`;
return `- ${formatTimeContext(entry.at, now)} tool=${tool} phase=${phase}${err}`;
});
if (!events.length) return '- none';
return events.join('\n');
}
async function runDecision(triggerReason) {
// Use one reference time so all ages in this model request agree.
const now = Date.now();
const previousRunAt = runtime.lastDecisionAt;
runtime.lastDecisionAt = now;
const runId = runtime.tickCount;
updateStatus({ phase: 'context_build', currentRunId: runId, lastTriggerReason: triggerReason, lastError: null, lastErrorDetails: null });
@@ -389,24 +394,29 @@ async function runDecision(triggerReason) {
const liftState = getLiftState();
const roster = buildRosterSummary();
const stateUpdate = toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster, triggerReason });
const stateUpdate = toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster, triggerReason, timezone: loadConfig().timezone, now, previousRunAt });
const toolState = buildToolState({ mode, homeAssistantState, neatoState, liftState });
let chatCount = 0;
let botCount = 0;
const recentConversation = getRecentMessages(MAX_CHAT_CONTEXT + MAX_BOT_CONTEXT + 20, { includeSystem: true })
.filter((entry) => Number(entry?.ts || 0) >= runtime.contextResetAt)
.filter((entry) => {
if (!entry?.roverId) return true;
return isPublicChatTargetId(entry.roverId);
})
.slice(-(MAX_CHAT_CONTEXT + MAX_BOT_CONTEXT));
const conversationMessages = buildConversation({ recentMessages: recentConversation, name });
// Select newest entries within each budget, then restore chronological order.
.reverse()
.filter((entry) => entry?.bot ? ++botCount <= MAX_BOT_CONTEXT : ++chatCount <= MAX_CHAT_CONTEXT)
.reverse();
const conversationMessages = buildConversation({ recentMessages: recentConversation, name, now });
const systemPrompt = await readPrompt();
const modelMessages = buildModelMessages({
systemPrompt,
stateUpdate,
memorySummary: summarizeMemory(runtime.memoryStore),
recentEvents: buildRecentEventsSummary(),
recentEvents: buildRecentEventsSummary(now),
conversationMessages,
availableTools: toolState.available,
blockedTools: toolState.blocked,
@@ -680,6 +690,7 @@ function clearHistory(reason = 'admin requested clear history', options = {}) {
runtime.contextResetAt = Date.now();
runtime.runHistory = [];
runtime.liveToolCalls = [];
runtime.lastDecisionAt = null;
runtime.generationCount = 0;
runtime.generationTotalMs = 0;
if (resetPersistentMemory) {