Imported from eldermoraes/quarkus-agentic-scaffolding (
AGENTS.md). Install upstream withnpx skills add eldermoraes/quarkus-agentic-scaffolding. Copyright stays with the author.
Quarkus + LangChain4j + AI Stack - Project Conventions
Version: 0.23.4
These conventions apply whenever Codex or Bob writes, reviews, or configures code in a Quarkus +
LangChain4j project. They are always-on. Procedural scaffolding steps and starter code live in
the scaffold-project skill and its templates, not here.
1. Required tooling (mandatory)
These tools are prerequisites for this project, not suggestions. Do not work around their absence: if a required tool is unavailable, stop and report it rather than falling back to model memory or a generic web search.
- Quarkus Agents MCP - required for every Quarkus task. Project creation, extension selection,
configuration, version checks, API usage, and troubleshooting MUST go through the Quarkus Agents
MCP; never create a Quarkus project, add an extension, or answer a Quarkus question from model
memory by hand. Before any Quarkus task, VERIFY the MCP is reachable - its
quarkus_*tools are present and a cheap call (e.g.quarkus_status) succeeds. If the tools are absent or the call fails, STOP immediately: report exactly what is missing, point the user to/setup-agentic-scaffolding(and to restarting the session after registering it, since MCPs load at session start), and end the turn. A missing or unreachable MCP is never permission to proceed manually - do not fall back to the Quarkus CLI, Maven/Gradle archetypes, model memory, or web search, do not offer to "continue without it", and do not treat the stop as optional. The only exception is/setup-agentic-scaffolding, whose job is to install it. - context7 - required for external library and framework documentation. Before relying on
memory or web search for any library or framework API - LangChain4j included - you MUST look it
up with
context7first. - superpowers skills - use whenever applicable. Invoke the relevant
superpowersskill capabilities for the task at hand.
2. Java conventions
- Java 25 is the minimum language level, not a ceiling. Compile with
maven.compiler.releaseset to at least 25 and adopt newer language levels freely. Document any project that must pin an older level and explain why (see section 6). One cap applies to native targets: GraalVM ships no releases for JDK 26, 27, or 28, so native-image stays on the JDK 25 baseline (with quarterly updates) until JDK 29 lands (September 2027) - projects that build a native binary keepmaven.compiler.releaseat 25 until then (GraalVM release calendar). - Default to Virtual Threads for I/O-bound and blocking concurrent work. Platform threads are acceptable only when the runtime or a critical dependency forbids virtual threads (for example, a JDBC driver that pins the carrier). When a blocking AI or tool call must run inside a reactive endpoint, run it on a virtual thread rather than on the event loop.
- Use Scoped Values in place of
ThreadLocalfor request- or agent-scoped identity that must survive virtual-thread continuations, avoiding the leakage and inheritance pitfalls ofThreadLocal. - Structured concurrency for related subtasks. For fan-out across related concurrent
subtasks, prefer declarative parallelism (LangChain4j
@ParallelAgent/@ParallelMapperAgent, see section 4) or explicit virtual-thread fan-out (Thread.startVirtualThread(...)or anExecutors.newVirtualThreadPerTaskExecutor()), instead of ad-hoc executor coordination.StructuredTaskScopeis the preferred structured-concurrency primitive where the project can enable it; note it is a Java preview feature (requires--enable-preview) with GraalVM native-image considerations, so adopt it only when the preview flag and the native target allow. - Prefer records, sealed types, and pattern matching where they clarify intent. Use records
for DTOs and value objects (they also minimize the GraalVM reflection surface), sealed
interfaces for closed hierarchies such as event or result types, and pattern-matching
switchover those hierarchies so the compiler enforces exhaustiveness.
3. Quarkus conventions
- Import the platform BOMs; do not pin extension versions. Import
quarkus-bomandquarkus-langchain4j-bomat the same platform version and let the BOMs manage every extension and LangChain4j version. - CDI-first. Use
quarkus-arcand standard CDI (@ApplicationScoped,@Inject,@Produces) for wiring. Produce framework objects (retrieval augmentors, memory providers, embedding stores) from@ApplicationScopedproducer beans. - REST and API surface. Use
quarkus-rest(Quarkus REST) withquarkus-rest-jacksonfor JSON (Jackson is the Quarkus default serializer), and exposequarkus-smallrye-openapiso endpoints are documented and explorable. - Errors leave the REST edge as RFC 9457 problem details. Add
quarkus-http-problem(io.quarkiverse.httpproblem) and every exception escaping a resource becomes anapplication/problem+jsonresponse instead of a raw 500 with a stack trace - which matters here because an unhandled model timeout, a dead inference endpoint, or a throwing tool would otherwise leak prompts and internal names to the caller; the server still logs the failure in full. It needs no configuration: thequarkus.http-problem.*keys only tune it, andinclude-detailsstaysfalseso parser failures do not echo internal class names. This covers the REST edge only - the WebSockets Next streaming path keeps its own@OnErrorhandling (section 4) - and it complements rather than replaces the declarative fault tolerance in section 4, which handles failure inside the service. The extension is in the platform BOM from Quarkus 3.38.0, so it takes no version pin. - Streaming uses WebSockets Next. For token or progress streaming, use
quarkus-websockets-nextrather than rolling a custom transport (see section 4 for the streaming pattern). - Observability comes from platform extensions, not code. Add
quarkus-micrometer-registry-prometheus(metrics, scraped at/q/metrics) andquarkus-opentelemetry(traces) and AI services are instrumented automatically: per-method timers and counters (langchain4j.aiservices.*), GenAI-semconv token usage (gen_ai.client.token.usage, tagged by operation and token type), one span per AI-service call (langchain4j.aiservices.<Interface>.<method>) and per tool call (langchain4j.tools.<tool>). Register a CDICostEstimatorbean (io.quarkiverse.langchain4j.cost) to emitgen_ai.client.estimated_cost. Prompt and completion text reaches spans only when explicitly enabled (quarkus.langchain4j.tracing.include-prompt/.include-completion) - treat those as dev-only and scope them with%dev., since they record user content. - Enable parameter-name retention. Configure the compiler with
-parameters(Maven:<parameters>true</parameters>), which REST and AI-service binding rely on. - Build for both JVM and native. Keep a
nativeMaven profile so the project can produce a GraalVM native binary alongside the JVM build, and gate native integration tests in that profile. Native builds compile against the GraalVM JDK 25 line until JDK 29 (September 2027) - see section 2 - so a project with a native profile does not raise the language level above 25. - Disable Dev Services when an external model endpoint is configured. When the project points
at a real Ollama endpoint (local or cloud), disable LangChain4j Dev Services
(
quarkus.langchain4j.devservices.enabled=false) so a container is not started implicitly.
4. LangChain4j conventions
- Declarative AI services are the default. Define AI services as CDI-managed interfaces
annotated with
@RegisterAiService(the Quarkus form of LangChain4j's declarative service), using@SystemMessage/@UserMessagefor prompts and@MemoryIdfor per-conversation memory. Prefer this over manualChatModelwiring unless there is a documented reason. - Tools are CDI beans. Expose actions to a model with
@Toolmethods on@ApplicationScopedbeans, wired via@RegisterAiService(tools = ...)or@ToolBox- never hand-rolled JSON function dispatch. Tool methods doing I/O follow the section 2 virtual-thread rules. - Multi-agent workflows are composed declaratively. Build agentic workflows from
@RegisterAiServiceagents annotated with@Agent(name, description, outputKey)and orchestrate them with the LangChain4j Agentic annotations -@SequenceAgent,@ParallelAgent,@ParallelMapperAgent, and@SupervisorAgent(+@SupervisorRequest) - assembling results with@Outputover theAgenticScope. Use thequarkus-langchain4j-agenticextension. Avoid hand-rolled executor or coordination glue between AI services. - Structured output via typed return values. Have services return records or enums to get
structured results, and set
temperature=0for classification and other deterministic tasks. - Name and right-size models. Configure models by name (
@RegisterAiService(modelName = "...")on services,@ModelName("...")on injected models) and use a small, fast, low-temperature model for cheap subtasks (classification, query rewriting) and a larger model for the primary task. - Streaming pattern: reactive only at the edge. Stream over
quarkus-websockets-next(@WebSocket,@OnTextMessagereturning a MutinyMulti,@OnError). Keep the agent and engine logic free of reactive types: have the WebSocket delegate to an@ApplicationScopedorchestrator that runs the blocking pipeline on a virtual thread (Multi.createFrom().emitter(...)+Thread.startVirtualThread(...)) and emits progress. Mutiny appears only at the channel edge, never inside the engine. - Guardrails wrap AI services declaratively. Validate prompts/responses with
@InputGuardrails/@OutputGuardrailsbeans implementing the upstreamdev.langchain4j.guardrailinterfaces (the Quarkus-specific guardrail API was retired in favor of upstream); tune retries withquarkus.langchain4j.guardrails.max-retries. - Externally originated free text is data, never instructions. Free text the application did
not author itself - end-user input, inbound email or ticket bodies, webhook payloads, text
relayed from an upstream system - is interpolated into a prompt only inside explicit delimiters
(
<ticket>...</ticket>), with the system message stating that the delimited span is data to process and never instructions to follow, and every entry method that receives it carries@InputGuardrails. Downstream services reading only model-produced state need no guardrail, but still delimit values derived from that text. - Fault tolerance is declarative on AI-service methods. With
quarkus-smallrye-fault-tolerance, put MicroProfile@Timeout,@Retry, and@Fallback(org.eclipse.microprofile.faulttolerance) directly on@RegisterAiServicemethods, with the fallback as adefaultmethod on the same interface - never hand-rolled try/retry loops around AI calls. Size@Timeoutgenerously on tool-calling methods: a single invocation may span several model/tool round-trips before it returns. - Reusable instructions ship as skills, not as prompt strings. When behavior would otherwise
be pasted into an ever-growing
@SystemMessage, put it in aSKILL.md(YAML front matter withnameanddescription, instructions in the body), pointquarkus.langchain4j.skills.directoriesat the folder, and annotate the service or method with@Skills(io.quarkiverse.langchain4j.skills, extensionquarkus-langchain4j-skills). The extension registers anactivate_skilltool and a system message advertising what is available, so the model pulls in a skill's instructions only when they apply - the prompt stays small and each skill stays independently editable. Narrow the surface with@Skills("name")rather than exposing everything. The extension isstatus:preview: pin the behavior you depend on with a test, and expect its API to move. - RAG starts simple with Easy RAG. For retrieval-augmented generation, start with the
quarkus-langchain4j-easy-ragextension plus an in-process embedding model: pointquarkus.langchain4j.easy-rag.pathat a documents folder and let it ingest on startup. Move to a hand-builtRetrievalAugmentor(a CDI-producedEmbeddingStore+EmbeddingStoreContentRetriever) only when a project needs control Easy RAG does not provide. - Enable request/response logging in dev. Set
%dev.quarkus.langchain4j.log-requests=trueand%dev.quarkus.langchain4j.log-responses=trueso prompts and model output are observable during development without recording user content in production.
5. Testing
No test suite is mandated, so treat this as the intended baseline rather than an observed standard. Apply it when adding tests:
- Use
@QuarkusTest(artifactio.quarkus:quarkus-junit) for integration-style tests andio.rest-assured:rest-assuredto exercise HTTP endpoints. - Run native integration tests through
maven-failsafe-plugininside thenativeprofile. - Keep model interactions deterministic in tests (
temperature=0, fixed prompts) or mock the model so tests do not depend on live inference. - Grade model quality with the evaluation framework
(
quarkus-langchain4j-testing-evaluation-junit5+ semantic-similarity / AI-judge strategies) rather than brittle string asserts; keep the scaffolded@QuarkusTestwiring smoke test green without a live model.
6. Scope and overrides
These conventions apply to projects in this Quarkus + LangChain4j stack. A per-project addition or override is allowed when justified - for example, pinning a fixed older Java version, choosing platform threads for a pinning dependency, or selecting a different model provider - and must be documented inline near the override so the deviation and its reason stay visible.
When maintaining eldermoraes/quarkus-agentic-scaffolding itself, apply the tooling requirements
per operation as described in CONTRIBUTING.md (Required tooling). An unavailable MCP blocks
only work that needs that tool; independent repository review, documentation, CI inspection,
and approved merges may continue. This override applies only to maintenance of the scaffolding
repository; applications using its conventions retain the requirements in section 1.