Workshop AI-Driven Development — Session 2
4 hours
From API calls to autonomous agents in vanilla Java
2026 WayUp
4 intensive hours
What did we learn? Where do we go next?
From passive tool to autonomous actor
Definition: an agent is an LLM that can call tools, observe results, and decide what to do next — autonomously.
Four components working together
Observe → Think → Act → repeat until done
Key insight: the agent keeps looping until the LLM decides no more tools are needed.
HttpClient + org.json — no frameworks
var body = new JSONObject()
.put("model", "gpt-5-mini")
.put("messages", new JSONArray(messages))
.put("tools", registry.declarations())
.put("tool_choice", "auto");
var req = HttpRequest.newBuilder()
.uri(URI.create(
"https://api.openai.com/v1"
+ "/chat/completions"))
.header("Content-Type",
"application/json")
.header("Authorization",
"Bearer " + API_KEY)
.POST(BodyPublishers.ofString(
body.toString()))
.build();
var resp = client.send(req,
BodyHandlers.ofString());
Security: API_KEY = System.getenv("OPENAI_API_KEY") — never hardcode it.
Text response or tool call — two paths
var json = new JSONObject(resp.body());
var message = json
.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("message");
if (message.has("tool_calls")) {
var calls = message.getJSONArray(
"tool_calls");
for (var call : calls) {
var fn = call.getJSONObject(
"function");
String name = fn.getString("name");
var args = new JSONObject(
fn.getString("arguments"));
String id = call.getString("id");
// → execute tool, send result back
}
} else {
String text = message.getString(
"content");
// → final answer
}
Important: arguments is a string, not an object. Always wrap it with new JSONObject(fn.getString("arguments")).
The pattern behind every agent framework
interface Tool {
String name();
String description();
JSONObject parameters();
String execute(JSONObject args);
}
class ToolRegistry {
Map<String, Tool> tools = new HashMap<>();
void register(Tool t) {
tools.put(t.name(), t);
}
String run(String name, JSONObject args) {
return tools.get(name).execute(args);
}
JSONArray declarations() {
var arr = new JSONArray();
for (var t : tools.values()) {
arr.put(new JSONObject()
.put("type", "function")
.put("function", new JSONObject()
.put("name", t.name())
.put("description", t.description())
.put("parameters", t.parameters())));
}
return arr;
}
}
Fun fact: this is the same pattern used by LangChain, Spring AI, and Claude’s tool system.
A concrete tool implementation
class CalculatorTool implements Tool {
public String name() {
return "calculate";
}
public String description() {
return "Evaluate a math expression";
}
public JSONObject parameters() {
return new JSONObject("""
{"type":"object","properties":{
"expression":{"type":"string",
"description":"e.g. 2+3*4"}
},"required":["expression"]}""");
}
public String execute(JSONObject args) {
String expr = args.getString(
"expression");
// Simple eval (or use ScriptEngine)
return String.valueOf(eval(expr));
}
}
registry.register(new CalculatorTool())Lab preview: in the lab you’ll implement at least 2 tools like this.
30 lines of Java — that’s the entire agent
var messages = new ArrayList<JSONObject>();
messages.add(systemMsg(prompt));
messages.add(userMsg(input));
for (int i = 0; i < MAX_ITER; i++) {
var resp = llm.call(messages,
registry.declarations());
var msg = resp.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("message");
if (msg.has("tool_calls")) {
messages.add(msg);
for (var tc : msg.getJSONArray(
"tool_calls")) {
var fn = tc.getJSONObject(
"function");
var result = registry.run(
fn.getString("name"),
new JSONObject(
fn.getString("arguments")));
messages.add(new JSONObject()
.put("role", "tool")
.put("tool_call_id",
tc.getString("id"))
.put("content", result));
}
} else {
return msg.getString("content");
}
}
That’s it. A complete LLM agent. Everything else is just adding more tools.
Understand the pattern, then pick the right tool
| Vanilla Java | LangChain4j | Spring AI | |
|---|---|---|---|
| Dependencies | org.json only | ~20 JARs | Spring Boot stack |
| Lines for an agent | ~150 | ~30 | ~20 |
| Learning curve | Just Java | New abstractions | Spring ecosystem |
| Flexibility | Total control | Plugin-based | Convention-based |
| Best for | Learning, prototypes | Medium projects | Enterprise |
| You understand | Everything | Mostly | Framework magic |
Our approach: we use vanilla Java so you see what frameworks do under the hood. Once you understand the loop, any framework becomes transparent.
A tool is a function. A skill is an AI-powered capability.
A skill bundles a system prompt + specialized tools + output format into a single reusable unit. Think of it as a plugin for your agent.
Example: a “Code Review” skill that reads files, analyzes patterns, and outputs a structured report.
System prompt + tools = one reusable capability
class Skill {
private final String name;
private final String systemPrompt;
private final List<Tool> tools;
Skill(String name, String prompt,
List<Tool> tools) {
this.name = name;
this.systemPrompt = prompt;
this.tools = tools;
}
String execute(String userInput) {
var registry = new ToolRegistry();
tools.forEach(registry::register);
var agent = new Agent(
systemPrompt, registry);
return agent.run(userInput);
}
}
Key insight: a skill is just a focused agent. Skills can even call other skills.
A reusable skill with a focused prompt and read-only tools
var reviewSkill = new Skill(
"codeReview",
"""
You are a code reviewer. Analyze
the given code for:
1. Security vulnerabilities
2. Performance issues
3. Readability problems
4. Potential bugs
Return a structured report with
severity: critical / major / minor.
""",
List.of(
new ReadFileTool(),
new CountLinesTool()
)
);
String report = reviewSkill.execute(
"Review src/agent/Agent.java");
System.out.println(report);
Safety: a review skill should NEVER have write tools. Read-only only.
Four ready-to-build skills
Reads Java files, analyzes for bugs, security, and style. Outputs a structured report with severity levels.
Tools: ReadFile, CountLines
Reads a Java class, generates JUnit test cases with edge cases and assertions. Outputs test file content.
Tools: ReadFile, ListMethods
Reads code and comments, generates Javadoc or README sections. Outputs formatted markdown.
Tools: ReadFile, ReadDirectory
Reads CSV/JSON data, computes statistics, generates a human-readable summary with key insights.
Tools: ReadFile, Calculate
The same concept, productized — SKILL.md format
# ~/.claude/skills/review/SKILL.md
---
name: review
description: Review code for bugs and style
user-invocable: true
allowed-tools: Read Grep
argument-hint: [file-path]
---
Review the following file for:
1. Security vulnerabilities
2. Performance issues
3. Readability problems
4. Potential bugs
File to review: $ARGUMENTS
Return a structured report with
severity levels: critical / major / minor.
Cite specific line numbers.
---) — metadata/review command/review src/Agent.java
Same pattern: your Java Skill class = Claude Code SKILL.md. System prompt + tools + input → structured output.
Turn the CRAFT methodology into a reusable command
# ~/.claude/skills/craft/SKILL.md
---
name: craft
description: Generate a CRAFT-structured
prompt for AI-driven development
user-invocable: true
allowed-tools: Read Grep Glob
argument-hint: [feature-description]
---
The user wants to build: $ARGUMENTS
Generate a CRAFT prompt by:
1. **Context**: Use Read and Grep to scan
the project. Identify: framework, lang,
existing files, conventions, types.
2. **Requirement**: Restate what the user
wants with precise acceptance criteria.
Add edge cases and error behavior.
3. **Action**: Specify exact file(s) to
create or modify, with full paths.
4. **Format**: State the tech constraints
(language, framework, patterns, types).
5. **Test**: Describe how to verify the
result: expected behavior, edge cases,
what must NOT happen.
Output the CRAFT prompt in a code block
ready to copy-paste into an AI assistant.
Instead of writing CRAFT prompts manually, the skill auto-generates them by reading your codebase first.
/craft add user login with email
Claude reads your project, finds the stack, existing auth files, types, then outputs a complete CRAFT prompt.
Meta: this skill uses AI to write better AI prompts. That’s the power of skills.
Agent-powered projects — pick one and build it
Reads Java files, analyzes quality, generates structured review. Skills: file reading, pattern detection, report generation.
Reads course notes, answers questions, generates flashcards and quizzes. Skills: document parsing, Q&A, quiz generation.
Reads CSV/JSON, cleans data, computes stats, generates summary. Skills: file I/O, data parsing, text-based visualization.
Reads log files, diagnoses issues, suggests fixes. Skills: command execution, log analysis, troubleshooting.
Queries 2+ public APIs (weather, news, stocks), combines data, generates a briefing. Skills: HTTP fetch, data merge, summary.
Any agent with: 2+ custom tools, 1+ reusable skill, conversation memory, and error handling. Be creative.
What you must deliver
How your project will be graded
| Criterion | Points | Details |
|---|---|---|
| AIDD Methodology | /15 | vision.md, GitHub issues, clean commits, workflow followed |
| Agent Core | /25 | Working agent loop, LLM API integration, message history, proper exit |
| Tools & Skills | /25 | 2+ working tools, 1+ skill, clean interfaces, error handling |
| Code Quality | /15 | Clean Java, proper OOP (interfaces, encapsulation), no dead code |
| Tests | /10 | 1 unit test (tool) + 1 integration test (agent loop), all passing |
| Demo | /10 | Clarity, live demo, technical depth, honest AI assessment |
Total: /100. Agent quality and skill design count 50%. A well-designed agent with 2 solid tools beats a messy agent with 5 broken ones.
Learn by evaluating others’ agent code
5 minutes to showcase your agent
Skills acquired in this session
You now understand what happens inside every AI agent framework. The patterns are universal — Java, Python, TypeScript, the loop is the same.
3 hours to build an autonomous LLM agent
Head to the lab → practical-work-2-personal-challenge.html