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

agents-and-scope.mddocs/

Agents and the AgenticScope

What an agent is

An agent performs a specific task using an LLM. It is defined as an interface with a single method annotated with @Agent — essentially a normal LangChain4j AI service that can be composed with other agents.

public interface CreativeWriter {
    @UserMessage("""
        You are a creative writer.
        Generate a draft of a story no more than
        3 sentences long around the given topic.
        Return only the story and nothing else.
        The topic is {{topic}}.
        """)
    @Agent("Generates a story based on the given topic")
    String generateStory(@V("topic") String topic);
}

@Agent description and name

  • Description: provide a short purpose description, especially for pure-agentic patterns where other agents must understand this agent's capabilities to decide when to use it. Can also be set via the builder's .description(...).
  • Name: must uniquely identify the agent within the system. Set it in @Agent(name = ...), via .name(...), or let it default to the annotated method name.

@V argument binding

Arguments are not passed directly — their values are read from AgenticScope shared variables with the matching name. @V("topic") reads the topic variable. If the class is compiled with -parameters (parameter names retained at runtime), @V can be omitted and names are inferred.

Building an agent

CreativeWriter creativeWriter = AgenticServices
    .agentBuilder(CreativeWriter.class)
    .chatModel(myChatModel)
    .outputKey("story")
    .build();

The key difference from a plain AI service is outputKey: the name of the shared AgenticScope variable where the result is stored so other agents can read it. The output key can instead be declared on the annotation:

@Agent(outputKey = "story", description = "Generates a story based on the given topic")

Untyped agents

You can skip the typed interface entirely. AgenticServices.agentBuilder() (no class) returns an UntypedAgent:

public interface UntypedAgent {
    @Agent
    Object invoke(Map input);
}
UntypedAgent creativeWriter = AgenticServices.agentBuilder()
    .chatModel(BASE_MODEL)
    .description("Generate a story based on the given topic")
    .userMessage("""
        You are a creative writer.
        Generate a draft of a story no more than
        3 sentences long around the given topic.
        Return only the story and nothing else.
        The topic is {{topic}}.
        """)
    .inputKey(String.class, "topic")
    .returnType(String.class) // String is the default return type for untyped agents
    .outputKey("story")
    .build();

Values in the input Map are copied into the AgenticScope shared variables; the result is read back from the named outputKey.

The AgenticScope

The AgenticScope is the collection of data shared among agents in a system:

  • A shared variable store: an agent writes its result (to its outputKey) for others to read (via @V).
  • It auto-records the sequence of agent invocations and their responses.
  • It is created automatically when the root agent is invoked, and surfaced through callbacks when needed (e.g. output(...) functions, exitCondition predicates, error handlers, dynamic model functions).

Common read/write API used throughout the patterns:

agenticScope.readState("score", 0.0);   // read with default
agenticScope.writeState("topic", "dragons and wizards");
agenticScope.hasState("score");
agenticScope.state();                    // full Map view, e.g. state().keySet()
agenticScope.readState(Category.class);  // typed-key read (see declarative-api.md)

Composed systems are agents too

Any builder result (sequence, loop, supervisor, ...) is itself an agent and can be used as a sub-agent of another system. You may give the composed system its own typed interface for strongly typed invocation — see workflow-patterns.md for NovelCreator/StyledWriter examples.

docs

agent-configuration.md

agents-and-scope.md

custom-strategy-critic-result.md

declarative-api.md

demo-storylines.md

gotchas.md

index.md

mcp-and-a2a.md

pure-agentic.md

tools-memory-and-build.md

workflow-patterns.md

README.md

tile.json