From b23f4776b1b1f1a968b39297978b064a8a260cea Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 20 Feb 2025 17:17:21 -0600 Subject: [PATCH] add state to self prompter for pausing --- example_tasks.json | 1 + src/agent/action_manager.js | 2 +- src/agent/agent.js | 12 ++++---- src/agent/commands/actions.js | 6 ++-- src/agent/conversation.js | 29 ++++++------------- src/agent/history.js | 3 +- src/agent/modes.js | 8 +++--- src/agent/self_prompter.js | 52 +++++++++++++++++++++++++++-------- src/agent/tasks.js | 2 +- src/models/prompter.js | 3 +- 10 files changed, 68 insertions(+), 50 deletions(-) diff --git a/example_tasks.json b/example_tasks.json index 69ab550..f828d9a 100644 --- a/example_tasks.json +++ b/example_tasks.json @@ -44,6 +44,7 @@ }, "multiagent_techtree_1_stone_pickaxe": { "conversation": "Let's collaborate to build a stone pickaxe", + "goal": "Build a stone pickaxe", "agent_count": 2, "initial_inventory": { "0": { diff --git a/src/agent/action_manager.js b/src/agent/action_manager.js index ad08827..f5c6cae 100644 --- a/src/agent/action_manager.js +++ b/src/agent/action_manager.js @@ -46,7 +46,7 @@ export class ActionManager { assert(actionLabel != null, 'actionLabel is required for new resume'); this.resume_name = actionLabel; } - if (this.resume_func != null && (this.agent.isIdle() || new_resume) && (!this.agent.self_prompter.on || new_resume)) { + if (this.resume_func != null && (this.agent.isIdle() || new_resume) && (!this.agent.self_prompter.isActive() || new_resume)) { this.currentActionLabel = this.resume_name; let res = await this._executeAction(this.resume_name, this.resume_func, timeout); this.currentActionLabel = ''; diff --git a/src/agent/agent.js b/src/agent/agent.js index f2eed4d..1726ad2 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -156,10 +156,10 @@ export class Agent { }; if (save_data?.self_prompt) { - let prompt = save_data.self_prompt; - // add initial message to history - this.history.add('system', prompt); - await this.self_prompter.start(prompt); + if (init_message) { + this.history.add('system', init_message); + } + await this.self_prompter.handleLoad(save_data.self_prompt, save_data.self_prompting_state); } if (save_data?.last_sender) { this.last_sender = save_data.last_sender; @@ -193,7 +193,7 @@ export class Agent { shutUp() { this.shut_up = true; - if (this.self_prompter.on) { + if (this.self_prompter.isActive()) { this.self_prompter.stop(false); } convoManager.endAllConversations(); @@ -259,7 +259,7 @@ export class Agent { await this.history.add(source, message); this.history.save(); - if (!self_prompt && this.self_prompter.on) // message is from user during self-prompting + if (!self_prompt && this.self_prompter.isActive()) // message is from user during self-prompting max_responses = 1; // force only respond to this message, then let self-prompting take over for (let i=0; i JSON.parse(readFileSync(p, 'utf8')).name); let agents_in_game = []; -let self_prompter_paused = false; - class Conversation { constructor(name) { this.name = name; @@ -97,7 +95,7 @@ class ConversationManager { this._clearMonitorTimeouts(); return; } - if (!self_prompter_paused) { + if (!agent.self_prompter.isPaused()) { this.endConversation(convo_partner); agent.handleMessage('system', `${convo_partner} disconnected, conversation has ended.`); } @@ -125,9 +123,8 @@ class ConversationManager { const convo = this._getConvo(send_to); convo.reset(); - if (agent.self_prompter.on) { - await agent.self_prompter.stop(); - self_prompter_paused = true; + if (agent.self_prompter.isActive()) { + await agent.self_prompter.pause(); } if (convo.active) return; @@ -191,9 +188,8 @@ class ConversationManager { convo.queue(received); // responding to conversation takes priority over self prompting - if (agent.self_prompter.on){ - await agent.self_prompter.stopLoop(); - self_prompter_paused = true; + if (agent.self_prompter.isActive()){ + await agent.self_prompter.pause(); } _scheduleProcessInMessage(sender, received, convo); @@ -235,7 +231,7 @@ class ConversationManager { if (this.activeConversation.name === sender) { this._stopMonitor(); this.activeConversation = null; - if (self_prompter_paused && !this.inConversation()) { + if (agent.self_prompter.isPaused() && !this.inConversation()) { _resumeSelfPrompter(); } } @@ -246,7 +242,7 @@ class ConversationManager { for (const sender in this.convos) { this.endConversation(sender); } - if (self_prompter_paused) { + if (agent.self_prompter.isPaused()) { _resumeSelfPrompter(); } } @@ -258,14 +254,6 @@ class ConversationManager { this.endConversation(sender); } } - - scheduleSelfPrompter() { - self_prompter_paused = true; - } - - cancelSelfPrompter() { - self_prompter_paused = false; - } } const convoManager = new ConversationManager(); @@ -360,8 +348,7 @@ function _tagMessage(message) { async function _resumeSelfPrompter() { await new Promise(resolve => setTimeout(resolve, 5000)); - if (self_prompter_paused && !convoManager.inConversation()) { - self_prompter_paused = false; + if (agent.self_prompter.isPaused() && !convoManager.inConversation()) { agent.self_prompter.start(); } } diff --git a/src/agent/history.js b/src/agent/history.js index a578377..4ef0c37 100644 --- a/src/agent/history.js +++ b/src/agent/history.js @@ -84,7 +84,8 @@ export class History { const data = { memory: this.memory, turns: this.turns, - self_prompt: this.agent.self_prompter.on ? this.agent.self_prompter.prompt : null, + self_prompting_state: this.agent.self_prompter.state, + self_prompt: this.agent.self_prompter.isStopped() ? null : this.agent.self_prompter.prompt, last_sender: this.agent.last_sender }; writeFileSync(this.memory_fp, JSON.stringify(data, null, 2)); diff --git a/src/agent/modes.js b/src/agent/modes.js index 8bf1594..8747cf3 100644 --- a/src/agent/modes.js +++ b/src/agent/modes.js @@ -277,7 +277,7 @@ const modes_list = [ ]; async function execute(mode, agent, func, timeout=-1) { - if (agent.self_prompter.on) + if (agent.self_prompter.isActive()) agent.self_prompter.stopLoop(); let interrupted_action = agent.actions.currentActionLabel; mode.active = true; @@ -290,7 +290,7 @@ async function execute(mode, agent, func, timeout=-1) { let should_reprompt = interrupted_action && // it interrupted a previous action !agent.actions.resume_func && // there is no resume function - !agent.self_prompter.on && // self prompting is not on + !agent.self_prompter.isActive() && // self prompting is not on !code_return.interrupted; // this mode action was not interrupted by something else if (should_reprompt) { @@ -311,9 +311,9 @@ for (let mode of modes_list) { class ModeController { /* SECURITY WARNING: - ModesController must be isolated. Do not store references to external objects like `agent`. + ModesController must be reference isolated. Do not store references to external objects like `agent`. This object is accessible by LLM generated code, so any stored references are also accessible. - This can be used to expose sensitive information by malicious human prompters. + This can be used to expose sensitive information by malicious prompters. */ constructor() { this.behavior_log = ''; diff --git a/src/agent/self_prompter.js b/src/agent/self_prompter.js index 439b6c6..aae8371 100644 --- a/src/agent/self_prompter.js +++ b/src/agent/self_prompter.js @@ -1,7 +1,10 @@ +const STOPPED = 0 +const ACTIVE = 1 +const PAUSED = 2 export class SelfPrompter { constructor(agent) { this.agent = agent; - this.on = false; + this.state = STOPPED; this.loop_active = false; this.interrupt = false; this.prompt = ''; @@ -16,16 +19,38 @@ export class SelfPrompter { return 'No prompt specified. Ignoring request.'; prompt = this.prompt; } - if (this.on) { - this.prompt = prompt; - } - this.on = true; + this.state = ACTIVE; this.prompt = prompt; this.startLoop(); } - setPrompt(prompt) { + isActive() { + return this.state === ACTIVE; + } + + isStopped() { + return this.state === STOPPED; + } + + isPaused() { + return this.state === PAUSED; + } + + async handleLoad(prompt, state) { + if (state == undefined) + state = STOPPED; + this.state = state; this.prompt = prompt; + if (state !== STOPPED && !prompt) + throw new Error('No prompt loaded when self-prompting is active'); + if (state === ACTIVE) { + await this.start(prompt); + } + } + + setPromptPaused(prompt) { + this.prompt = prompt; + this.state = PAUSED; } async startLoop() { @@ -47,7 +72,7 @@ export class SelfPrompter { let out = `Agent did not use command in the last ${MAX_NO_COMMAND} auto-prompts. Stopping auto-prompting.`; this.agent.openChat(out); console.warn(out); - this.on = false; + this.state = STOPPED; break; } } @@ -63,7 +88,7 @@ export class SelfPrompter { update(delta) { // automatically restarts loop - if (this.on && !this.loop_active && !this.interrupt) { + if (this.state === ACTIVE && !this.loop_active && !this.interrupt) { if (this.agent.isIdle()) this.idle_time += delta; else @@ -96,12 +121,17 @@ export class SelfPrompter { this.interrupt = true; if (stop_action) await this.agent.actions.stop(); - await this.stopLoop(); - this.on = false; + this.stopLoop(); + this.state = STOPPED; + } + + async pause() { + this.interrupt = true; + this.state = PAUSED; } shouldInterrupt(is_self_prompt) { // to be called from handleMessage - return is_self_prompt && this.on && this.interrupt; + return is_self_prompt && this.state === ACTIVE && this.interrupt; } handleUserPromptedCmd(is_self_prompt, is_action) { diff --git a/src/agent/tasks.js b/src/agent/tasks.js index a51bb0c..a7f140c 100644 --- a/src/agent/tasks.js +++ b/src/agent/tasks.js @@ -82,7 +82,7 @@ export class Task { if (this.validator && this.validator.validate()) return {"message": 'Task successful', "code": 2}; // TODO check for other terminal conditions - // if (this.task.goal && !this.self_prompter.on) + // if (this.task.goal && !this.self_prompter.isActive()) // return {"message": 'Agent ended goal', "code": 3}; // if (this.task.conversation && !inConversation()) // return {"message": 'Agent ended conversation', "code": 3}; diff --git a/src/models/prompter.js b/src/models/prompter.js index 29e7cc6..6cc54e2 100644 --- a/src/models/prompter.js +++ b/src/models/prompter.js @@ -257,7 +257,8 @@ export class Prompter { if (prompt.includes('$CONVO')) prompt = prompt.replaceAll('$CONVO', 'Recent conversation:\n' + stringifyTurns(messages)); if (prompt.includes('$SELF_PROMPT')) { - let self_prompt = this.agent.self_prompter.on ? `YOUR CURRENT ASSIGNED GOAL: "${this.agent.self_prompter.prompt}"\n` : ''; + // if active or paused, show the current goal + let self_prompt = !this.agent.self_prompter.isStopped() ? `YOUR CURRENT ASSIGNED GOAL: "${this.agent.self_prompter.prompt}"\n` : ''; prompt = prompt.replaceAll('$SELF_PROMPT', self_prompt); } if (prompt.includes('$LAST_GOALS')) {