Compare commits

...

5 Commits

Author SHA1 Message Date
Xingyao Wang
3e5a709b52 tweak implementation of /plan and /execute command 2025-08-17 13:14:41 -04:00
Xingyao Wang
d1ba74f639 revert agent controller 2025-08-17 13:08:40 -04:00
openhands
135c127f50 feat(prompt+ux):
- Strengthen ReadOnlyPlanningAgent system prompt (SOLE GOAL: produce PLAN.md)
- Improve delegate completion message to summarize planning handoff to user
- Add /plan and /execute to TUI COMMANDS for help/autocomplete

Co-authored-by: openhands <openhands@all-hands.dev>
2025-08-17 03:39:18 +00:00
openhands
affba28554 chore(cli): add /plan and /execute to TUI COMMANDS (help/autocomplete menu)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-08-17 03:20:12 +00:00
openhands
7d1829451b feat(planning): add planning delegate agent, plan_md tool, CLI /plan and /execute; prompt update; alias ReadOnlyAgent
Co-authored-by: openhands <openhands@all-hands.dev>
2025-08-16 23:47:18 +00:00
8 changed files with 175 additions and 49 deletions

View File

@@ -1,4 +1,6 @@
from openhands.agenthub.readonly_agent.readonly_agent import ReadOnlyAgent
from openhands.agenthub.readonly_agent.readonly_agent import ReadOnlyPlanningAgent
from openhands.controller.agent import Agent
Agent.register('ReadOnlyAgent', ReadOnlyAgent)
Agent.register('ReadOnlyPlanningAgent', ReadOnlyPlanningAgent)
# Backward-compat alias for tests and existing references
Agent.register('ReadOnlyAgent', ReadOnlyPlanningAgent)

View File

