Skip to content

Latest commit

 

History

History
247 lines (193 loc) · 10.5 KB

File metadata and controls

247 lines (193 loc) · 10.5 KB

Agents

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.

Builder

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.

@AgentDef annotation

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.

Identity

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.

LLM tuning

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.

Tools

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.

Multi-agent

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.

Guardrails

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.

Credentials

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");
    // ...
}

Code execution

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

Callbacks

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.

Fallback

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

Running an agent

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.