CtrlK
BlogDocsLog inGet started
Tessl Logo

gamussa/langchain4j-agentic

Build and demo Java AI agent systems with langchain4j-agentic: workflow patterns, supervisor, custom Planner strategies (incl. the flagship typed-verdict / CriticResult-style critic pattern), plus MCP tools, A2A remote agents, build setup, and conference-demo storylines. Pinned to 1.15.0 / 1.15.0-beta25.

84

4.76x
Quality

89%

Does it follow best practices?

Impact

100%

4.76x

Average score across 2 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

agentic-best-practices.mdrules/

LangChain4j Agentic — Best Practices

Apply these when writing code with the langchain4j-agentic module.

Module status & versioning

  • The module is experimental; APIs may change. Always pin an explicit version. Current (verify with scripts/check_versions.sh): core 1.15.0 (GA), langchain4j-agentic / -agentic-a2a / -agentic-patterns / -mcp 1.15.0-beta25.
  • The langchain4j-bom aligns stable modules only — it does not manage the beta agentic/mcp artifacts. Pin each beta independently; mixing a transitive beta with BOM-managed core causes NoSuchMethodError at runtime.
  • Custom patterns (goal-oriented, peer-to-peer, voting) come from langchain4j-agentic-patterns, not the core agentic module.

Build & tools (silent-failure guards)

  • Compile with -parameters (Gradle options.compilerArgs.add("-parameters") / Maven <parameters>true</parameters>). Without it, @V("name") binds null silently — no error, wrong output.
  • .maxSequentialToolsInvocations(int) is mandatory on any tool-using agent (default 10) to prevent runaway tool loops.
  • Tools use the standard @Tool/@P from langchain4j-core; there is no agentic-specific tool annotation. Use .tools(...) for static tools, .toolProviders(...) for dynamic/MCP tools.
  • Always register a shutdown hook calling mcpClient.close() when using stdio MCP transport.
  • Domain-model the objects that flow between agents where routing depends on them: prefer a typed result object (illustrated as CriticResult — your own type, generic or concrete) over a stringly-typed score so branch decisions are compile-checked.

Defining agents

  • Give every @Agent a meaningful description — it is what the supervisor and pure-agentic planners read to decide when to use the agent.
  • Give every agent a unique name (from @Agent(name=...), the builder .name(...), or the method name by default). Names must be unique within an agentic system.
  • Compile with -parameters so @V can be omitted, or annotate every argument with @V("..."). Do not rely on parameter names without -parameters.

Wiring through the AgenticScope

  • The outputKey of one agent and the @V argument names of the next must match exactly. A typo silently produces a MissingArgumentException at runtime.
  • Reusing the same outputKey across agents (e.g. "story") is the idiomatic way to refine a value through a chain — but it overwrites. Use distinct keys when you need both values later.
  • For non-trivial systems, prefer TypedKey + @K over string keys to get compile-time safety and avoid casts when reading state.

Choosing control flow

  • Default to deterministic workflows (sequence/loop/parallel/conditional). Reach for the supervisor (LLM-planned) only when the step order genuinely can't be known ahead of time.
  • The goal-oriented planner is a middle ground: deterministic graph search over agents' input/output dependencies. It cannot express loops — nest a loop workflow as a sub-agent if needed.
  • parallelMapper sub-agents must not use ChatMemory (the framework throws if they do), since the same agent runs repeatedly on different items.

Loops

  • Always set maxIterations to bound a loopBuilder. Combine with an exitCondition.
  • By default exitCondition is checked after every sub-agent invocation to minimize calls. Use testExitAtLoopEnd(true) only when all sub-agents must run each iteration.

Concurrency

  • Provide an explicit Executor for parallelBuilder/parallelMapperBuilder in production rather than relying on the default cached thread pool.
  • async(true) agents run off the calling thread; the scope blocks on their result only when a later agent needs it. AgentListener callbacks run on the invocation thread — keep them non-blocking.

Streaming

  • An agent returning TokenStream only streams to the caller when it is the last agent invoked; otherwise it behaves asynchronously and is fully consumed before the next agent runs.

Robustness & observability

  • Add an errorHandler returning ErrorRecoveryResult.retry() / .result(...) / .throwException() to recover from missing arguments or tool/agent failures instead of letting the whole system fail.
  • During development, register an AgentMonitor (or extend MonitoredAgent) and generate an HTML report with HtmlReportGenerator to inspect the invocation tree, timings, and token usage.
  • For the supervisor, set responseStrategy deliberately: LAST (default) returns the last agent's output, SUMMARY returns the planner's summary, SCORED lets a scorer agent pick. Use supervisorContext to inject policies/constraints.

README.md

tile.json