Agent is the single orchestration primitive. One agent wraps an LLM plus tools. An agent whose tools include other agents is a multi-agent system — no separate Team or Swarm classes.
Agent agent = Agent.builder()
.name("my_agent") // required; becomes the Conductor workflow name
.model("anthropic/claude-sonnet-4-6") // required; "provider/model" format
.instructions("You are a helpful agent.") // system prompt
.build();Every field below is optional.
Instead of the builder, a method can be annotated with @AgentDef (the Java counterpart of the Python SDK's @agent decorator). The method body returns the instructions; @Tool and @GuardrailDef methods on the same object are attached automatically.
import org.conductoross.conductor.ai.Agent;
import org.conductoross.conductor.ai.annotations.AgentDef;
import org.conductoross.conductor.ai.annotations.Tool;
public class Weather {
@Tool(name = "get_weather", description = "Get weather for a city")
public String getWeather(String city) { return "Sunny, 72F in " + city; }
@AgentDef(model = "openai/gpt-4o")
public String weatherbot() {
return "You are a weather assistant.";
}
}
Agent agent = Agent.fromInstance(new Weather(), "weatherbot");| Attribute | Default | Description |
|---|---|---|
name |
method name | Agent name. |
model |
"" |
"provider/model". When empty and used as a sub-agent, inherits the parent's model. |
instructions |
"" |
Static system prompt. A non-empty String returned by the method wins over this attribute. |
tools |
{"*"} |
Names of @Tool methods on the same object. {"*"} = all, {} = none. |
guardrails |
{"*"} |
Names of @GuardrailDef methods on the same object. Same wildcard rules. |
agents |
{} |
Names of other @AgentDef methods on the same object, used as sub-agents. |
strategy |
HANDOFF |
Multi-agent strategy. |
maxTurns |
25 |
Maximum agent loop iterations. |
maxTokens |
unset | LLM max_tokens (0 = unset). |
temperature |
unset | Sampling temperature (NaN = unset). |
credentials |
{} |
Agent-level credential names. |
contextWindowBudget |
unset | Proactive condensation threshold (0 = unset). |
Method contract. The return type declares what the method provides:
| Return type | Meaning |
|---|---|
void |
Nothing — the annotation attributes alone define the agent. |
String |
Dynamic instructions. A no-arg method is lazy: re-invoked on every run submission (when the config is serialized), so the prompt can reflect current state. A non-empty result wins over the instructions attribute. |
PromptTemplate |
Server-side instructions template (instructionsTemplate); invoked once. |
Agent.Builder |
The definition itself — the returned builder is built. |
Agent |
The definition itself, returned as-is (full factory, CrewAI-style). |
The method may take no parameters, or a single Agent.Builder parameter — the escape hatch to the full builder API. The builder arrives pre-populated from the annotation and the discovered tools/guardrails/sub-agents; the method body can then apply anything the builder supports, including sub-agents defined in other classes. Builder-param methods are invoked exactly once (a customizer must not be replayed per run):
public class Research {
@AgentDef(model = "openai/gpt-4o", instructions = "You are a researcher.")
public void researcher(Agent.Builder builder) {
builder.termination(new MaxMessageTermination(10))
.agents(Agent.fromInstance(new Editing(), "editor"));
}
// full factory — annotation is a discovery marker; attributes other than name are rejected
@AgentDef
public Agent reviewer() {
return Agent.builder().name("reviewer").model("openai/gpt-4o")
.instructions("Review the draft.").build();
}
}Agent.fromInstance(obj) resolves all @AgentDef methods on an object; Agent.fromInstance(obj, "name") resolves one. For factory methods the lookup name is still the annotation name/method name, while the agent keeps the name the factory set.
Dynamic instructions are also available directly on the builder, without the annotation: Agent.builder().instructions(() -> "Today is " + LocalDate.now()) — the supplier is re-evaluated on each run submission, matching the Python SDK's callable instructions.
Discovery rules. @AgentDef methods must be public (a non-public annotated method throws rather than being silently ignored) and cannot also carry @Tool or @GuardrailDef. Discovery walks the full type hierarchy — superclasses and interfaces (including default methods) — and the nearest annotated declaration wins. An unannotated override does not hide the agent: the ancestor's annotation is used and invocation dispatches to the override, so CGLIB-proxied Spring beans (@Transactional etc.) keep working. In Spring Boot apps, the auto-configured AgentCatalog collects @AgentDef agents from every bean.
| Builder method | Type | Default | Description |
|---|---|---|---|
name(String) |
String |
— | Required. Unique workflow name. Use snake_case. |
model(String) |
String |
— | Required. "provider/model", e.g. "openai/gpt-4o", "anthropic/claude-opus-4-8". |
instructions(String) |
String |
"" |
System prompt. |
instructionsTemplate(PromptTemplate) |
PromptTemplate |
null |
Prompt stored on the server; use PromptTemplate.of("name"). |
introduction(String) |
String |
null |
Injected before the first user message (not the system prompt). |
metadata(Map<String,Object>) |
Map |
{} |
Arbitrary key-value metadata stored with the workflow. |
| Builder method | Type | Default | Description |
|---|---|---|---|
maxTurns(int) |
int |
25 |
Maximum agent loop iterations before termination. |
maxTokens(int) |
int |
null |
LLM max_tokens. |
temperature(double) |
double |
null |
LLM sampling temperature. |
thinkingBudgetTokens(int) |
int |
null |
Extended thinking budget (Anthropic only). |
timeoutSeconds(int) |
int |
0 |
Overall agent execution timeout. 0 lets the server apply its own default. |
import org.conductoross.conductor.ai.internal.ToolRegistry;
Agent agent = Agent.builder()
.name("tool_agent")
.model("anthropic/claude-sonnet-4-6")
.tools(ToolRegistry.fromInstance(new MyTools())) // from @Tool-annotated POJO
.tools(HttpTool.builder()
.name("search")
.url("https://api.example.com/search")
.method("GET").build()) // HTTP tool
.build();See Tools for all tool types.
Agent pipeline = writer.then(editor); // .then() = Strategy.SEQUENTIAL shorthand
Agent team = Agent.builder()
.name("team")
.model("anthropic/claude-sonnet-4-6")
.agents(writer, editor, reviewer)
.strategy(Strategy.PARALLEL)
.build();See Multi-Agent for all strategies.
import org.conductoross.conductor.ai.guardrail.RegexGuardrail;
import org.conductoross.conductor.ai.guardrail.LLMGuardrail;
import org.conductoross.conductor.ai.enums.Position;
import org.conductoross.conductor.ai.enums.OnFail;
Agent agent = Agent.builder()
.name("safe_agent")
.model("anthropic/claude-sonnet-4-6")
.guardrails(
RegexGuardrail.builder()
.name("no_phone").position(Position.OUTPUT)
.patterns("\\d{10}").onFail(OnFail.RAISE).build(),
LLMGuardrail.builder()
.name("tone_check").position(Position.OUTPUT)
.model("anthropic/claude-sonnet-4-6").policy("The tone must be professional.")
.onFail(OnFail.RETRY).build())
.build();See Guardrails.
Declare which secrets the agent's tools require. A capable Conductor server resolves the declared names at poll time and delivers them to the tool's per-call context; plaintext values never enter workflow input or output.
Agent agent = Agent.builder()
.name("github_agent")
.model("anthropic/claude-sonnet-4-6")
.credentials("GITHUB_TOKEN", "JIRA_API_KEY")
.build();
// In the tool — read the resolved secret off the injected ToolContext:
public String createIssue(String title, ToolContext ctx) {
String token = ctx.getCredential("GITHUB_TOKEN");
// ...
}Agent agent = Agent.builder()
.name("coder")
.model("anthropic/claude-sonnet-4-6")
.localCodeExecution(true)
.allowedLanguages(List.of("python", "javascript"))
.codeExecutionTimeout(30) // seconds per execution
.build();Intercept the agent loop before or after each model call:
Agent agent = Agent.builder()
.name("observed_agent")
.model("anthropic/claude-sonnet-4-6")
.beforeModelCallback(ctx -> {
System.out.println("Calling LLM with: " + ctx.get("messages"));
return ctx; // return modified context or the original
})
.afterModelCallback(ctx -> {
System.out.println("LLM replied: " + ctx.get("output"));
return ctx;
})
.build();For composable, reusable hooks — including onToolStart/onToolEnd and agent-level
start/end — use a CallbackHandler. See Callbacks.
Run a second agent if the first exceeds fallbackMaxTurns:
Agent agent = Agent.builder()
.name("primary")
.model("anthropic/claude-sonnet-4-6")
.fallback(Agent.builder().name("backup").model("anthropic/claude-haiku-4-5-20251001").build())
.fallbackMaxTurns(5)
.build();Use AgentRuntime as the SDK entry point and close it when the application stops. For a simple request/response flow:
try (AgentRuntime runtime = new AgentRuntime()) {
AgentResult result = runtime.run(agent, "Hello!");
System.out.println(result.getOutput());
}AgentResult contains the output, terminal status, execution ID, tool calls, events, token usage, and any error. For background work use start and its AgentHandle; for incremental output use stream. AgentRuntime is thread-safe, so share it instead of creating one per request.
See AgentRuntime for every overload, deployment and serve modes, scheduling, and lifecycle details. See Deploy · Serve · Run · Plan to choose an execution mode.