@@ -22,6 +22,7 @@ from openhands.agenthub.readonly_agent.tools import (
GlobTool,
GrepTool,
ViewTool,
create_plan_md_tool,
)
from openhands.core.exceptions import (
FunctionCallNotExistsError,
@@ -33,11 +34,12 @@ from openhands.events.action import (
AgentFinishAction,
AgentThinkAction,
CmdRunAction,
FileEditAction,
FileReadAction,
MCPAction,
MessageAction,
)
from openhands.events.event import FileReadSource
from openhands.events.event import FileEditSource, FileReadSource
from openhands.events.tool import ToolCallMetadata
@@ -193,6 +195,42 @@ def response_to_actions(
glob_cmd = glob_to_cmdrun(pattern, path)
action = CmdRunAction(command=glob_cmd, is_input=False)
# ================================================
# Plan MD Tool (ACI: FileReadAction/FileEditAction)
# ================================================
elif tool_call.function.name == 'plan_md':
cmd = arguments.get('command')
if cmd not in ['view', 'create', 'edit']:
raise FunctionCallValidationError(
f'Invalid command for plan_md: {cmd}'
)
plan_path = '/workspace/PLAN.md'
if cmd == 'view':
action = FileReadAction(
path=plan_path, impl_source=FileReadSource.OH_ACI
)
elif cmd == 'create':
file_text = arguments.get('file_text', '')
action = FileEditAction(
path=plan_path,
command='create',
file_text=file_text,
impl_source=FileEditSource.OH_ACI,
)
else: # edit
old_str = arguments.get('old_str')
new_str = arguments.get('new_str')
if old_str is None or new_str is None:
raise FunctionCallValidationError(
'plan_md.edit requires both old_str and new_str (replace full content). Use plan_md.view first to get exact old_str.'
)
action = FileEditAction(
path=plan_path,
command='str_replace',
old_str=old_str,
new_str=new_str,
impl_source=FileEditSource.OH_ACI,
)
# ================================================
# MCPAction (MCP)
@@ -245,4 +283,5 @@ def get_tools() -> list[ChatCompletionToolParam]:
GrepTool,
GlobTool,
ViewTool,
create_plan_md_tool(),
]

View File

@@ -1,34 +1,29 @@
You are OpenHands ReadOnlyAgent, a helpful AI assistant focused on code analysis and exploration. You can:
- Explore and analyze codebases
- Browse the web for relevant information
- Plan potential changes
- Answer questions about code
You are the ReadOnlyPlanningAgent, a planning-focused assistant.
Your SOLE GOAL is to produce and iterate on a single planning document for the current task: /workspace/PLAN.md.
You must not perform implementation work; when the plan is ready, finish the session.
<CAPABILITIES>
✓ READ-ONLY TOOLS:
- view: Read file contents
- grep: Search for patterns
- glob: List matching files
- think: Analyze information
- web_read: Access web resources
- finish: Complete current task
CAPABILITIES
- grep: search patterns in files to understand the codebase
- glob: list matching files
- view: read files to gather context
- plan_md: manage the planning doc PLAN.md (only commands: view, create, edit)
- think: reason about findings
- finish: end the planning session when ready to execute
RESTRICTIONS:
- Cannot modify any files
- Cannot execute state-changing commands
</CAPABILITIES>
STRICT RESTRICTIONS
- Do NOT run shell commands or execute code
- Do NOT modify any files except /workspace/PLAN.md via plan_md
- Do NOT create or edit any other files
<GUIDELINES>
1. When analyzing code or answering questions:
- Be thorough and methodical
- Prioritize accuracy over speed
- Provide detailed explanations
PLANNING GUIDELINES
- Produce a concise, numbered plan with rationale and assumptions
- Capture open questions and risks
- Use plan_md.view to inspect current content, plan_md.create to create the file, and plan_md.edit to replace content
- For plan_md.edit, first view the file to obtain its current exact contents; then replace the entire file by setting old_str to the exact current contents and new_str to the updated full plan
- Iterate as needed until the plan is solid
- When done, use finish to end the planning session with a short summary of the plan
2. For file operations:
- Always verify file locations before accessing
- Don't assume paths are relative to current directory
3. If asked to make changes:
- Explain you are read-only
- Recommend using CodeActAgent instead
</GUIDELINES>
OUTPUT STYLE
- Keep the plan readable and structured
- Prefer bullets and numbered lists
- Defer implementation details and code changes to the execution phase

View File

@@ -1,4 +1,10 @@
"""ReadOnlyAgent - A specialized version of CodeActAgent that only uses read-only tools."""
"""ReadOnlyPlanningAgent - A specialized planning agent for read-only research plus maintaining PLAN.md.
This agent is derived from CodeActAgent and constrained to:
- Use read-only research tools (grep, glob, view) to explore the repository
- Maintain a single planning document at /workspace/PLAN.md via a dedicated plan editor tool
- Avoid any other code execution or edits outside PLAN.md
"""
import os
from typing import TYPE_CHECKING
@@ -19,20 +25,14 @@ from openhands.llm.llm import LLM
from openhands.utils.prompt import PromptManager
class ReadOnlyAgent(CodeActAgent):
class ReadOnlyPlanningAgent(CodeActAgent):
VERSION = '1.0'
"""
The ReadOnlyAgent is a specialized version of CodeActAgent that only uses read-only tools.
This agent is designed for safely exploring codebases without making any changes.
It only has access to tools that don't modify the system: grep, glob, view, think, finish, web_read.
Use this agent when you want to:
1. Explore a codebase to understand its structure
2. Search for specific patterns or code
3. Research without making any changes
When you're ready to make changes, switch to the regular CodeActAgent.
The ReadOnlyPlanningAgent is designed for planning large features safely.
It provides:
- Read-only repo exploration: grep, glob, view
- PLAN.md maintenance via a specialized plan editor tool (create/view/edit only)
- No other file modifications or command execution
"""
def __init__(
@@ -50,7 +50,7 @@ class ReadOnlyAgent(CodeActAgent):
super().__init__(llm, config)
logger.debug(
f'TOOLS loaded for ReadOnlyAgent: {", ".join([tool.get("function").get("name") for tool in self.tools])}'
f'TOOLS loaded for ReadOnlyPlanningAgent: {", ".join([tool.get("function").get("name") for tool in self.tools])}'
)
@property
@@ -74,7 +74,7 @@ class ReadOnlyAgent(CodeActAgent):
- mcp_tools (list[dict]): The list of MCP tools.
"""
logger.warning(
'ReadOnlyAgent does not support MCP tools. MCP tools will be ignored by the agent.'
'ReadOnlyPlanningAgent does not support MCP tools. MCP tools will be ignored by the agent.'
)
def response_to_actions(self, response: 'ModelResponse') -> list['Action']:

View File

@@ -5,17 +5,20 @@ This module defines the read-only tools for the ReadOnlyAgent.
from .glob import GlobTool
from .grep import GrepTool
from .plan_md import create_plan_md_tool
from .view import ViewTool
__all__ = [
'ViewTool',
'GrepTool',
'GlobTool',
'create_plan_md_tool',
]
# Define the list of read-only tools
# Define the list of read-only tools for exploration and planning
READ_ONLY_TOOLS = [
ViewTool,
GrepTool,
GlobTool,
# plan_md tool is exported as a factory; included via function_calling.get_tools
]

View File

@@ -0,0 +1,47 @@
from litellm import ChatCompletionToolParam, ChatCompletionToolParamFunctionChunk
PLAN_MD_TOOL_NAME = 'plan_md'
_PLAN_MD_DESCRIPTION = """Maintain the planning document PLAN.md in the repository root.
This tool is ONLY for planning. It can:
- view: Show the current contents of PLAN.md
- create: Create PLAN.md with provided text (fails if file already exists)
- edit: Replace PLAN.md content with provided text
It MUST NOT be used to modify any other file.
"""
def create_plan_md_tool() -> ChatCompletionToolParam:
return ChatCompletionToolParam(
type='function',
function=ChatCompletionToolParamFunctionChunk(
name=PLAN_MD_TOOL_NAME,
description=_PLAN_MD_DESCRIPTION,
parameters={
'type': 'object',
'properties': {
'command': {
'type': 'string',
'enum': ['view', 'create', 'edit'],
'description': 'Operation to perform on PLAN.md',
},
'file_text': {
'type': 'string',
'description': 'Required for create: Initial content of PLAN.md',
},
'old_str': {
'type': 'string',
'description': 'Required for edit: EXACT current content of PLAN.md (unique match). Use plan_md.view first.',
},
'new_str': {
'type': 'string',
'description': 'Required for edit: New full content of PLAN.md (replaces file)',
},
},
'required': ['command'],
'additionalProperties': False,
},
),
)

View File

@@ -46,6 +46,8 @@ from openhands.core.schema import AgentState
from openhands.core.schema.exit_reason import ExitReason
from openhands.events import EventSource
from openhands.events.action import (
AgentDelegateAction,
AgentFinishAction,
ChangeAgentStateAction,
MessageAction,
)
@@ -165,6 +167,10 @@ async def handle_commands(
)
elif command == '/mcp':
await handle_mcp_command(config)
elif command == '/plan':
await handle_plan_command(config, event_stream, agent_state)
elif command == '/execute':
await handle_execute_command(event_stream)
else:
close_repl = True
action = MessageAction(content=command)
@@ -173,6 +179,38 @@ async def handle_commands(
return close_repl, reload_microagents, new_session_requested, exit_reason
async def handle_plan_command(
config: OpenHandsConfig,
event_stream: EventStream,
agent_state: str,
) -> None:
# Prompt for planning objective and delegate to ReadOnlyPlanningAgent
print_formatted_text(
HTML(
'<ansigreen>Planning mode activated. Please provide your planning objective:</ansigreen>'
)
)
objective = await read_prompt_input(config, agent_state)
if objective == '/exit':
return
event_stream.add_event(
AgentDelegateAction(agent='ReadOnlyPlanningAgent', inputs={'task': objective}),
EventSource.USER,
)
async def handle_execute_command(event_stream):
event_stream.add_event(
AgentFinishAction(
final_thought='Switching to execution mode',
outputs={
'content': 'Read-Only planning agent finished its discussion with the user. You may find the discussed plan at PLAN.md.'
},
),
EventSource.USER,
)
def handle_exit_command(
config: OpenHandsConfig,
event_stream: EventStream,

View File

@@ -86,6 +86,8 @@ COMMANDS = {
'/settings': 'Display and modify current settings',
'/resume': 'Resume the agent when paused',
'/mcp': 'Manage MCP server configuration and view errors',
'/plan': 'Delegate to planning agent (ReadOnlyPlanningAgent) to draft/iterate PLAN.md',
'/execute': 'End planning delegate and resume execution by parent agent',
}
print_lock = threading.Lock()