Agent Quickstart
The goal of this page is not to show the "shortest demo", but to help you first get a minimal yet real Agent main path working.
By "real" we mean the path must go through at least:
AgentBuilderdefault wiring- the
AgentRuntimestep loop AgentModelClientprotocol adaptationAgentMemorywrite and write-backAgentResultconvergence
If this path is not yet working, you should not pile on:
- workflow
- subagent
- team
- custom approval
- trace platform integration
1. First grasp 4 key design decisions
1.1 Quickstart first validates that "the runtime path holds", not "every feature is loaded"
The minimal quickstart has only one job:
Prove that a single Agent run can travel from input to final output.
So the first version of the example should not, out of the gate, depend on:
- multiple tools
- external approval
- complex memory
- workflow orchestration
Otherwise you cannot tell which layer a problem lives in.
1.2 modelClient(...) is a required dependency; the model name is not
What AgentBuilder.build() truly hard-requires is:
modelClient != null
If you do not pass it, it throws directly:
IllegalStateException: modelClient is required
And while model(...) will not stop you at the Builder stage, BaseAgentRuntime.buildPrompt(...) checks at runtime:
IllegalStateException: model is required
In other words:
modelClientdecides how the request is sentmodeldecides who the request is sent to
Missing either one will not work.
1.3 The default maxSteps = 0 is not a safe default
Among the defaults of AgentOptions.builder().build():
maxSteps = 0
And the semantics of BaseAgentRuntime.runInternal(...) are:
- only
maxSteps > 0is treated as a hard cap - otherwise the loop sets no step limit
So in the quickstart example it is best to set it explicitly:
.options(AgentOptions.builder().maxSteps(1).build())
or:
.options(AgentOptions.builder().maxSteps(2).build())
Do not mistake "convenient for experimentation" for "a safe production default".
1.4 toolRegistry(List<String>, List<String>) is a convenience entry point, not the only low-level one
What you are most likely to see in a quickstart is:
.toolRegistry(Arrays.asList("queryWeather"), null)
But this API is essentially a reflective wiring entry point that tries to create:
ToolUtilRegistryToolUtilExecutor
If the relevant integration module is not on the classpath, build() will fail.
So the more robust way to understand it is:
- this is a demo / quick-wiring entry point
- it is not the only official production wiring approach
2. First correct validation path: run a tool-less Agent first
For the minimal quickstart, "tool-less validation" is the most recommended first step.
The reason is simple:
- no dependency on ToolUtil
- no dependency on a tool allowlist
- no dependency on tool execution
- it only validates the model protocol + runtime loop + memory write-back
Example:
import io.github.lnyocly.ai4j.agent.Agent;
import io.github.lnyocly.ai4j.agent.AgentOptions;
import io.github.lnyocly.ai4j.agent.AgentRequest;
import io.github.lnyocly.ai4j.agent.AgentResult;
import io.github.lnyocly.ai4j.agent.Agents;
import io.github.lnyocly.ai4j.agent.model.ResponsesModelClient;
Agent agent = Agents.react()
.modelClient(new ResponsesModelClient(responsesService))
.model("gpt-4.1")
.systemPrompt("You are a concise assistant.")
.options(AgentOptions.builder().maxSteps(1).build())
.build();
AgentResult result = agent.run(AgentRequest.builder()
.input("Introduce the AI4J Agent in one sentence.")
.build());
System.out.println(result.getOutputText());
System.out.println(result.getSteps());
If this step fails, the problem usually lies only in:
- provider credentials / baseUrl
- the
modelClientprotocol wiring - the model name
- the most basic runtime execution
3. Second validation path: add a minimal tool allowlist
Once the tool-less Agent runs, adding tools becomes meaningful.
Example:
Agent agent = Agents.react()
.modelClient(new ResponsesModelClient(responsesService))
.model("gpt-4.1")
.systemPrompt("You are a weather assistant.")
.instructions("Use queryWeather when weather information is needed.")
.toolRegistry(java.util.Arrays.asList("queryWeather"), null)
.options(AgentOptions.builder().maxSteps(2).build())
.build();
AgentResult result = agent.run(AgentRequest.builder()
.input("Give me a weather summary for Beijing today.")
.build());
What this validates is not "whether a weather tool exists in the system", but whether the following three things hold at the same time:
- The tool schema can be exposed to the model
- The model actually returns a tool call
- The
ToolExecutorcan execute it and write the result back to memory
4. What actually happens behind this code
The most valuable thing about the quickstart is not the line count, but that it happens to cover the default wiring path.
4.1 Agents.react() is not a magic entry point
It essentially just enters AgentBuilder and defaults to ReActRuntime.
4.2 What default values AgentBuilder.build() fills in
The current default wiring includes:
runtime->ReActRuntimememorySupplier->InMemoryAgentMemory::newtoolRegistry->StaticToolRegistry.empty()codeExecutor->NashornCodeExecutoron Java 8,GraalVmCodeExecutoron higher versionsoptions->AgentOptions.builder().build()codeActOptions->CodeActOptions.builder().build()eventPublisher-> a newAgentEventPublisher
If you also configure:
traceExporter(...)
the Builder attaches an AgentTraceListener along the way.
4.3 Which path run(...) actually enters
The execution path is roughly:
Agent.run(...)
-> ReActRuntime.run(...)
-> BaseAgentRuntime.runInternal(...)
-> memory.addUserInput(...)
-> buildPrompt(...)
-> modelClient.create(...)
-> memory.addOutputItems(...)
-> normalizeToolCalls(...)
-> execute tools if needed
-> final AgentResult
If you do not yet understand this path, it is best not to keep adding new capabilities to the quickstart.
5. What the quickstart should validate first
5.1 First check whether it can complete one turn
Minimum requirements:
- no exception thrown
AgentResult.outputTexthas a valueAgentResult.stepsis a reasonable value
5.2 Then check whether it unexpectedly enters multiple turns
If you only wanted a single round of pure Q&A, yet find:
steps > 1
it usually means:
- the prompt induced tool behavior
- or the model output was interpreted as a condition to continue the loop
5.3 For the tool version, also check whether the loop fully closes
For the tool version, do not look only at the final answer; also check:
toolCallstoolResultssteps
Because sometimes the final answer looks "plausibly correct", but in fact no tool was ever triggered.
6. The fields in AgentResult most worth looking at
AgentResult currently has very few fields:
outputTextrawResponsetoolCallstoolResultssteps
Their troubleshooting value is:
| Field | Primary use |
|---|---|
outputText | See the final answer |
rawResponse | See the provider's raw response structure |
toolCalls | See which tools the model actually wanted to call |
toolResults | See whether the tools actually executed and what they produced |
steps | See how many turns the loop ran |
7. The most common quickstart errors, and which layer each actually points to
7.1 Passing only a model name, without modelClient
This is not a model problem; it means the protocol adaptation layer was never wired in.
7.2 Calling a Core SDK service directly and assuming you have entered the Agent
Calling IChatService / IResponsesService directly is not yet an Agent.
Only when you enter the AgentRuntime main loop have you truly entered the Agent layer.
7.3 The tool exists, but the model cannot see it
This is usually not because the tool implementation was not written, but because you did not put the tool into:
toolRegistry
7.4 toolRegistry(List<String>, List<String>) throws as soon as you call it
This is usually not a runtime problem, but rather:
ToolUtilRegistryToolUtilExecutor
the corresponding modules are not on the classpath.
7.5 Stacking workflow / subagent / team all at once from the start
This expands the troubleshooting surface from:
- a single Agent
to instantly:
- runtime
- tool surface
- handoff
- task board
- message bus
This is not faster; it is harder to locate.
8. When the quickstart is no longer enough
If you have run into any of the following, it is time to leave the quickstart behind:
- You want to decide whether
ChatModelClientorResponsesModelClientfits better - You want to understand how
systemPromptandinstructionsactually map - You want tool approval, interception, audit
- You want longer sessions or memory compaction
- You want to orchestrate Agent nodes into a workflow
- You want to switch to CodeAct / SubAgent / Team
The quickstart's mission is to help you get the entry point working first, not to carry all the complexity.
9. Recommended minimum validation order
- First run a tool-less Agent
- Then add a minimal tool allowlist
- Then turn on tracing to inspect step / model / tool
- Then consider memory / workflow / subagent / team
This order looks slow, but in practice yields the fastest troubleshooting.
10. Recommended source reading order
ai4j-agent/src/main/java/io/github/lnyocly/ai4j/agent/Agents.javaai4j-agent/src/main/java/io/github/lnyocly/ai4j/agent/AgentBuilder.javaai4j-agent/src/main/java/io/github/lnyocly/ai4j/agent/Agent.javaai4j-agent/src/main/java/io/github/lnyocly/ai4j/agent/AgentOptions.javaai4j-agent/src/main/java/io/github/lnyocly/ai4j/agent/runtime/BaseAgentRuntime.javaai4j-agent/src/main/java/io/github/lnyocly/ai4j/agent/runtime/ReActRuntime.javaai4j-agent/src/main/java/io/github/lnyocly/ai4j/agent/AgentResult.java