Skip to content

Commit 0deabd5

Browse files
malhotra5openhands-agentenyst
authored
[Feat]: add context msg to new conversation endpoint (OpenHands#8586)
Co-authored-by: openhands <[email protected]> Co-authored-by: Engel Nyst <[email protected]>
1 parent 6f5bb43 commit 0deabd5

File tree

16 files changed

+202
-14
lines changed

16 files changed

+202
-14
lines changed

docs/static/openapi.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,11 @@
876876
"type": "string",
877877
"nullable": true
878878
},
879+
"conversation_instructions": {
880+
"type": "string",
881+
"nullable": true,
882+
"description": "Optional instructions the agent must follow throughout the conversation while addressing the user's initial task"
883+
},
879884
"image_urls": {
880885
"type": "array",
881886
"items": {

openhands/agenthub/codeact_agent/prompts/additional_info.j2

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,9 @@ You are have access to the following environment variables
3636
Today's date is {{ runtime_info.date }} (UTC).
3737
{% endif %}
3838
</RUNTIME_INFORMATION>
39+
{% if conversation_instructions and conversation_instructions.content -%}
40+
<CONVERSATION_INSTRUCTIONS>
41+
{{ conversation_instructions.content }}
42+
</CONVERSATION_INSTRUCTIONS>
43+
{% endif %}
3944
{% endif %}

openhands/cli/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ async def run_session(
105105
settings_store: FileSettingsStore,
106106
current_dir: str,
107107
task_content: str | None = None,
108+
conversation_instructions: str | None = None,
108109
session_name: str | None = None,
109110
) -> bool:
110111
reload_microagents = False
@@ -248,6 +249,7 @@ def on_event(event: Event) -> None:
248249
sid=sid,
249250
selected_repository=config.sandbox.selected_repo,
250251
repo_directory=repo_directory,
252+
conversation_instructions=conversation_instructions,
251253
)
252254

253255
# Add MCP tools to the agent

openhands/core/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ async def run_controller(
5555
fake_user_response_fn: FakeUserResponseFunc | None = None,
5656
headless_mode: bool = True,
5757
memory: Memory | None = None,
58+
conversation_instructions: str | None = None,
5859
) -> State | None:
5960
"""Main coroutine to run the agent controller with task input flexibility.
6061
@@ -126,6 +127,7 @@ async def run_controller(
126127
sid=sid,
127128
selected_repository=config.sandbox.selected_repo,
128129
repo_directory=repo_directory,
130+
conversation_instructions=conversation_instructions,
129131
)
130132

131133
# Add MCP tools to the agent

openhands/core/setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def create_memory(
135135
selected_repository: str | None = None,
136136
repo_directory: str | None = None,
137137
status_callback: Callable | None = None,
138+
conversation_instructions: str | None = None,
138139
) -> Memory:
139140
"""Create a memory for the agent to use.
140141
@@ -145,13 +146,16 @@ def create_memory(
145146
selected_repository: The repository to clone and start with, if any.
146147
repo_directory: The repository directory, if any.
147148
status_callback: Optional callback function to handle status updates.
149+
conversation_instructions: Optional instructions that are passed to the agent
148150
"""
149151
memory = Memory(
150152
event_stream=event_stream,
151153
sid=sid,
152154
status_callback=status_callback,
153155
)
154156

157+
memory.set_conversation_instructions(conversation_instructions)
158+
155159
if runtime:
156160
# sets available hosts
157161
memory.set_runtime_info(runtime, {})

openhands/events/observation/agent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class RecallObservation(Observation):
7575
additional_agent_instructions: str = ''
7676
date: str = ''
7777
custom_secrets_descriptions: dict[str, str] = field(default_factory=dict)
78+
conversation_instructions: str = ''
7879

7980
# knowledge
8081
microagent_knowledge: list[MicroagentKnowledge] = field(default_factory=list)
@@ -117,6 +118,7 @@ def __str__(self) -> str:
117118
f'additional_agent_instructions={self.additional_agent_instructions[:20]}...',
118119
f'date={self.date}'
119120
f'custom_secrets_descriptions={self.custom_secrets_descriptions}',
121+
f'conversation_instructions={self.conversation_instructions[0:20]}...'
120122
]
121123
)
122124
else:

openhands/memory/conversation_memory.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@
4141
from openhands.events.observation.mcp import MCPObservation
4242
from openhands.events.observation.observation import Observation
4343
from openhands.events.serialization.event import truncate_content
44-
from openhands.utils.prompt import PromptManager, RepositoryInfo, RuntimeInfo
44+
from openhands.utils.prompt import (
45+
ConversationInstructions,
46+
PromptManager,
47+
RepositoryInfo,
48+
RuntimeInfo,
49+
)
4550

4651

4752
class ConversationMemory:
@@ -467,6 +472,13 @@ def _process_observation(
467472
custom_secrets_descriptions=obs.custom_secrets_descriptions,
468473
)
469474

475+
conversation_instructions = None
476+
477+
if obs.conversation_instructions:
478+
conversation_instructions = ConversationInstructions(
479+
content=obs.conversation_instructions
480+
)
481+
470482
repo_instructions = (
471483
obs.repo_instructions if obs.repo_instructions else ''
472484
)
@@ -476,10 +488,10 @@ def _process_observation(
476488
repo_info.repo_name or repo_info.repo_directory
477489
)
478490
has_runtime_info = runtime_info is not None and (
479-
runtime_info.available_hosts
480-
or runtime_info.additional_agent_instructions
491+
runtime_info.date or runtime_info.custom_secrets_descriptions
481492
)
482493
has_repo_instructions = bool(repo_instructions.strip())
494+
has_conversation_instructions = conversation_instructions is not None
483495

484496
# Filter and process microagent knowledge
485497
filtered_agents = []
@@ -497,11 +509,17 @@ def _process_observation(
497509
message_content = []
498510

499511
# Build the workspace context information
500-
if has_repo_info or has_runtime_info or has_repo_instructions:
512+
if (
513+
has_repo_info
514+
or has_runtime_info
515+
or has_repo_instructions
516+
or has_conversation_instructions
517+
):
501518
formatted_workspace_text = (
502519
self.prompt_manager.build_workspace_context(
503520
repository_info=repo_info,
504521
runtime_info=runtime_info,
522+
conversation_instructions=conversation_instructions,
505523
repo_instructions=repo_instructions,
506524
)
507525
)

openhands/memory/memory.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
load_microagents_from_dir,
2323
)
2424
from openhands.runtime.base import Runtime
25-
from openhands.utils.prompt import RepositoryInfo, RuntimeInfo
25+
from openhands.utils.prompt import (
26+
ConversationInstructions,
27+
RepositoryInfo,
28+
RuntimeInfo,
29+
)
2630

2731
GLOBAL_MICROAGENTS_DIR = os.path.join(
2832
os.path.dirname(os.path.dirname(openhands.__file__)),
@@ -65,6 +69,7 @@ def __init__(
6569
# Store repository / runtime info to send them to the templating later
6670
self.repository_info: RepositoryInfo | None = None
6771
self.runtime_info: RuntimeInfo | None = None
72+
self.conversation_instructions: ConversationInstructions | None = None
6873

6974
# Load global microagents (Knowledge + Repo)
7075
# from typically OpenHands/microagents (i.e., the PUBLIC microagents)
@@ -156,6 +161,7 @@ def _on_workspace_context_recall(
156161
or self.runtime_info
157162
or repo_instructions
158163
or microagent_knowledge
164+
or self.conversation_instructions
159165
):
160166
obs = RecallObservation(
161167
recall_type=RecallType.WORKSPACE_CONTEXT,
@@ -180,6 +186,9 @@ def _on_workspace_context_recall(
180186
custom_secrets_descriptions=self.runtime_info.custom_secrets_descriptions
181187
if self.runtime_info is not None
182188
else {},
189+
conversation_instructions=self.conversation_instructions.content
190+
if self.conversation_instructions is not None
191+
else '',
183192
)
184193
return obs
185194
return None
@@ -290,7 +299,9 @@ def set_repository_info(self, repo_name: str, repo_directory: str) -> None:
290299
self.repository_info = None
291300

292301
def set_runtime_info(
293-
self, runtime: Runtime, custom_secrets_descriptions: dict[str, str]
302+
self,
303+
runtime: Runtime,
304+
custom_secrets_descriptions: dict[str, str],
294305
) -> None:
295306
"""Store runtime info (web hosts, ports, etc.)."""
296307
# e.g. { '127.0.0.1': 8080 }
@@ -306,9 +317,21 @@ def set_runtime_info(
306317
)
307318
else:
308319
self.runtime_info = RuntimeInfo(
309-
date=date, custom_secrets_descriptions=custom_secrets_descriptions
320+
date=date,
321+
custom_secrets_descriptions=custom_secrets_descriptions,
310322
)
311323

324+
def set_conversation_instructions(
325+
self, conversation_instructions: str | None
326+
) -> None:
327+
"""
328+
Set contextual information for conversation
329+
This is information the agent may require
330+
"""
331+
self.conversation_instructions = ConversationInstructions(
332+
content=conversation_instructions or ''
333+
)
334+
312335
def send_error_message(self, message_id: str, message: str):
313336
"""Sends an error message if the callback function was provided."""
314337
if self.status_callback:

openhands/server/routes/manage_conversations.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class InitSessionRequest(BaseModel):
6161
image_urls: list[str] | None = None
6262
replay_json: str | None = None
6363
suggested_task: SuggestedTask | None = None
64+
conversation_instructions: str | None = None
6465

6566
model_config = {'extra': 'forbid'}
6667

@@ -82,6 +83,7 @@ async def _create_new_conversation(
8283
initial_user_msg: str | None,
8384
image_urls: list[str] | None,
8485
replay_json: str | None,
86+
conversation_instructions: str | None = None,
8587
conversation_trigger: ConversationTrigger = ConversationTrigger.GUI,
8688
attach_convo_id: bool = False,
8789
) -> AgentLoopInfo:
@@ -120,6 +122,7 @@ async def _create_new_conversation(
120122
session_init_args['selected_repository'] = selected_repository
121123
session_init_args['custom_secrets'] = custom_secrets
122124
session_init_args['selected_branch'] = selected_branch
125+
session_init_args['conversation_instructions'] = conversation_instructions
123126
conversation_init_data = ConversationInitData(**session_init_args)
124127
logger.info('Loading conversation store')
125128
conversation_store = await ConversationStoreImpl.get_instance(config, user_id)
@@ -195,6 +198,7 @@ async def new_conversation(
195198
replay_json = data.replay_json
196199
suggested_task = data.suggested_task
197200
git_provider = data.git_provider
201+
conversation_instructions = data.conversation_instructions
198202

199203
conversation_trigger = ConversationTrigger.GUI
200204

@@ -222,6 +226,7 @@ async def new_conversation(
222226
image_urls=image_urls,
223227
replay_json=replay_json,
224228
conversation_trigger=conversation_trigger,
229+
conversation_instructions=conversation_instructions
225230
)
226231

227232
return InitSessionResponse(

openhands/server/session/agent_session.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ async def start(
9090
selected_repository: str | None = None,
9191
selected_branch: str | None = None,
9292
initial_message: MessageAction | None = None,
93+
conversation_instructions: str | None = None,
9394
replay_json: str | None = None,
9495
) -> None:
9596
"""Starts the Agent session
@@ -144,6 +145,7 @@ async def start(
144145
self.memory = await self._create_memory(
145146
selected_repository=selected_repository,
146147
repo_directory=repo_directory,
148+
conversation_instructions=conversation_instructions,
147149
custom_secrets_descriptions=custom_secrets_handler.get_custom_secrets_descriptions()
148150
)
149151

@@ -415,7 +417,11 @@ def _create_controller(
415417
return controller
416418

417419
async def _create_memory(
418-
self, selected_repository: str | None, repo_directory: str | None, custom_secrets_descriptions: dict[str, str]
420+
self,
421+
selected_repository: str | None,
422+
repo_directory: str | None,
423+
conversation_instructions: str | None,
424+
custom_secrets_descriptions: dict[str, str]
419425
) -> Memory:
420426
memory = Memory(
421427
event_stream=self.event_stream,
@@ -426,6 +432,7 @@ async def _create_memory(
426432
if self.runtime:
427433
# sets available hosts and other runtime info
428434
memory.set_runtime_info(self.runtime, custom_secrets_descriptions)
435+
memory.set_conversation_instructions(conversation_instructions)
429436

430437
# loads microagents from repo/.openhands/microagents
431438
microagents: list[BaseMicroagent] = await call_sync_from_async(
@@ -435,7 +442,10 @@ async def _create_memory(
435442
memory.load_user_workspace_microagents(microagents)
436443

437444
if selected_repository and repo_directory:
438-
memory.set_repository_info(selected_repository, repo_directory)
445+
memory.set_repository_info(
446+
selected_repository,
447+
repo_directory
448+
)
439449
return memory
440450

441451
def _maybe_restore_state(self) -> State | None:

0 commit comments

Comments
 (0)