A Java agent framework with Plan-and-Execute (Plan → Execute → Replan) capabilities and configurable LLMs, inspired by popular agents such as OpenManus / Hermes / LangChain Plan-and-Execute.
- Plan-and-Execute Loop: Breaks down tasks into steps, executes them one by one, and dynamically replans after each step, allowing the agent to adapt based on intermediate results.
- Configurable LLMs: Built on the OpenAI-compatible protocol, one codebase works with DeepSeek / Qwen / Kimi / Zhipu / OpenAI — just change the config.
- Role-level Model Config: Planner / Executor / Replanner can each use different models and temperatures.
- Tool Calling (Function Calling): Built-in calculator, date/time, text analysis, web search, and more, with multi-round tool-calling loops.
- MCP Protocol Support: Connects to external MCP servers via the standard Model Context Protocol, supporting both stdio and HTTP/SSE transports, with automatic tool discovery and registration.
- Skills System: Follows the Agent Skills open standard. Drop a folder containing
SKILL.mdintoskills/and it's auto-loaded, with support for scripts and reference docs. - Extensible: Implement the
Toolinterface to add new tools; implement theLLMClientinterface to integrate new model providers.
┌──────────────────────────────────────────┐
│ PlanAndExecuteAgent │
│ (main orchestrator, loop control) │
└──────────────────────────────────────────┘
│ 1. Plan │ 2. Execute │ 3. Replan
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌───────────┐
│ Planner │───▶│ Executor │───▶│ Replanner │
└─────────┘ └─────┬─────┘ └─────┬─────┘
│ │
┌─────▼─────┐ continue/replan/finish
│ Tools │
│ Registry │
└───────────┘
- Planner: Breaks the user's goal into 2–6 executable steps (JSON).
- Executor: Takes the next pending step and completes it via an LLM + tool-calling loop, producing the step's result.
- Replanner: Evaluates "goal + completed steps/results + remaining steps" to decide the next action:
continue: The plan is still valid; proceed to the next step.replan: Adjust the remaining plan based on new findings.finish: The goal is achieved; produce the final answer.
- Loop steps 2–3 until
finishor the maximum replan count is reached, then synthesize the final answer.
src/main/java/com/agent/
├── Main.java # Entry point (interactive / single-task mode)
├── config/ # Configuration loading
│ ├── AgentConfig.java
│ └── ConfigLoader.java
├── llm/ # LLM abstraction layer (configurable models)
│ ├── LLMClient.java # Interface
│ ├── LLMConfig.java
│ ├── LLMRequest/Response.java
│ ├── OpenAICompatibleClient.java # OpenAI/DeepSeek/Qwen compatible impl
│ └── LLMFactory.java
├── model/ # Data models
│ ├── Message / Plan / Step / StepResult / StepStatus
│ ├── AgentState.java # Global state
│ └── ReplanDecision.java # Replan decision
├── tools/ # Tool framework
│ ├── Tool / ToolSpec / ToolResult / ToolRegistry
│ ├── SecurityPolicy.java # Shared security policy (hazard detection + lock)
│ ├── AbstractFileTool.java # File tool base (path validation / sandboxing)
│ ├── ReadFileTool.java # Built-in: read file
│ ├── WriteFileTool.java # Built-in: write file
│ ├── ListDirectoryTool.java # Built-in: list directory
│ ├── EditFileTool.java # Built-in: edit file
│ ├── FetchUrlTool.java # Built-in: fetch URL
│ ├── PythonInterpreterTool.java # Built-in: Python code execution
│ ├── ExecuteShellTool.java # Built-in: shell execution (hazard detection)
│ ├── CalculatorTool.java # Built-in: calculator
│ ├── DateTimeTool.java # Built-in: date/time
│ ├── TextAnalyzerTool.java # Built-in: text analysis
│ └── WebSearchTool.java # Built-in: Tavily search
├── mcp/ # MCP protocol client
│ ├── Transport.java # Transport layer abstraction
│ ├── StdioTransport.java # stdio transport (local subprocess)
│ ├── HttpSseTransport.java # HTTP/SSE transport (remote server)
│ ├── MCPClient.java # JSON-RPC client (based on Transport)
│ └── MCPToolWrapper.java # MCP tool adapter
├── skill/ # Skills system
│ ├── Skill.java # Skill definition
│ ├── SkillLoader.java # Directory scan + SKILL.md parser
│ ├── SkillTool.java # Skill tool wrapper
│ └── SkillRegistry.java # Skill registry
├── core/ # Plan-and-Execute core
│ ├── Planner.java # Planner
│ ├── Executor.java # Executor (with tool-calling loop)
│ ├── Replanner.java # Replanner
│ └── PlanAndExecuteAgent.java
├── prompt/ # Prompt templates
│ └── PromptTemplates.java
└── util/ # Utilities
└── JsonUtil.java
skills/ # Skills directory (each subfolder is a skill)
├── translation/
│ └── SKILL.md
└── code-stats/
├── SKILL.md
└── scripts/
└── count_stats.py
Edit src/main/resources/application.yml and fill in your API key, base URL, and model name:
llm:
provider: "openai-compatible"
apiKey: "sk-xxxxxxxx"
baseUrl: "https://api.deepseek.com/v1" # DeepSeek
# baseUrl: "https://api.openai.com/v1" # OpenAI
# baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1" # Qwen
model: "deepseek-chat"
temperature: 0.2
maxTokens: 2048mvn clean package# Interactive mode
java -jar target/plan-and-execute-agent-1.0.0.jar
# Single-task mode
java -jar target/plan-and-execute-agent-1.0.0.jar "Calculate (123 + 456) * 2 and tell me what day of the week it is"
# Specify an external config file
java -Dconfig.file=/path/to/my.yml -jar target/plan-and-execute-agent-1.0.0.jarNote: The chosen model must support Function Calling (tool calling), otherwise the Executor cannot use tools and will fall back to plain text reasoning. Models like DeepSeek-Chat, GPT-4o, and Qwen-Max are recommended.
agent:
maxIterations: 10 # Max rounds of LLM-tool calling loop per step
maxReplans: 8 # Max replan attempts (prevents infinite loops)
verbose: true
workspace: "./workspace" # Working directory for file tools (sandbox root)
tools:
pythonCmd: "python" # Python executable (e.g. python / python3)
llm: # Global model config
provider: "openai-compatible"
apiKey: "..."
baseUrl: "..."
model: "..."
# Optional: specify different models/temperatures per role (blank = use global llm)
planner: { model: "", temperature: 0.1 }
executor: { model: "", temperature: 0.2 }
replanner: { model: "", temperature: 0.2 }| Tool | Name | Description |
|---|---|---|
| Read File | read_file |
Read a text file, supports line-range reading |
| Write File | write_file |
Create/overwrite a file, auto-creates parent dirs |
| List Directory | list_directory |
List directory contents, supports recursion |
| Edit File | edit_file |
Find and replace file fragments, precise editing |
| Fetch URL | fetch_url |
Fetch a URL's body and convert to plain text |
| Python Interpreter | python_interpreter |
Execute Python code in a subprocess (30s timeout) |
| Execute Shell | execute_shell |
Run shell commands in the workspace; hazardous commands require confirmation |
| Calculator | calculator |
Precise arithmetic evaluation (parentheses, exponents) |
| Current Time | get_current_time |
Get the current time in a specified timezone |
| Text Analyzer | text_analyzer |
Count characters/words/lines |
| Web Search | web_search |
Web search via Tavily API, returns summaries and results |
For example, use a cheaper model for planning and a stronger model for execution:
llm:
model: "deepseek-chat"
planner:
model: "deepseek-chat"
temperature: 0.1
executor:
model: "deepseek-reasoner"
temperature: 0.2The agent supports the Model Context Protocol (MCP), allowing it to connect to any external MCP server and automatically discover its tools. Once connected at startup, MCP server tools are registered alongside built-in tools in the ToolRegistry — the agent can call them just like built-in tools.
The agent supports two MCP transports, auto-selected by config or explicitly set via the transport field:
| Transport | Use Case | Config | Description |
|---|---|---|---|
| stdio | Local MCP servers | command + args |
Launches a subprocess via ProcessBuilder, communicates over stdin/stdout |
| HTTP/SSE | Remote MCP servers | url |
Supports both Streamable HTTP and legacy SSE modes, auto-detected |
Auto-inference rules:
urlconfigured and nocommand→ HTTP/SSE modecommandconfigured → stdio mode- Can also be explicitly set via
transport(stdio/http/sse)
Add an mcpServers section to application.yml:
For local MCP servers launched via npx/node/python:
mcpServers:
- name: "filesystem"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "./workspace"]
- name: "github"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "ghp_xxxxxxxxxxxx"
- name: "sqlite"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-sqlite", "--db-path", "./data.db"]For MCP servers deployed remotely, communicating over HTTP:
mcpServers:
- name: "remote-mcp"
url: "http://some ip address:8000/mcp"
transport: "http" # Optional: http / sse, auto-inferred if omitted
headers: # Optional: custom request headers
Authorization: "Bearer xxx"
# Legacy SSE mode (URL ends with /sse)
- name: "sse-mcp"
url: "http://some ip address:3000/sse"
transport: "sse"HTTP/SSE auto-detection:
- URL ends with
/sse→ Legacy SSE mode (GET to establish a long-lived SSE connection, then POST requests) - Other URLs → Streamable HTTP mode (direct POST, response may be JSON or SSE stream)
Streamable HTTP mode automatically handles the mcp-session-id returned by the server and sends it back in subsequent requests to maintain the session.
| Field | Mode | Required | Description |
|---|---|---|---|
name |
All | Yes | Server name (for log identification) |
command |
stdio | Yes | Launch command (e.g. npx, node, python) |
args |
stdio | No | Command arguments |
env |
stdio | No | Environment variables |
url |
HTTP/SSE | Yes | MCP server URL (e.g. http://host:port/mcp) |
transport |
All | No | Transport type: stdio / http / sse, auto-inferred if omitted |
headers |
HTTP/SSE | No | Custom request headers (e.g. Authorization: Bearer xxx) |
- At startup, the agent selects the transport based on config (stdio subprocess / HTTP connection)
- Completes the
initializehandshake via JSON-RPC 2.0 - Calls
tools/listto discover all tools provided by the server - Wraps each tool as an
MCPToolWrapperand registers it in the ToolRegistry - When the agent calls a tool, it sends a
tools/callrequest to the MCP server - All MCP connections are automatically closed on program exit
| Server | Type | Install/Connect | Description |
|---|---|---|---|
| filesystem | stdio | npx -y @modelcontextprotocol/server-filesystem <dir> |
File system read/write |
| github | stdio | npx -y @modelcontextprotocol/server-github |
GitHub API (requires GITHUB_TOKEN) |
| sqlite | stdio | npx -y @modelcontextprotocol/server-sqlite --db-path <path> |
SQLite database operations |
| postgres | stdio | npx -y @modelcontextprotocol/server-postgres <conn> |
PostgreSQL database |
| datetime | HTTP | url: http://host:8000/mcp |
Date/time tool (example) |
See the MCP Servers directory for more.
The agent supports the Agent Skills open standard, managing skills via folders. Place a folder containing SKILL.md into skills/ and the agent will auto-discover and load it as a usable tool at startup.
Each skill is a self-contained directory:
skills/
├── my-skill/
│ ├── SKILL.md # Required: skill definition (YAML frontmatter + Markdown instructions)
│ ├── scripts/ # Optional: executable scripts
│ │ ├── extract.py
│ │ └── process.sh
│ ├── references/ # Optional: on-demand reference docs
│ │ ├── REFERENCE.md
│ │ └── api_docs.md
│ └── assets/ # Optional: static resources (templates, data files, etc.)
│ └── template.json
---
name: my-skill
description: Describe what the skill does and when to use it. Include keywords for agent matching.
license: Apache-2.0
compatibility: Requires Python 3.10+
---
# Skill Instructions
Tell the agent how to handle this type of task...
## Workflow
1. Step one
2. Step two
3. Step threeFrontmatter fields:
| Field | Required | Description |
|---|---|---|
name |
Yes | Skill name (lowercase + hyphens, e.g. code-review) |
description |
Yes | What the skill does and when to use it (max 1024 chars) |
license |
No | License |
compatibility |
No | Environment requirements |
allowed-tools |
No | List of tools this skill is allowed to call |
The agent supports three modes when calling a skill:
1. LLM Mode (default): Uses the SKILL.md instructions as an expert prompt, letting the LLM handle the task professionally.
{"task": "Review the quality of the following code..."}2. Script Mode: Specify the script parameter to directly execute a script in the scripts/ directory. Supports .py, .sh, .js, .ps1.
{"task": "Stats for Main.java", "script": "count_stats.py", "script_args": "Main.java --lang java"}3. Reference Doc Mode: Specify the reference parameter to load a document from references/ into context, then hand it to the LLM.
{"task": "Review the API design against the spec", "reference": "api_docs.md"}Specify the skills directory path in application.yml:
skillsDir: "./skills"Defaults to ./skills but can be any path. The agent scans all subdirectories containing SKILL.md at startup and loads them automatically.
The project ships with two example skills:
| Skill | Type | Description |
|---|---|---|
translation |
Pure LLM | Multilingual translation skill, injects expert translation prompts |
code-stats |
LLM + Script | Code statistics skill, supports Python script for precise stats |
- Create a new folder under
skills/(the folder name is the skill name):
mkdir skills/my-skill- Create
SKILL.md:
---
name: my-skill
description: Describe what your skill does and when to use it
---
# Skill Instructions
You are a xxx expert. Follow these steps to process...- (Optional) Add scripts, reference docs, or resources:
skills/my-skill/
├── SKILL.md
├── scripts/
│ └── process.py
└── references/
└── guide.md
- Restart the agent — it will be auto-loaded and ready to use.
Implement the Tool interface and register it in the ToolRegistry:
public class WeatherTool implements Tool {
@Override
public String name() { return "get_weather"; }
@Override
public ToolSpec spec() {
return new ToolSpec("get_weather", "Query the weather for a given city",
"{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"]}");
}
@Override
public ToolResult execute(String argumentsJson) {
// Parse args, call weather API, return result
return ToolResult.ok("Beijing, sunny, 25°C");
}
}Register it in Main.defaultTools():
return new ToolRegistry()
.register(new CalculatorTool())
.register(new WeatherTool());Implement the LLMClient interface and add a branch in LLMFactory.create() (e.g. to integrate Anthropic Claude, local Ollama, etc.).
- LangChain Plan-and-Execute Agent: The plan-then-execute-then-replan loop paradigm.
- OpenManus / Hermes and similar agents: Role-based prompting, tool-calling loops, dynamic replanning.