Skip to content

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.


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();

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".

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 AgentTool instance (implements AgentTool interface)
  • 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();

FieldTypeDefaultDescription
maxIterationsint25Maximum tool-call iterations before forcing a final answer
allowDelegationbooleanfalseWhether this agent may delegate to other agents
verbosebooleanfalseLogs prompts and responses at INFO level
responseFormatString""Formatting instruction appended to the system prompt

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.

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 wordSynthesized role
Research / InvestigateResearcher
Write / Draft / ComposeWriter
Analyze / Analyse / EvaluateAnalyst
DesignDesigner
Build / Implement / DevelopDeveloper
Test / VerifyTester
Summarize / SummariseSummarizer
ReviewReviewer
PlanPlanner
(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");

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.

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();

Use an explicit agent when you need:

  • A specific persona with a crafted background
  • Verbose logging for debugging a particular task
  • A custom responseFormat instruction
  • 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.