<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
	<title>Elder Moraes</title>
	<atom:link href="https://eldermoraes.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://eldermoraes.com</link>
	<description>Backend | Java | Quarkus | AI | Career</description>
	<language>en-US</language>
	<item>
		<title>Code got cheap. Making software didn't.</title>
		<link>https://eldermoraes.com/code-got-cheap-making-software-didnt/</link>
		<pubDate>Wed, 19 Aug 2026 00:00:00 +0000</pubDate>
		<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://eldermoraes.com/?p=8065</guid>
		<description><![CDATA[My friend Markus Eisele put it in one line: code is cheap now, software isn't. Until recently, both were expensive, so the gap stayed hidden. Now you just get to the expensive part faster. Writing code was never the same thing as making software. The typing was always the cheap half, and the cheap half…]]></description>
		<content:encoded><![CDATA[<p>My friend Markus Eisele put it in one line: code is cheap now, software isn't. Until recently, both were expensive, so the gap stayed hidden. Now you just get to the expensive part faster.</p>
<p>Writing code was never the same thing as making software. The typing was always the cheap half, and the cheap half is the one that got cheaper.</p>
<p>Matt Pocock points to the other side: bad code is far more expensive today than it used to be. The reason is speed. The rate at which I can push bad code into production has no ceiling anymore. Whatever I used to get wrong once a sprint, I can now get wrong continuously.</p>
<p>Here is what I see day to day. AI does great work on a good codebase. Well structured, well written, documented, and it just flies. Working with it is a pleasure. On a bad codebase, it goes the other way and starts going nuts.</p>
<p>I once read Venkat Subramaniam stating the sharpest version of why. Developers say AI-generated code is bad. AI was trained on code written by developers. That, he says, is called karma. 🙂</p>
<p>So none of this is a new problem. We have been dealing with design, structure, and documentation for a very long time, and we already know what happens when they are missing. What changed is the clock. The same problems now arrive faster, and usually at a much bigger scale.</p>
<p>If you are running AI on a legacy codebase right now, I would like to hear which of the two you are getting: does it fly, or does it go nuts?</p>
]]></content:encoded>
	</item>
	<item>
		<title>Your Agent Needs to Read Before It Writes</title>
		<link>https://eldermoraes.com/your-agent-needs-to-read-before-it-writes/</link>
		<pubDate>Wed, 12 Aug 2026 00:00:00 +0000</pubDate>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Java]]></category>
		<guid isPermaLink="false">https://eldermoraes.com/?p=8046</guid>
		<description><![CDATA[A few months ago I asked an AI agent to write some Quarkus code for me. The code came back, and it worked. It compiled, it booted, it answered. If the bar were "it works", the story would end here. Then I read the code: ```java @Produces @ApplicationScoped public PromptGuardAgent promptGuardAgent() { ChatModel guardModel =…]]></description>
		<content:encoded><![CDATA[<p>A few months ago I asked an AI agent to write some Quarkus code for me. The code came back, and it worked. It compiled, it booted, it answered. If the bar were "it works", the story would end here.</p>
<p>Then I read the code:</p>
<pre><code class="language-java">@Produces
@ApplicationScoped
public PromptGuardAgent promptGuardAgent() {
    ChatModel guardModel = OllamaChatModel.builder()
            .baseUrl(baseUrl)
            .modelName(guardModelName)
            .temperature(0.0)
            .think(Boolean.FALSE)
            .timeout(Duration.ofSeconds(timeoutSeconds))
            .build();

    return AiServices.builder(PromptGuardAgent.class)
            .chatModel(guardModel)
            .build();
}
</code></pre>
<p>This is real output from an agent in one of my projects. It even shows some care: temperature zero, an explicit timeout. But look at what it is: a producer assembling the <code>ChatModel</code> by hand and calling <code>AiServices.builder</code>. That's raw LangChain4j inside a Quarkus application. The Quarkus way is declarative: <code>@RegisterAiService</code> on the interface, the model named in <code>application.properties</code>. And the difference is expensive. With manual wiring you lose what the integration gives you for free: metrics and traces on every model call, declarative fault tolerance, guardrails you can plug in. None of that stops the app from running today. That's exactly where the danger lives: the problem doesn't show up on day one.</p>
<h2>The bill always arrives</h2>
<p>The gap between "works" and "well built" appears when the system lives on. It appears when someone has to maintain that code six months from now. It appears when traffic grows and resource usage becomes real money. And the pattern multiplies: a class with ten lines that could be one is a detail; ten thousand of them is an extra codebase you didn't need, polluting the context of every future agent session that touches it.</p>
<p>Markus Eisele, who writes The Main Thread and is on my team at IBM, compressed all of this into one line at his JCON keynote this year: "Code is cheap now. Software is not. You just get there a lot faster now." The cost of producing code collapsed. The cost of software (intent, correctness, maintenance, complexity) stayed exactly where it always was. He closes with two variations I keep coming back to: code is cheap, intent is not; code is cheap, verification is everything.</p>
<p>Matt Pocock inverted the same argument at the AI Engineer conference: bad code is the most expensive it has ever been. If your codebase is good, AI multiplies your speed. If it's bad, AI sinks with it and produces more bad code on top. Engineering fundamentals gained value in the age of agents. Good practices stopped being a reviewer's nitpick and became an economic argument.</p>
<p>And none of these pains are new. "It didn't do what I asked": Frederick Brooks, The Design of Design. "Too verbose": Eric Evans, Domain-Driven Design. "Done, but broken": Andy Hunt and Dave Thomas, The Pragmatic Programmer. "Hard to test": John Ousterhout, A Philosophy of Software Design. These books didn't break with AI. They became more important. We don't need new fundamentals; we need the usual ones, written in a format the agent can use.</p>
<p>Venkat Subramaniam summed up the irony: developers say AI-generated code is bad; AI was trained on code written by developers; that's called karma.</p>
<h2>A good engineer with no memory</h2>
<p>Why does the agent miss what a senior engineer gets right? Because everything an experienced engineer carries implicitly (the house patterns, the known traps, the right way to test) the agent doesn't carry between sessions. It's born again in every conversation. That knowledge has to be written somewhere the agent reads. If it isn't written, for the agent it doesn't exist.</p>
<p>Eisele has a phrase for the consequence, and I've adopted it: any structure you present to the model beats any clever prompt. A good prompt solves the task at hand. Structure (a conventions file, skills, MCP servers) solves every task that comes after. It's the difference between asking well and teaching once.</p>
<p>Fabio Akita put it in a way that lands: AI reflects who you are. If your engineering practice is messy, the agent amplifies the mess. If it's explicit and disciplined, the agent amplifies that instead. So the question stops being "does AI write good code?" and becomes: what am I giving it to read?</p>
<p>All of this lands on one thesis: the agent needs to read before it writes. The good news is that in our ecosystem, that work had already started.</p>
<h2>What the Quarkus ecosystem already gives you</h2>
<p>The Quarkus team attacked part of the problem with the Quarkus Agent MCP: a standalone Model Context Protocol (MCP) server that teaches your agent to code using Quarkus. It creates projects and manages the app lifecycle. It exposes skills per extension and runs semantic search over the whole documentation. And it carries a design detail I find brilliant: it runs outside the application process, so when the app crashes, the agent stays alive, reads the structured exception and goes fix the problem.</p>
<p>When I started using it, the quality of the Quarkus code my agents produced jumped immediately.</p>
<p>But a real application is bigger than the framework. There are the design patterns of the Java world, there are tests, there's persistence. And there's LangChain4j, which for me is the most critical case: a young ecosystem with a fast release pace, where the model has little training baggage. That's exactly where being specific and opinionated pays the most. The stakes are not small either: the State of Java survey by Azul found that among companies building AI features, half use Java in the implementation. This problem is ours.</p>
<h2>Quarkus Agentic Scaffolding</h2>
<p>So I built Quarkus Agentic Scaffolding. In one sentence: an installable artifact that puts Quarkus + LangChain4j best practices in front of the agent before it writes the first line.</p>
<p>It's open source under Apache 2.0. The templates are code that compiles, validated in CI. And it installs on practically any assistant that supports the Agent Skills format (Claude Code, Codex, Copilot, Cursor, IBM Bob and others) with one command:</p>
<pre><code class="language-bash">npx skills add eldermoraes/quarkus-agentic-scaffolding
</code></pre>
<p>The architecture separates two things that usually come tangled together. On one side, an always-on conventions file (<code>CLAUDE.md</code> for Claude, <code>AGENTS.md</code> for the rest) that declares the rules the code must follow. On the other, three skills that execute procedures. The skills don't repeat the rules; they point to the file. One source of truth.</p>
<p>A sample of what the conventions declare:</p>
<ul>
<li>Java 25+ as the baseline</li>
<li>Virtual threads by default for blocking work</li>
<li>records, sealed types and pattern matching where they clarify intent</li>
<li>Platform BOMs instead of pinned extension versions</li>
<li>CDI-first</li>
<li>Declarative AI services with <code>@RegisterAiService</code></li>
<li>Declarative guardrails</li>
<li>Easy RAG first, portable to something heavier later</li>
<li>Zero-code observability with Micrometer and OpenTelemetry</li>
</ul>
<p>Nothing in that list is generic clean-code advice. It's the current way of building on this stack, and every line exists because it fixes a mistake agents make in the real world.</p>
<p>The plugin ships three skills.</p>
<p><code>/setup-agentic-scaffolding</code> prepares the ground, and it carries a design decision I care about: the plugin doesn't try to be an island. It verifies your toolchain: JDK 25 / GraalVM, JBang, a container runtime. It registers the Quarkus Agent MCP and Context7, for library documentation fresher than the model's training data. And it recommends the Superpowers skill set for process. Each of these pieces expands what the agent can do well, and the plugin uses all of them along the way.</p>
<p><code>/scaffold-project</code> covers both moments of creation: the new project end-to-end, and the new component inside a project that already exists (an AI service, tools, agents and multi-agent workflows, RAG, an MCP client or server, guardrails). The project skeleton comes from the Quarkus Agent MCP; the skill applies the layout, the base <code>application.properties</code> and the starter templates. You trigger it in natural language: ask for a RAG pipeline and it enters the flow on its own.</p>
<p><code>/audit-project</code> closes the loop, because most projects won't be born now; they already exist. It compares the project against the conventions and returns a prioritized list: what's missing, where the evidence is, what the suggested fix looks like. It's read-only by default and only applies fixes with your confirmation. There's a foundation behind keeping this separate, and Andrej Karpathy named it: generating code and judging code are different capabilities. The audit puts the agent in the reviewer's seat with an explicit ruler in hand. The same kind of agent that wrote the code at the top of this post, now enforcing the practices it used to skip.</p>
<h2>Does it save tokens?</h2>
<p>A fair question: do the skills reduce token usage, or do they only improve quality? Honest answer: I haven't measured it yet. The runs were clearly faster, and my reading is that more upfront direction means less reasoning, because reasoning is largely the agent validating its own inferences. Fewer inferences to validate, fewer tokens burned. I intend to measure it properly and publish the numbers.</p>
<h2>Skills: a new kind of open source asset</h2>
<p>Conventions rot if nobody maintains them, so the project tracks the releases of Quarkus, Java and LangChain4j: when the stack moves, the target moves with it. Semantic versioning, a changelog, CI making sure the templates still compile.</p>
<p>There's a line from the DevStar working group, inside the Quarkus project, that frames this whole space: tools do things; skills teach how to do things well. I believe this becomes a new kind of open source contribution: library maintainers and platform teams writing knowledge for agents to read. And it works inside your company too. Your team's conventions can live in a file the agent reads before writing. The model is the same.</p>
<h2>Try it on your own project</h2>
<p>The sentence that made me start this project is the invitation I'll leave you with: I want to shorten your path to building applications that follow best practices while using AI.</p>
<p>Install it, scaffold a new project, audit one an agent already wrote. Then tell me what you find. Issues, pull requests and criticism are welcome: <a href="https://github.com/eldermoraes/quarkus-agentic-scaffolding">github.com/eldermoraes/quarkus-agentic-scaffolding</a>.</p>
]]></content:encoded>
	</item>
	<item>
		<title>Everyone Says &quot;Context Engineering,&quot; But Thousands of Repositories Say AGENTS.md</title>
		<link>https://eldermoraes.com/everyone-says-context-engineering-but-thousands-of-repositories-say-agents-md/</link>
		<pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate>
		<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://eldermoraes.com/?p=7991</guid>
		<description><![CDATA[Eight configuration mechanisms exist for agentic coding tools. A team of researchers went looking for them in 2,853 GitHub repositories, and what came back is one mechanism carrying almost all the weight. The paper is Harness Engineering for Agentic AI Coding Tools: An Exploratory Study. It builds on work published in AIware 2026. The stated…]]></description>
		<content:encoded><![CDATA[<p>Eight configuration mechanisms exist for agentic coding tools. A team of researchers went looking for them in 2,853 GitHub repositories, and what came back is one mechanism carrying almost all the weight.</p>
<p>The paper is <a href="https://arxiv.org/abs/2602.14690">Harness Engineering for Agentic AI Coding Tools: An Exploratory Study</a>. It builds on work published in AIware 2026. The stated method: identify eight configuration mechanisms <em>spanning from static context to executable and external integrations</em>, then examine whether and how they are adopted across those 2,853 repositories, with a detailed analysis of Context Files, Skills and Subagents.</p>
<p>Two findings carry the paper.</p>
<p>First: <strong>Context Files dominate the configuration landscape and are often the sole mechanism in a repository, with <code>AGENTS.md</code> emerging as an interoperable standard across tools.</strong></p>
<p>Second: <strong>few repositories adopt advanced mechanisms such as Skills and Subagents. Skills predominantly rely on static instructions rather than executable scripts.</strong></p>
<p>Read those two sentences next to any vendor keynote from the last twelve months and you will notice the gap.</p>
<h2>What "sole mechanism" means when you are the one maintaining the repo</h2>
<p>The phrase doing the most work in that first finding is <em>often the sole mechanism</em>. Read it literally: configuration begins and ends there. A repository that has configured its coding agent has, in a large share of cases, written one markdown file and stopped.</p>
<p>If you use a coding agent daily on an enterprise Java codebase, that result is either deflating or vindicating depending on what you have been told to feel bad about. The narrative you have been sold says the sophisticated shop graduates from a context file to skills, then to subagents, then to a fleet of them coordinating. The observed behavior says the sophisticated shop writes a good markdown file and gets back to work.</p>
<p>There is a version of this finding that is just "adoption takes time, the advanced stuff is new." That reading is available and probably partly right. But it does not survive the second half of the second finding: <em>Skills predominantly rely on static instructions rather than executable scripts.</em></p>
<h2>The mechanism people adopted, minus the part that made it a mechanism</h2>
<p>Skills, as a category, exist to hold something a context file cannot: procedure that runs. A skill can carry a script the agent executes: a deterministic step, a verification, a generator, something with an exit code. Executable scripts are optional in a skill (only the SKILL.md file is required), which is exactly why a skill can collapse into a longer paragraph of <code>AGENTS.md</code> with a folder around it.</p>
<p>The finding says the repositories that adopted Skills largely did not adopt that part. They used the skill container to hold more static instructions. So the file structure changed and the mechanism count went up. The configuration surface stayed exactly where it was: text that shapes what the model reads, with nothing that constrains what the system does.</p>
<p>That is a much more specific result than "advanced features have low adoption." It says people reached for the advanced mechanism, and then used it as if it were the basic one.</p>
<h2>Three explanations, and the one you can test</h2>
<p>The paper reports what it observed. Why executable configuration did not take is where the argument is, and from here I am reasoning past the data. Three explanations fit the shape of the finding:</p>
<p><strong>Maintenance cost.</strong> An executable skill is code. It has dependencies and an owner. It breaks when the repo moves, and it needs updating every time the toolchain changes. A markdown file has none of those properties. It degrades silently where code fails loudly, which is worse in principle and much cheaper in practice. Anyone who has maintained a <code>Makefile</code> that only three people on the team can modify already knows this tradeoff and made the same call.</p>
<p><strong>No visible return.</strong> When you improve <code>AGENTS.md</code>, the next agent run reflects it immediately and legibly: you read the output and see your instruction landed. When you add an executable skill, the benefit is conditional on the agent choosing to invoke it, in a situation that matches, on a run you may not observe. Static context pays you back in the same session. Developers optimize what they can see move.</p>
<p><strong>No good way to test it.</strong> This is the explanation I would put money on, and it is the one with a concrete fix. We have decades of practice testing application code and roughly none testing agent configuration. What is the assertion for "this skill fires under the right conditions," and what does an error look like? A team with a mature CI pipeline can tell you in minutes whether a change broke the build. That same team cannot tell you whether last week's edit to their agent config made the agent better or worse, and they know it. Executable configuration without a test harness is a component you ship untested into a nondeterministic runtime. Java teams built an entire testing culture around not doing that.</p>
<p>The three are not exclusive. But notice which one you can act on: cost and visibility are properties of the ecosystem, and testability is a thing a team can build.</p>
<h2>Claude Code users reach for more mechanisms than anyone else</h2>
<p>One result deserves separate treatment: users of Claude Code employ the widest range of mechanisms among the tools studied.</p>
<p>In other words: a particular tool attracts more sophisticated practitioners. Read it as a fact about surfaces and you get something more useful. A mechanism that a tool does not expose cannot show up in a repository, no matter how sophisticated the person typing. If adoption breadth tracks the tool and not the team, what the study measures is the ceiling each tool sets.</p>
<h2>What to actually do with this</h2>
<p>The paper suggests <code>AGENTS.md</code> as a natural starting point, and the empirical result gives that suggestion teeth: it is the interoperable standard across tools, so the effort transfers when you switch. For most repositories it is also, apparently, where the work ends. Given the state of the tooling, that is a defensible place to stop.</p>
<p>The move that follows from the data has no glamour at all. Write the context file, make it genuinely good, and treat every step beyond it as a claim you have to justify. Before adding an executable skill, answer the question those 2,853 repositories answer with silence: how will you know it works, and how will you know when it stops working? If you have an answer, build it. If you do not, you would be adding a component to your build that apparently nobody will verify, which is the situation the static markdown file quietly avoids.</p>
<p>The market is selling multi-agent orchestration. The configuration files of thousands of repositories describe something plainer: one file, written carefully, doing most of the work. With a gap that wide between the pitch and the practice, the question worth asking is what the executable mechanisms still need before teams reach for them. Testability is the first item on that list.</p>
]]></content:encoded>
	</item>
	<item>
		<title>Quarkus 3.37.0.CR1: extensions start teaching the AI coding agent</title>
		<link>https://eldermoraes.com/quarkus-3-37-0-cr1-extensions-start-teaching-the-ai-coding-agent/</link>
		<pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Java]]></category>
		<guid isPermaLink="false">https://eldermoraes.com/?p=7953</guid>
		<description><![CDATA[The release notes for Quarkus 3.37.0.CR1 read like a busy week. A wave of entries adds an "AI skill" to specific extensions, including `quarkus-quartz`, `quarkus-hibernate-reactive`, `quarkus-redis-cache`, and `quarkus-security-jpa`. One line introduces the ability to get response metadata in a streamed response. One enables Jackson reflection-free serializers by default. Two of these lines, read slowly, change…]]></description>
		<content:encoded><![CDATA[<p>The release notes for Quarkus 3.37.0.CR1 read like a busy week. A wave of entries adds an "AI skill" to specific extensions, including <code>quarkus-quartz</code>, <code>quarkus-hibernate-reactive</code>, <code>quarkus-redis-cache</code>, and <code>quarkus-security-jpa</code>. One line introduces the ability to get response metadata in a streamed response. One enables Jackson reflection-free serializers by default. Two of these lines, read slowly, change something structural about how a Quarkus codebase gets built and operated.</p>
<h2>The AI skill moved inside the extension</h2>
<p>The interesting word in "Add AI skill for quarkus-quartz" is <em>for</em>. The skill ships attached to a single extension rather than as a global description of Quarkus living somewhere central.</p>
<p>That placement is the whole argument. A coding agent working in your project does not need Quarkus in the abstract; it needs the version of <code>quarkus-quartz</code> that is actually on your classpath, with the configuration keys and patterns that version supports. When the skill travels with the extension, the agent reads patterns matched to what you installed, instead of patterns averaged over every version ever published to the open web.</p>
<p>The alternative most teams settle for is a single, generic body of "how do I do X in Quarkus" knowledge that an agent queries, one description that knows the framework in the abstract and quickly drifts out of sync. The 3.37.0.CR1 approach inverts the arrangement. Each extension carries its own agent-facing knowledge as a first-class, version-matched artifact, delivered to the agent through the Quarkus Agent MCP server rather than bolted onto the whole framework as one description.</p>
<p>Notice the kinds of extension getting skills in this batch: a scheduler, a reactive ORM, a cache, a security integration. These are the parts where the "right" usage is full of small, version-specific decisions that an agent routinely gets subtly wrong. That is the correct place to start.</p>
<h2>Where hallucinated configs actually come from</h2>
<p>Most wrong Quarkus configuration an agent emits started as real configuration: for a different version, or a property renamed two releases ago, or an extension that was later split. The agent learned it from public text that carries no version stamp, so it cannot tell that the snippet it is confidently reproducing stopped being correct eighteen months ago.</p>
<p>A skill that ships with the extension carries that stamp implicitly. The knowledge an agent reads is the knowledge that matches the artifact resolved into your build. The failure mode persists, but its main fuel source, version-blind text scraped from everywhere, stops being the default input. For a senior reviewer, that is the difference between catching one plausible-but-stale config in review and catching five.</p>
<p>This is the part of the release that compounds. Skills attached to extensions are something every extension can grow over time, and every one that does makes the agent a little less dependent on the public internet for your stack.</p>
<h2>The line that touches your production numbers</h2>
<p>"Introduce ability to get response metadata in streamed response" is the entry nobody will quote. The capability it points at, reading what is around a streamed response rather than just the body, is the kind of thing that quietly changes a dashboard. If you stream LLM output in Quarkus with the LangChain4j extension, you push tokens to the client as they arrive. The text is the easy part. The awkward part has always been everything around it: how many tokens the call consumed, and why the model stopped. That metadata is what you bill against, alert on, and debug with. Capturing token usage and finish reason without buffering the whole response is something the LangChain4j extension already supports through the completed ChatResponse. What this changelog line actually adds lives on the Quarkus REST Client side: a way to read the HTTP status code and headers of a streamed response, rather than the body alone. Before it, reading those meant falling back to the raw Vert.x HTTP client.</p>
<p>Here is the shape of consuming it in a real project. The dependencies first:</p>
<pre><code class="language-xml">&#x3C;dependencies>
    &#x3C;dependency>
        &#x3C;groupId>io.quarkus&#x3C;/groupId>
        &#x3C;artifactId>quarkus-rest-jackson&#x3C;/artifactId>
    &#x3C;/dependency>
    &#x3C;dependency>
        &#x3C;groupId>io.quarkiverse.langchain4j&#x3C;/groupId>
        &#x3C;artifactId>quarkus-langchain4j-openai&#x3C;/artifactId>
    &#x3C;/dependency>
&#x3C;/dependencies>
</code></pre>
<p>There is no separate AI service interface here. Quarkus LangChain4j exposes the configured model as a CDI bean, so the metadata work lives in one service: you inject the default <code>StreamingChatModel</code> and drive it with a <code>StreamingChatResponseHandler</code>. Partial responses flow straight out to the caller as they arrive. When the stream completes, you are handed a <code>ChatResponse</code> whose <code>metadata()</code> carries the finish reason and the token usage. You record it at that point. The text was never buffered:</p>
<pre><code class="language-java">// src/main/java/com/example/story/StoryService.java
package com.example.story;

import java.util.List;

import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.ChatResponseMetadata;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import io.smallrye.mutiny.Multi;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

@ApplicationScoped
public class StoryService {

    private static final Logger LOG = Logger.getLogger(StoryService.class);

    @Inject
    StreamingChatModel model;

    public Multi&#x3C;String> write(String topic) {
        ChatRequest request = ChatRequest.builder()
                .messages(List.of(UserMessage.from(
                        "Write a short story about " + topic + ". Keep it under 200 words.")))
                .build();

        return Multi.createFrom().&#x3C;String>emitter(emitter ->
                model.chat(request, new StreamingChatResponseHandler() {

                    @Override
                    public void onPartialResponse(String partialResponse) {
                        emitter.emit(partialResponse);
                    }

                    @Override
                    public void onCompleteResponse(ChatResponse response) {
                        ChatResponseMetadata metadata = response.metadata();
                        LOG.infof("stream complete: finishReason=%s, tokenUsage=%s",
                                metadata.finishReason(), metadata.tokenUsage());
                        emitter.complete();
                    }

                    @Override
                    public void onError(Throwable error) {
                        emitter.fail(error);
                    }
                }));
    }
}
</code></pre>
<p>The REST resource stays trivial. It takes input, delegates, and streams the result back as Server-Sent Events. It does not know how the stream is produced:</p>
<pre><code class="language-java">// src/main/java/com/example/story/StoryResource.java
package com.example.story;

import io.smallrye.mutiny.Multi;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import org.jboss.resteasy.reactive.RestStreamElementType;

@Path("/stories")
public class StoryResource {

    @Inject
    StoryService service;

    @GET
    @Produces(MediaType.SERVER_SENT_EVENTS)
    @RestStreamElementType(MediaType.TEXT_PLAIN)
    public Multi&#x3C;String> stream(@QueryParam("topic") String topic) {
        return service.write(topic);
    }
}
</code></pre>
<p>Run it with <code>./mvnw quarkus:dev</code> and consume the stream with curl, which is the right tool for SSE since the response is open-ended:</p>
<pre><code class="language-bash">curl -N "http://localhost:8080/stories?topic=a%20lighthouse%20keeper"
</code></pre>
<p>The token usage and finish reason land in your logs the moment the stream closes, on the same call that streamed the text. The completed ChatResponse from the LangChain4j extension is where that payoff comes from: streaming and observability stop being a trade-off you make per endpoint. The placement of the metadata capture matters as much as the feature. It sits in the service, so the resource stays a thin transport and the orchestration has one home.</p>
<h2>Reflection-free serializers, now the default</h2>
<p>"Enable Jackson reflection-free serializers by default" is pure plumbing, and it ships switched on rather than waiting behind a flag. Reflection is the part of JSON serialization that GraalVM Native Image has always had to be told about, registration by registration. A reflection-free path is friendlier to native compilation and trims the reflective work the application does to move objects in and out of JSON.</p>
<p>The part that matters is the default. A capability you opt into helps the teams that already knew to look for it. A default helps the apps that never tuned serialization at all, which is most of them, and it applies on the next upgrade with no code change. That is the quiet kind of improvement that shows up as a slightly leaner build and a slightly cheaper request path across a whole fleet of services nobody is actively optimizing.</p>
<h2>Reading a changelog like a senior</h2>
<p>Most of the noteworthy lines here are about coding agents, so it is tempting to file the whole release under "AI" and move on. The more useful read sorts the lines by what they actually touch.</p>
<p>The AI skills line is architectural: it reshapes how your codebase and your agent relate over the long run, and compounds as more extensions ship skills of their own. The streaming-metadata line is about observability: it changes what you can see in production today, on the calls you are already making. The reflection-free serializer line is about runtime cost: it touches what your app does on every request, by default.</p>
<p>A candidate release rewards a careful read for exactly this reason. The line that gets the attention is the one that demos well. The lines worth acting on are usually the ones that change your numbers without asking for a press release.</p>
]]></content:encoded>
	</item>
	<item>
		<title>LangChain4j Brings the Blackboard Pattern to the JVM</title>
		<link>https://eldermoraes.com/langchain4j-brings-the-blackboard-pattern-to-the-jvm/</link>
		<pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Java]]></category>
		<guid isPermaLink="false">https://eldermoraes.com/?p=7926</guid>
		<description><![CDATA[The LangChain4j 1.16.0 release notes carry a line that should make anyone running a multi-agent system on the JVM stop scrolling: Introduce Blackboard agentic pattern. Until now, when you wanted agents to coordinate in LangChain4j, you reached for one of the orchestration shapes the library already gave you through `AgenticServices`: a sequence, a parallel fan-out…]]></description>
		<content:encoded><![CDATA[<p>The <a href="https://github.com/langchain4j/langchain4j/releases/tag/1.16.0">LangChain4j 1.16.0 release notes</a> carry a line that should make anyone running a multi-agent system on the JVM stop scrolling: <em>Introduce Blackboard agentic pattern.</em> Until now, when you wanted agents to coordinate in LangChain4j, you reached for one of the orchestration shapes the library already gave you through <code>AgenticServices</code>: a sequence, a parallel fan-out or mapper, or a supervisor routing work down to sub-agents. The blackboard is a different shape of coordination. It is also the oldest one in the room, and it arrives bringing a different approach if compared to the sequence and supervisor builders.</p>
<h2>What "blackboard" actually means</h2>
<p>The blackboard is a coordination architecture from the late 1970s and early 1980s, made famous by the Hearsay-II speech-understanding system. The idea is simple to state and consequential to implement: you keep one shared data structure, the blackboard, that holds the evolving solution. Around it sit independent specialists (classically called <em>knowledge sources</em>). Each specialist watches the blackboard, and whenever the current state matches what it knows how to act on, it contributes by writing back to the board. A control component decides which pending contribution runs next.</p>
<p>The defining property is that no specialist knows the global plan, and the order of contributions is not fixed when you wire the system together. Coordination is opportunistic: whoever can make a useful move given what is currently on the board, makes it. The solution emerges from the accumulated writes, not from a route you drew in advance.</p>
<p>That is a real departure from the agentic workflows LangChain4j already shipped. A <code>sequenceBuilder</code> workflow runs A, then B, then C, in an order you decided at build time. A <code>parallelBuilder</code> fan-out runs every sub-agent and merges their outputs. Even the <code>supervisorBuilder</code>, which is the most dynamic of the three, is still hub-and-spoke: a single controller decides who acts and threads context to them. In all three, you can draw the graph before the first request arrives.</p>
<p>The blackboard is for the case where you cannot.</p>
<h2>The decision rule</h2>
<p>Here is the rule worth internalizing before you touch the new builder: <strong>reach for the blackboard only when the order in which agents should contribute is genuinely emergent, not knowable a priori.</strong></p>
<p>If you can sketch the directed graph of who-runs-after-whom up front, you do not have a blackboard problem. You have a sequence, a parallel fan-out, or a supervisor routing problem, and those are simpler to reason about, simpler to test, and far simpler to debug. Picking a blackboard for a problem whose flow is actually fixed buys you nondeterminism you did not need.</p>
<p>The blackboard earns its complexity when the problem is shaped like this: you have many specialists, any one of which might hold the next useful move, and <em>which</em> one depends entirely on the partial solution built so far. Diagnostic reasoning, planning under incomplete information, multi-source interpretation where one agent's output is what makes another's contribution relevant. Those are the problems where a fixed route is a lie and a supervisor becomes a bottleneck guessing at routing it cannot pre-compute.</p>
<p>If you are building a multi-agent system on the JVM right now, whatever internal name it carries, the honest test is one question: <em>can I draw the flow before the request comes in?</em> If yes, a simpler shape already fits the problem, and the blackboard trades that simplicity for coordination the problem does not call for. If no, keep reading, because the rest of this is what a changelog was never going to cover.</p>
<h2>The cost nobody counts</h2>
<p>A blackboard is, by definition, mutable state that multiple agents read from and write to. LangChain4j already had shared state in its agentic workflows: the agentic scope that sub-agents access with <code>readState</code> and <code>writeState</code>. In a sequence or parallel workflow, that scope is incidental plumbing, and the framework controls <em>when</em> writes happen, at defined step boundaries. The blackboard pattern promotes that same shared state from plumbing to the central coordination mechanism, and it hands the timing of writes to the agents themselves.</p>
<p>So the prerequisite question before adopting the blackboard is concrete and unglamorous: <strong>what does LangChain4j guarantee about concurrent access to the blackboard, and what does it leave to you?</strong> Do not assume the shared state is thread-safe because the docs talk about agents instead of threads. Verify the concurrency contract, and design your agents' writes around whatever that contract actually is.</p>
<p>If those opportunistic writes run concurrently, you have reintroduced, inside your agent layer, the exact class of defect that enterprise Java spent roughly two decades learning to avoid: race conditions, lost updates, stale reads, cross-thread visibility gaps, and output that changes depending on which agent happened to write first.</p>
<p>This is not a hypothetical. It is the problem the Java Memory Model was rewritten to specify, the reason <code>java.util.concurrent</code> exists, and the motivation behind a generation of guidance on immutability, defensive copying, and happens-before reasoning. A senior Java engineer reading this already has scar tissue for it. The danger of the agentic framing is that the scar tissue does not automatically transfer: a "shared blackboard between agents" sounds like an AI architecture concept, and it is easy to forget that underneath it is the same <code>HashMap</code>-shaped hazard that has been failing code reviews since 2005.</p>
<p>A blackboard with no defined memory semantics under concurrent writers is a flaky test waiting to happen, and it will get blamed on model quality when the cause is concurrency you never guarded.</p>
<h2>Auditability and versioning are not optional here</h2>
<p>There is a second cost, and it follows directly from the first virtue. Because contribution order is emergent, you cannot reconstruct <em>why the system produced a given output</em> by reading the code. There is no fixed route to trace. The only place the explanation lives is in the history of the blackboard: which agent wrote what, in which order, in response to what prior state.</p>
<p>That makes auditability a structural requirement, not a nice-to-have you add later. A blackboard headed for production needs every write attributable to a specific agent, timestamped, and ideally versioned, so you can replay the sequence of contributions that led to an answer. This is the same lesson event sourcing taught enterprise Java: when state is mutable and many writers touch it, the durable asset is the log of changes, not the current value. Keep only the final blackboard state and your post-incident analysis of "why did the agents converge on this wrong answer" becomes archaeology without a record.</p>
<p>Treat the blackboard as an append-friendly, inspectable ledger of contributions, and you can debug it. Treat it as a mutable scratchpad, and you have built a system whose behavior you cannot explain to the person who has to operate it at 3 a.m.</p>
<h2>The two release notes that change the math</h2>
<p>Two other 1.16.0 additions are worth reading next to the blackboard, because they widen exactly the surface this post is about.</p>
<p>The first is the new <code>AgentConfigurator</code>, which the release describes as a way to <em>define an external function for agents retrieval or creation</em>. Listed on its own it reads like an ergonomics improvement. Read against the blackboard it is more pointed: opportunistic coordination sometimes needs an agent that was never pre-registered, summoned based on what is currently on the board. Dynamic creation and retrieval of agents is how you do that. It is also how you end up with writers to your shared state that did not exist at compile time and are not on any static roster. Powerful, and a direct expansion of the concurrency surface you just took responsibility for.</p>
<p>The second is about the Model Context Protocol: <em>surface tool outputSchema on ToolSpecification.</em> Tools exposed through MCP now advertise the schema of their output, not just their inputs. For a blackboard system this lands squarely on the auditability problem. If tool results are going to be written onto shared state, a declared output schema lets you validate and reason about what gets written, rather than parsing free-form text after the fact. Typed writes are auditable writes, and on a blackboard, auditability is the whole game.</p>
<p>The release also tightens state hygiene in a smaller way worth noting: output guardrail failures can now remove the violating <code>AiMessage</code> from memory, so a rejected contribution does not linger to poison later reads. On a board where everyone reads everyone, keeping bad writes out of the shared record is the same discipline as everything above, applied to memory.</p>
<h2>Where this leaves you</h2>
<p>The blackboard pattern is a genuine tool for a genuine class of problem, and putting it in a mainstream JVM library means Java teams no longer have to hand-roll opportunistic coordination to get it.</p>
<p>It is not a free promotion from the supervisor, though. Adopting it means re-taking on the concurrency reasoning and the auditability engineering that the JVM ecosystem already knows how to do, now applied to a layer where the abstractions are agents and the hazards are still threads. The teams that get value from it will be the ones who treat the blackboard as exactly what it is: shared mutable state, to be guarded, versioned, and logged with the same seriousness they would give any other piece of concurrent state in production.</p>
<p>So the move is unromantic. Reach for the blackboard when the routing is truly emergent and you have a record-keeping plan for the board. Keep your sequence, parallel, and supervisor workflows for everything whose graph you can still draw. The 1.16.0 changelog gave you a new shape of coordination. Whether it is an upgrade depends entirely on whether your problem actually has the shape the blackboard is for.</p>
]]></content:encoded>
	</item>
</channel>
</rss>
