Agents
In v2, agents are optional. When a task has no explicit agent, the framework synthesizes one automatically from the task description. You only need to declare an agent explicitly when you want full control over the persona (role, background, verbose logging, etc.).
For most use cases, rely on the zero-ceremony API and let the framework do the rest. See AgentSynthesizer below for details on how synthesis works.
Creating an Agent (explicit, power-user)
Section titled “Creating an Agent (explicit, power-user)”Agent researcher = Agent.builder() .role("Senior Research Analyst") .goal("Find accurate, well-structured information on any given topic") .background("You are a veteran researcher with 15 years of industry experience.") .llm(model) .build();The role, goal, and llm fields are required. All other fields are optional.
Bind the agent to a specific task:
Task task = Task.builder() .description("Research the latest developments in AI") .expectedOutput("A 400-word summary") .agent(researcher) // explicit agent .build();Role and Goal
Section titled “Role and Goal”The role and goal fields are the most important parts of the agent identity. They are
combined into the system prompt that the agent receives before each task.
- role: The agent’s title. Keep it concise and descriptive.
Example:
"Data Scientist","Legal Reviewer","Code Reviewer". - goal: The agent’s primary objective. Write this as a directive.
Example:
"Analyse data and identify statistical patterns".
Background
Section titled “Background”The background field adds context to the system prompt, giving the agent a persona and
relevant domain knowledge.
Agent analyst = Agent.builder() .role("Financial Analyst") .goal("Identify investment opportunities from market data") .background("You are a CFA-certified financial analyst with expertise in tech sector equities.") .llm(model) .build();The tools field registers tools available during the ReAct loop. Each entry must be either:
- An
AgentToolinstance (implementsAgentToolinterface) - An object with
@Tool-annotated methods
Agent researcher = Agent.builder() .role("Researcher") .goal("Find up-to-date information using web search") .llm(model) .tools(List.of(new WebSearchTool(), new CalculatorTool())) .build();Other Fields
Section titled “Other Fields”| Field | Type | Default | Description |
|---|---|---|---|
maxIterations | int | 25 | Maximum tool-call iterations before forcing a final answer |
allowDelegation | boolean | false | Whether this agent may delegate to other agents |
verbose | boolean | false | Logs prompts and responses at INFO level |
responseFormat | String | "" | Formatting instruction appended to the system prompt |
AgentSynthesizer
Section titled “AgentSynthesizer”When a task has no explicit agent, the framework auto-synthesizes one using the ensemble’s
configured AgentSynthesizer. The synthesized agent is ephemeral — it exists only for
the duration of that task execution.
Template-based synthesis (default)
Section titled “Template-based synthesis (default)”The default synthesizer extracts a role noun from the task description using a verb-to-role lookup table. No extra LLM call is made:
| First word | Synthesized role |
|---|---|
| Research / Investigate | Researcher |
| Write / Draft / Compose | Writer |
| Analyze / Analyse / Evaluate | Analyst |
| Design | Designer |
| Build / Implement / Develop | Developer |
| Test / Verify | Tester |
| Summarize / Summarise | Summarizer |
| Review | Reviewer |
| Plan | Planner |
| (anything else) | Agent |
The goal is set to the full task description. The backstory is derived from the role.
// "Research AI trends" -> role "Researcher", goal "Research AI trends"Task task = Task.of("Research AI trends and summarise findings");LLM-based synthesis (opt-in)
Section titled “LLM-based synthesis (opt-in)”For higher-quality personas, use AgentSynthesizer.llmBased(). This makes one additional
LLM call per agentless task to generate a tailored role, goal, and backstory:
Ensemble.builder() .chatLanguageModel(model) .agentSynthesizer(AgentSynthesizer.llmBased()) .task(Task.of("Analyse the quarterly earnings report")) .build() .run();For tests that use a stub or fake `ChatModel`, **prefer the default `AgentSynthesizer.template()`**(no LLM call during synthesis) or ensure the stub returns valid JSON persona responses.See the [Testing guide](testing.md#agent-synthesis-and-stub-models) for patterns.Custom synthesizer
Section titled “Custom synthesizer”Implement the AgentSynthesizer interface to provide your own strategy:
AgentSynthesizer customSynthesizer = (task, ctx) -> Agent.builder() .role("Domain Expert") .goal(task.getDescription()) .background("You are an expert in this domain.") .llm(ctx.model()) .build();
Ensemble.builder() .chatLanguageModel(model) .agentSynthesizer(customSynthesizer) .task(Task.of("Process the data")) .build() .run();When to use explicit agents
Section titled “When to use explicit agents”Use an explicit agent when you need:
- A specific persona with a crafted background
- Verbose logging for debugging a particular task
- A custom
responseFormatinstruction - An agent with
allowDelegation = true - A different LLM model than the ensemble default
For all other cases, the zero-ceremony API (Task.of(), Ensemble.run()) is simpler and
equally effective.