LangChain4j Agentic Framework provides a comprehensive Java library for building multi-agent AI systems with support for workflow orchestration, supervisor agents, planning-based execution, declarative configuration, agent-to-agent communication, and human-in-the-loop workflows.
Complete API reference for monitoring and tracking agent execution through listeners that observe agent lifecycle, tool execution, and agentic scope events.
import dev.langchain4j.agentic.observability.*;
class SimpleListener implements AgentListener {
@Override
public void beforeAgentInvocation(AgentRequest request) {
System.out.println("Starting: " + request.agentName());
}
@Override
public void afterAgentInvocation(AgentResponse response) {
System.out.println("Completed: " + response.agentName() +
" in " + response.duration().toMillis() + "ms");
}
}
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.listener(new SimpleListener())
.build();Central interface for observing all agent execution events.
/**
* Listener for agent execution events
* All methods are optional (default implementations provided)
*/
interface AgentListener {
/**
* Called before agent invocation
* @param agentRequest Request information including name, type, arguments, and scope
*/
default void beforeAgentInvocation(AgentRequest agentRequest) {
// Override to handle before invocation
}
/**
* Called after agent invocation completes successfully
* @param agentResponse Response information including name, result, scope, and duration
*/
default void afterAgentInvocation(AgentResponse agentResponse) {
// Override to handle after invocation
}
/**
* Called when agent invocation fails
* @param agentInvocationError Error information including name, error, and scope
*/
default void onAgentInvocationError(AgentInvocationError agentInvocationError) {
// Override to handle errors
}
/**
* Called after AgenticScope is created
* @param agenticScope Created scope with memory ID
*/
default void afterAgenticScopeCreated(AgenticScope agenticScope) {
// Override to handle scope creation
}
/**
* Called before AgenticScope is destroyed
* @param agenticScope Scope being destroyed with final state
*/
default void beforeAgenticScopeDestroyed(AgenticScope agenticScope) {
// Override to handle scope destruction
}
/**
* Called before tool execution
* @param beforeToolExecution Tool execution details including name and arguments
*/
default void beforeToolExecution(BeforeToolExecution beforeToolExecution) {
// Override to handle before tool execution
}
/**
* Called after tool execution completes
* @param toolExecution Tool execution details including name, result, error, and duration
*/
default void afterToolExecution(ToolExecution toolExecution) {
// Override to handle after tool execution
}
/**
* Whether listener is inherited by sub-agents
* @return true if inherited, false otherwise (default: false)
*/
default boolean inheritedBySubagents() {
return false;
}
}Usage Examples:
import dev.langchain4j.agentic.observability.*;
class ComprehensiveListener implements AgentListener {
@Override
public void beforeAgentInvocation(AgentRequest request) {
System.out.println("=== Agent Starting ===");
System.out.println("Agent: " + request.agentName());
System.out.println("Type: " + request.agentType().getSimpleName());
System.out.println("Arguments: " + request.arguments());
}
@Override
public void afterAgentInvocation(AgentResponse response) {
System.out.println("=== Agent Completed ===");
System.out.println("Agent: " + response.agentName());
System.out.println("Result: " + response.result());
System.out.println("Duration: " + response.duration().toMillis() + "ms");
}
@Override
public void onAgentInvocationError(AgentInvocationError error) {
System.err.println("=== Agent Error ===");
System.err.println("Agent: " + error.agentName());
System.err.println("Error: " + error.error().getMessage());
error.error().printStackTrace();
}
@Override
public void afterAgenticScopeCreated(AgenticScope scope) {
System.out.println("AgenticScope created for memory ID: " + scope.memoryId());
}
@Override
public void beforeAgenticScopeDestroyed(AgenticScope scope) {
System.out.println("AgenticScope destroyed. Final state: " + scope.state());
System.out.println("Total invocations: " + scope.agentInvocations().size());
}
@Override
public void beforeToolExecution(BeforeToolExecution beforeExec) {
System.out.println("Executing tool: " + beforeExec.toolName());
System.out.println("Arguments: " + beforeExec.arguments());
}
@Override
public void afterToolExecution(ToolExecution toolExec) {
System.out.println("Tool completed: " + toolExec.toolName() +
" in " + toolExec.duration().toMillis() + "ms");
if (toolExec.error() != null) {
System.err.println("Tool error: " + toolExec.error().getMessage());
} else {
System.out.println("Tool result: " + toolExec.result());
}
}
@Override
public boolean inheritedBySubagents() {
return true; // Propagate to sub-agents
}
}
// Use the listener
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.listener(new ComprehensiveListener())
.build();Information provided before agent invocation.
/**
* Request information for agent invocation
*/
record AgentRequest(
String agentName,
Class<?> agentType,
Map<String, Object> arguments,
AgenticScope agenticScope
) {}Usage Examples:
class DetailedRequestListener implements AgentListener {
@Override
public void beforeAgentInvocation(AgentRequest request) {
// Access request details
String name = request.agentName();
Class<?> type = request.agentType();
Map<String, Object> args = request.arguments();
AgenticScope scope = request.agenticScope();
// Log request details
System.out.println(String.format(
"Invoking agent '%s' of type %s with args: %s",
name,
type.getSimpleName(),
args
));
// Access scope state
System.out.println("Current scope state: " + scope.state());
System.out.println("Memory ID: " + scope.memoryId());
// Track invocation in scope
int count = scope.readState("invocation_count", 0);
scope.writeState("invocation_count", count + 1);
scope.writeState("last_agent", name);
}
}Information provided after successful agent invocation.
/**
* Response information from agent invocation
*/
record AgentResponse(
String agentName,
Class<?> agentType,
Object result,
AgenticScope agenticScope,
Duration duration
) {}Usage Examples:
class PerformanceTrackingListener implements AgentListener {
private final Map<String, List<Long>> executionTimes = new ConcurrentHashMap<>();
@Override
public void afterAgentInvocation(AgentResponse response) {
// Access response details
String name = response.agentName();
Object result = response.result();
Duration duration = response.duration();
AgenticScope scope = response.agenticScope();
// Track execution time
executionTimes.computeIfAbsent(name, k -> new ArrayList<>())
.add(duration.toMillis());
// Log performance
System.out.println(String.format(
"Agent '%s' completed in %dms with result: %s",
name,
duration.toMillis(),
result
));
// Store metrics in scope
scope.writeState("last_duration_ms", duration.toMillis());
scope.writeState("last_result", result);
// Calculate and log average
List<Long> times = executionTimes.get(name);
double average = times.stream().mapToLong(Long::longValue).average().orElse(0);
System.out.println(String.format(
"Average execution time for %s: %.2fms",
name,
average
));
// Alert on slow execution
if (duration.toMillis() > 5000) {
System.err.println("WARNING: Slow agent execution detected!");
}
}
}Information provided when agent invocation fails.
/**
* Error information from agent invocation
*/
record AgentInvocationError(
String agentName,
Class<?> agentType,
Throwable error,
AgenticScope agenticScope
) {}Usage Examples:
class ErrorTrackingListener implements AgentListener {
private final Map<String, Integer> errorCounts = new ConcurrentHashMap<>();
private final Map<String, List<String>> errorMessages = new ConcurrentHashMap<>();
@Override
public void onAgentInvocationError(AgentInvocationError invocationError) {
// Access error details
String name = invocationError.agentName();
Class<?> type = invocationError.agentType();
Throwable error = invocationError.error();
AgenticScope scope = invocationError.agenticScope();
// Track error count
errorCounts.merge(name, 1, Integer::sum);
// Track error messages
errorMessages.computeIfAbsent(name, k -> new ArrayList<>())
.add(error.getMessage());
// Log error with context
System.err.println(String.format(
"Agent '%s' (type: %s) failed with error: %s",
name,
type.getSimpleName(),
error.getMessage()
));
error.printStackTrace();
// Store error in scope for recovery
scope.writeState("last_error", error.getMessage());
scope.writeState("last_error_agent", name);
scope.writeState("error_timestamp", System.currentTimeMillis());
// Alert if too many errors
int count = errorCounts.get(name);
if (count > 5) {
System.err.println(String.format(
"CRITICAL: Agent %s has failed %d times!",
name,
count
));
sendAlert(name, count);
}
// Log error pattern
if (count > 1) {
System.err.println("Recent errors for " + name + ":");
errorMessages.get(name).forEach(msg ->
System.err.println(" - " + msg)
);
}
}
private void sendAlert(String agentName, int errorCount) {
// Send alert to monitoring system
}
}Information provided before tool execution.
/**
* Information before tool execution
*/
record BeforeToolExecution(
String toolName,
Map<String, Object> arguments
) {}Usage Examples:
class ToolMonitoringListener implements AgentListener {
@Override
public void beforeToolExecution(BeforeToolExecution beforeExec) {
System.out.println("=== Tool Starting ===");
System.out.println("Tool: " + beforeExec.toolName());
System.out.println("Arguments: " + beforeExec.arguments());
// Log specific tools
if ("database_query".equals(beforeExec.toolName())) {
String query = (String) beforeExec.arguments().get("query");
System.out.println("Executing database query: " + query);
}
}
}Information provided after tool execution.
/**
* Information after tool execution
*/
record ToolExecution(
String toolName,
Map<String, Object> arguments,
Object result,
Throwable error,
Duration duration
) {}Usage Examples:
class ToolPerformanceListener implements AgentListener {
private final Map<String, Statistics> toolStats = new ConcurrentHashMap<>();
@Override
public void afterToolExecution(ToolExecution toolExec) {
System.out.println("=== Tool Completed ===");
System.out.println("Tool: " + toolExec.toolName());
System.out.println("Duration: " + toolExec.duration().toMillis() + "ms");
// Track statistics
toolStats.computeIfAbsent(toolExec.toolName(), k -> new Statistics())
.record(toolExec.duration().toMillis());
// Check for errors
if (toolExec.error() != null) {
System.err.println("Tool error: " + toolExec.error().getMessage());
toolExec.error().printStackTrace();
} else {
System.out.println("Tool result: " + toolExec.result());
}
// Alert on slow tools
if (toolExec.duration().toMillis() > 1000) {
System.err.println(String.format(
"SLOW TOOL: %s took %dms",
toolExec.toolName(),
toolExec.duration().toMillis()
));
}
// Log statistics periodically
Statistics stats = toolStats.get(toolExec.toolName());
if (stats.count() % 10 == 0) {
System.out.println(String.format(
"Tool %s statistics - Avg: %.2fms, Min: %dms, Max: %dms, Count: %d",
toolExec.toolName(),
stats.average(),
stats.min(),
stats.max(),
stats.count()
));
}
}
static class Statistics {
private long sum = 0;
private long min = Long.MAX_VALUE;
private long max = 0;
private int count = 0;
void record(long value) {
sum += value;
min = Math.min(min, value);
max = Math.max(max, value);
count++;
}
double average() { return count > 0 ? (double) sum / count : 0; }
long min() { return min != Long.MAX_VALUE ? min : 0; }
long max() { return max; }
int count() { return count; }
}
}Monitor when agentic scopes are created.
/**
* Called after AgenticScope created
* @param agenticScope Created scope
*/
default void afterAgenticScopeCreated(AgenticScope agenticScope) {
// Override to handle scope creation
}Usage Examples:
class ScopeLifecycleListener implements AgentListener {
@Override
public void afterAgenticScopeCreated(AgenticScope scope) {
System.out.println("=== Scope Created ===");
System.out.println("Memory ID: " + scope.memoryId());
// Initialize tracking state
scope.writeState("scope_created_at", System.currentTimeMillis());
scope.writeState("operation_count", 0);
scope.writeState("scope_id", UUID.randomUUID().toString());
// Log creation
System.out.println("Initialized scope with ID: " + scope.readState("scope_id"));
}
}Monitor when agentic scopes are destroyed to capture final metrics.
/**
* Called before AgenticScope destroyed
* @param agenticScope Scope being destroyed
*/
default void beforeAgenticScopeDestroyed(AgenticScope agenticScope) {
// Override to handle scope destruction
}Usage Examples:
class ScopeAnalyticsListener implements AgentListener {
@Override
public void beforeAgenticScopeDestroyed(AgenticScope scope) {
System.out.println("=== Scope Destroying ===");
// Log final state
Map<String, Object> finalState = scope.state();
System.out.println("Final state keys: " + finalState.keySet());
System.out.println("Final state: " + finalState);
// Calculate lifetime
Long createdAt = (Long) scope.readState("scope_created_at");
if (createdAt != null) {
long lifetime = System.currentTimeMillis() - createdAt;
System.out.println("Scope lifetime: " + lifetime + "ms");
}
// Log invocation summary
List<AgentInvocation> invocations = scope.agentInvocations();
System.out.println("Total agent invocations: " + invocations.size());
// Group by agent
Map<String, Long> invocationsByAgent = invocations.stream()
.collect(Collectors.groupingBy(
AgentInvocation::agentName,
Collectors.counting()
));
System.out.println("Invocations by agent: " + invocationsByAgent);
// Calculate success rate
long errors = finalState.keySet().stream()
.filter(k -> k.contains("error"))
.count();
double successRate = invocations.isEmpty() ? 100.0 :
((invocations.size() - errors) * 100.0 / invocations.size());
System.out.println("Success rate: " + String.format("%.2f%%", successRate));
}
}Compose multiple listeners into one.
/**
* Composes multiple listeners into one
* All composed listeners receive all events
*/
class ComposedAgentListener implements AgentListener {
private final List<AgentListener> listeners;
/**
* Create composed listener
* @param listeners Listeners to compose
*/
public ComposedAgentListener(AgentListener... listeners) {
this.listeners = Arrays.asList(listeners);
}
@Override
public void beforeAgentInvocation(AgentRequest agentRequest) {
listeners.forEach(listener -> listener.beforeAgentInvocation(agentRequest));
}
@Override
public void afterAgentInvocation(AgentResponse agentResponse) {
listeners.forEach(listener -> listener.afterAgentInvocation(agentResponse));
}
@Override
public void onAgentInvocationError(AgentInvocationError agentInvocationError) {
listeners.forEach(listener -> listener.onAgentInvocationError(agentInvocationError));
}
@Override
public void afterAgenticScopeCreated(AgenticScope agenticScope) {
listeners.forEach(listener -> listener.afterAgenticScopeCreated(agenticScope));
}
@Override
public void beforeAgenticScopeDestroyed(AgenticScope agenticScope) {
listeners.forEach(listener -> listener.beforeAgenticScopeDestroyed(agenticScope));
}
@Override
public void beforeToolExecution(BeforeToolExecution beforeToolExecution) {
listeners.forEach(listener -> listener.beforeToolExecution(beforeToolExecution));
}
@Override
public void afterToolExecution(ToolExecution toolExecution) {
listeners.forEach(listener -> listener.afterToolExecution(toolExecution));
}
@Override
public boolean inheritedBySubagents() {
return listeners.stream().anyMatch(AgentListener::inheritedBySubagents);
}
}Usage Examples:
// Create individual listeners
AgentListener loggingListener = new LoggingListener();
AgentListener metricsListener = new MetricsListener();
AgentListener errorListener = new ErrorTrackingListener();
// Compose them
AgentListener composedListener = new ComposedAgentListener(
loggingListener,
metricsListener,
errorListener
);
// Use composed listener
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.listener(composedListener)
.build();
// All three listeners will receive all events
Object result = agent.invoke("Process this");Control whether listeners are inherited by sub-agents.
/**
* Whether listener is inherited by sub-agents
* @return true if inherited, false otherwise (default: false)
*/
default boolean inheritedBySubagents() {
return false;
}Usage Examples:
// Listener that propagates to sub-agents
class GlobalListener implements AgentListener {
@Override
public void beforeAgentInvocation(AgentRequest request) {
System.out.println("Global: " + request.agentName() + " starting");
}
@Override
public void afterAgentInvocation(AgentResponse response) {
System.out.println("Global: " + response.agentName() + " completed");
}
@Override
public boolean inheritedBySubagents() {
return true; // This listener will be inherited by all sub-agents
}
}
// Listener only for parent agent
class ParentOnlyListener implements AgentListener {
@Override
public void beforeAgentInvocation(AgentRequest request) {
System.out.println("Parent only: " + request.agentName());
}
@Override
public boolean inheritedBySubagents() {
return false; // Sub-agents won't receive these events
}
}
// Use in workflow
UntypedAgent workflow = AgenticServices.sequenceBuilder()
.listener(new GlobalListener()) // Will propagate to sub-agents
.listener(new ParentOnlyListener()) // Won't propagate
.subAgents(agent1, agent2, agent3)
.build();
// When workflow executes:
// - GlobalListener receives events from workflow AND all sub-agents
// - ParentOnlyListener only receives events from workflow itselfTrack and analyze agent performance metrics.
class PerformanceMonitor implements AgentListener {
private final Map<String, Statistics> stats = new ConcurrentHashMap<>();
@Override
public void afterAgentInvocation(AgentResponse response) {
String agentName = response.agentName();
long durationMs = response.duration().toMillis();
stats.computeIfAbsent(agentName, k -> new Statistics())
.record(durationMs);
// Log if slow
if (durationMs > 1000) {
System.out.println(String.format(
"SLOW: Agent %s took %dms",
agentName,
durationMs
));
}
}
public void printReport() {
System.out.println("=== Performance Report ===");
stats.forEach((name, stat) -> {
System.out.println(String.format(
"Agent: %s\n" +
" Average: %.2fms\n" +
" Min: %dms\n" +
" Max: %dms\n" +
" Count: %d\n",
name,
stat.average(),
stat.min(),
stat.max(),
stat.count()
));
});
}
static class Statistics {
private long sum = 0;
private long min = Long.MAX_VALUE;
private long max = 0;
private int count = 0;
void record(long value) {
sum += value;
min = Math.min(min, value);
max = Math.max(max, value);
count++;
}
double average() { return count > 0 ? (double) sum / count : 0; }
long min() { return min != Long.MAX_VALUE ? min : 0; }
long max() { return max; }
int count() { return count; }
}
}
// Usage
PerformanceMonitor monitor = new PerformanceMonitor();
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.listener(monitor)
.build();
agent.invoke("Process this");
agent.invoke("Process that");
monitor.printReport();Create detailed audit trails of all agent activity.
class AuditLogger implements AgentListener {
private final PrintWriter auditLog;
public AuditLogger(String logFilePath) throws IOException {
this.auditLog = new PrintWriter(new FileWriter(logFilePath, true));
}
@Override
public void beforeAgentInvocation(AgentRequest request) {
auditLog.println(String.format(
"[%s] INVOKE | Agent: %s | Type: %s | Args: %s | MemoryId: %s",
Instant.now(),
request.agentName(),
request.agentType().getSimpleName(),
request.arguments(),
request.agenticScope().memoryId()
));
auditLog.flush();
}
@Override
public void afterAgentInvocation(AgentResponse response) {
auditLog.println(String.format(
"[%s] COMPLETE | Agent: %s | Duration: %dms | Result: %s",
Instant.now(),
response.agentName(),
response.duration().toMillis(),
truncate(String.valueOf(response.result()), 100)
));
auditLog.flush();
}
@Override
public void onAgentInvocationError(AgentInvocationError error) {
auditLog.println(String.format(
"[%s] ERROR | Agent: %s | Error: %s | StackTrace: %s",
Instant.now(),
error.agentName(),
error.error().getMessage(),
Arrays.toString(error.error().getStackTrace())
));
auditLog.flush();
}
@Override
public void beforeToolExecution(BeforeToolExecution beforeExec) {
auditLog.println(String.format(
"[%s] TOOL_START | Tool: %s | Args: %s",
Instant.now(),
beforeExec.toolName(),
beforeExec.arguments()
));
auditLog.flush();
}
@Override
public void afterToolExecution(ToolExecution toolExec) {
auditLog.println(String.format(
"[%s] TOOL_END | Tool: %s | Duration: %dms | Error: %s",
Instant.now(),
toolExec.toolName(),
toolExec.duration().toMillis(),
toolExec.error() != null ? toolExec.error().getMessage() : "none"
));
auditLog.flush();
}
public void close() {
auditLog.close();
}
private String truncate(String text, int maxLength) {
return text.length() > maxLength ?
text.substring(0, maxLength) + "..." : text;
}
}
// Usage
AuditLogger logger = new AuditLogger("/var/log/agents/audit.log");
try {
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.listener(logger)
.build();
agent.invoke("Process data");
} finally {
logger.close();
}Integrate with distributed tracing systems.
class DistributedTracingListener implements AgentListener {
private final Tracer tracer;
public DistributedTracingListener(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void beforeAgentInvocation(AgentRequest request) {
// Start span
Span span = tracer.buildSpan(request.agentName())
.withTag("agent.type", request.agentType().getSimpleName())
.withTag("memory.id", String.valueOf(request.agenticScope().memoryId()))
.start();
// Store span in scope for later retrieval
request.agenticScope().writeState("trace.span", span);
// Set as active span
tracer.scopeManager().activate(span);
}
@Override
public void afterAgentInvocation(AgentResponse response) {
// Retrieve and finish span
Span span = (Span) response.agenticScope().readState("trace.span");
if (span != null) {
span.setTag("agent.duration.ms", response.duration().toMillis());
span.setTag("agent.success", true);
span.finish();
}
}
@Override
public void onAgentInvocationError(AgentInvocationError error) {
// Mark span as error
Span span = (Span) error.agenticScope().readState("trace.span");
if (span != null) {
span.setTag("error", true);
span.log(Map.of(
"error.kind", error.error().getClass().getName(),
"error.message", error.error().getMessage(),
"error.stack", Arrays.toString(error.error().getStackTrace())
));
span.finish();
}
}
@Override
public void beforeToolExecution(BeforeToolExecution beforeExec) {
// Create child span for tool
Span parentSpan = (Span) tracer.activeSpan();
if (parentSpan != null) {
Span toolSpan = tracer.buildSpan("tool:" + beforeExec.toolName())
.asChildOf(parentSpan)
.withTag("tool.name", beforeExec.toolName())
.start();
// Store for later
parentSpan.setTag("current.tool.span", toolSpan);
}
}
@Override
public void afterToolExecution(ToolExecution toolExec) {
Span parentSpan = (Span) tracer.activeSpan();
if (parentSpan != null) {
Span toolSpan = (Span) parentSpan.getBaggageItem("current.tool.span");
if (toolSpan != null) {
toolSpan.setTag("tool.duration.ms", toolExec.duration().toMillis());
if (toolExec.error() != null) {
toolSpan.setTag("error", true);
toolSpan.log(Map.of("error.message", toolExec.error().getMessage()));
}
toolSpan.finish();
}
}
}
@Override
public boolean inheritedBySubagents() {
return true; // Trace sub-agents too
}
}Send notifications for critical events.
class NotificationListener implements AgentListener {
private final NotificationService notificationService;
public NotificationListener(NotificationService service) {
this.notificationService = service;
}
@Override
public void afterAgentInvocation(AgentResponse response) {
// Notify on long-running agents
if (response.duration().toSeconds() > 30) {
notificationService.send(
"Long Running Agent",
String.format(
"Agent %s took %d seconds to complete",
response.agentName(),
response.duration().toSeconds()
)
);
}
}
@Override
public void onAgentInvocationError(AgentInvocationError error) {
// Notify on errors
notificationService.send(
"Agent Error",
String.format(
"Agent %s failed: %s",
error.agentName(),
error.error().getMessage()
),
NotificationPriority.HIGH
);
}
@Override
public void beforeAgenticScopeDestroyed(AgenticScope scope) {
// Notify on completion
int invocations = scope.agentInvocations().size();
if (invocations > 20) {
notificationService.send(
"High Activity Detected",
String.format(
"Workflow completed with %d agent invocations",
invocations
)
);
}
}
}Validate state consistency and data integrity.
class StateValidationListener implements AgentListener {
private final List<String> requiredKeys;
public StateValidationListener(String... requiredKeys) {
this.requiredKeys = Arrays.asList(requiredKeys);
}
@Override
public void afterAgentInvocation(AgentResponse response) {
AgenticScope scope = response.agenticScope();
// Validate required state
for (String key : requiredKeys) {
if (!scope.hasState(key)) {
System.err.println(String.format(
"WARNING: Agent %s did not set required state key: %s",
response.agentName(),
key
));
}
}
// Validate state types
if (scope.hasState("user_id") && !(scope.readState("user_id") instanceof String)) {
System.err.println("ERROR: user_id must be a String");
}
if (scope.hasState("count") && !(scope.readState("count") instanceof Integer)) {
System.err.println("ERROR: count must be an Integer");
}
}
@Override
public void beforeAgenticScopeDestroyed(AgenticScope scope) {
// Final validation
Map<String, Object> state = scope.state();
// Check for null values
state.forEach((key, value) -> {
if (value == null) {
System.err.println("WARNING: Null value for key: " + key);
}
});
// Check for required keys at end
for (String key : requiredKeys) {
if (!scope.hasState(key)) {
System.err.println(String.format(
"ERROR: Required key %s not found in final state",
key
));
}
}
}
}Listeners should not perform heavy operations that slow down agent execution.
// Good - lightweight logging
class GoodListener implements AgentListener {
@Override
public void afterAgentInvocation(AgentResponse response) {
System.out.println(response.agentName() + " completed");
}
}
// Avoid - heavy computation in listener
class AvoidListener implements AgentListener {
@Override
public void afterAgentInvocation(AgentResponse response) {
// Don't do this - it blocks agent execution
performExpensiveAnalysis(response);
sendToExternalService(response);
}
}
// Better - offload to async processing
class BetterListener implements AgentListener {
private final ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
public void afterAgentInvocation(AgentResponse response) {
// Offload to background thread
executor.submit(() -> {
performExpensiveAnalysis(response);
sendToExternalService(response);
});
}
}Don't let listener exceptions break agent execution.
class SafeListener implements AgentListener {
@Override
public void afterAgentInvocation(AgentResponse response) {
try {
// Listener logic
processResponse(response);
} catch (Exception e) {
// Log but don't propagate
System.err.println("Listener error: " + e.getMessage());
e.printStackTrace();
}
}
}Only propagate listeners to sub-agents when necessary.
// Propagate for global monitoring
class GlobalMonitor implements AgentListener {
@Override
public boolean inheritedBySubagents() {
return true; // Monitor all agents
}
}
// Don't propagate for parent-specific logic
class ParentSpecificListener implements AgentListener {
@Override
public boolean inheritedBySubagents() {
return false; // Only parent agent
}
}Create focused listeners and compose them.
// Each listener has single responsibility
AgentListener logger = new LoggingListener();
AgentListener metrics = new MetricsListener();
AgentListener errors = new ErrorTrackingListener();
// Compose them
AgentListener composed = new ComposedAgentListener(logger, metrics, errors);
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.listener(composed)
.build();Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-agenticdocs
declarative
A2AClientAgent
ActivationCondition
Agent
ConditionalAgent
ErrorHandler
ExitCondition
HumanInTheLoop
HumanInTheLoopResponseSupplier
LoopAgent
LoopCounter
Output
ParallelAgent
ParallelExecutor
PlannerAgent
SequenceAgent
SupervisorAgent
SupervisorRequest
quick-start
workflows