0
# State Management
1
2
Comprehensive state management system with channels, serialization, and update strategies. Handles complex state updates with merge functions, conflict resolution, and schema-based validation.
3
4
## Capabilities
5
6
### AgentState Base Class
7
8
Foundation for all graph state with key-value storage and update mechanisms.
9
10
```java { .api }
11
/**
12
* Base class for agent state with map-based data storage
13
* @param initData Initial state data as key-value map
14
*/
15
class AgentState {
16
AgentState(Map<String, Object> initData);
17
18
/**
19
* Returns unmodifiable view of state data
20
* @return Immutable map of current state
21
*/
22
Map<String, Object> data();
23
24
/**
25
* Retrieves typed value by key
26
* @param key State key to retrieve
27
* @return Optional containing value if present
28
*/
29
<T> Optional<T> value(String key);
30
}
31
```
32
33
**Usage Examples:**
34
35
```java
36
// Define custom state class
37
class MyAgentState extends AgentState {
38
public MyAgentState(Map<String, Object> initData) {
39
super(initData);
40
}
41
}
42
43
// Create and use state
44
Map<String, Object> initialData = Map.of(
45
"messages", new ArrayList<String>(),
46
"score", 0,
47
"active", true
48
);
49
50
MyAgentState state = new MyAgentState(initialData);
51
52
// Access state values
53
Optional<Integer> score = state.value("score");
54
Optional<List<String>> messages = state.value("messages");
55
Optional<Boolean> active = state.value("active");
56
57
// Get raw data
58
Map<String, Object> allData = state.data();
59
```
60
61
### State Update Operations
62
63
Static methods for updating state with channel-based merge strategies.
64
65
```java { .api }
66
/**
67
* Updates state with partial state using channels for merge logic
68
* @param state Current state map
69
* @param partialState State updates to apply
70
* @param channels Channel definitions for merge behavior
71
* @return New state map with updates applied
72
* @throws NullPointerException if state is null
73
*/
74
static Map<String, Object> updateState(Map<String, Object> state, Map<String, Object> partialState, Map<String, Channel<?>> channels);
75
76
/**
77
* Updates AgentState with partial state using channels
78
* @param state Current AgentState instance
79
* @param partialState State updates to apply
80
* @param channels Channel definitions for merge behavior
81
* @return New state map with updates applied
82
* @throws NullPointerException if state is null
83
*/
84
static Map<String, Object> updateState(AgentState state, Map<String, Object> partialState, Map<String, Channel<?>> channels);
85
```
86
87
**Usage Examples:**
88
89
```java
90
// Simple state update
91
Map<String, Object> currentState = Map.of("counter", 5, "name", "Alice");
92
Map<String, Object> updates = Map.of("counter", 10, "active", true);
93
Map<String, Object> newState = AgentState.updateState(currentState, updates, Map.of());
94
// Result: {counter=10, name=Alice, active=true}
95
96
// Update with channels for merge behavior
97
Map<String, Channel<?>> channels = Map.of(
98
"messages", Channels.appender(ArrayList::new),
99
"counter", (key, current, new_val) -> (Integer) current + (Integer) new_val
100
);
101
102
Map<String, Object> stateWithMessages = Map.of(
103
"messages", new ArrayList<>(List.of("Hello")),
104
"counter", 5
105
);
106
107
Map<String, Object> updatesWithMessages = Map.of(
108
"messages", List.of("World"),
109
"counter", 3
110
);
111
112
Map<String, Object> merged = AgentState.updateState(stateWithMessages, updatesWithMessages, channels);
113
// messages: ["Hello", "World"], counter: 8
114
```
115
116
### Special State Markers
117
118
Control state value lifecycle with special marker objects.
119
120
```java { .api }
121
// Constants for state control
122
static final Object MARK_FOR_REMOVAL; // Removes key from state
123
static final Object MARK_FOR_RESET; // Resets key to null/default
124
```
125
126
**Usage Examples:**
127
128
```java
129
// Remove state keys
130
Map<String, Object> updates = Map.of(
131
"temporary_data", AgentState.MARK_FOR_REMOVAL,
132
"cache", AgentState.MARK_FOR_RESET,
133
"new_value", "updated"
134
);
135
136
Map<String, Object> cleaned = AgentState.updateState(currentState, updates, Map.of());
137
// temporary_data is completely removed, cache is set to null
138
```
139
140
### Channel System
141
142
Define how state values are updated and merged during graph execution.
143
144
```java { .api }
145
/**
146
* Interface defining state value update behavior
147
* @param <T> Type of values handled by this channel
148
*/
149
interface Channel<T> {
150
/**
151
* Updates a state value using channel-specific merge logic
152
* @param key State key being updated
153
* @param currentValue Current value (may be null)
154
* @param newValue New value to merge
155
* @return Updated value after applying channel logic
156
*/
157
Object update(String key, Object currentValue, Object newValue);
158
}
159
```
160
161
**Channel Implementations:**
162
163
```java { .api }
164
// Utility class for creating standard channels
165
class Channels {
166
/**
167
* Creates appender channel for collections
168
* @param factory Supplier creating empty collection instances
169
* @return Channel that appends new values to collections
170
*/
171
static <C extends Collection<Object>> Channel<C> appender(Supplier<C> factory);
172
}
173
174
// Appender channel for list-like state values
175
class AppenderChannel<C extends Collection<Object>> implements Channel<C> {
176
AppenderChannel(Supplier<C> factory);
177
Object update(String key, Object currentValue, Object newValue);
178
}
179
```
180
181
**Usage Examples:**
182
183
```java
184
import org.bsc.langgraph4j.state.Channels;
185
186
// Define schema with channels
187
Map<String, Channel<?>> schema = Map.of(
188
// Append new messages to existing list
189
"messages", Channels.appender(ArrayList::new),
190
191
// Custom counter channel that adds values
192
"counter", (key, current, newVal) -> {
193
int currentInt = (current != null) ? (Integer) current : 0;
194
int newInt = (Integer) newVal;
195
return currentInt + newInt;
196
},
197
198
// Last-writer-wins channel (default behavior)
199
"status", (key, current, newVal) -> newVal
200
);
201
202
// Use schema in StateGraph
203
StateGraph<MyAgentState> workflow = new StateGraph<>(schema, MyAgentState::new);
204
```
205
206
### State Factory Pattern
207
208
Define how state instances are created and initialized.
209
210
```java { .api }
211
/**
212
* Factory interface for creating agent state instances
213
* @param <State> Type of agent state to create
214
*/
215
@FunctionalInterface
216
interface AgentStateFactory<State extends AgentState> extends Function<Map<String, Object>, State> {
217
/**
218
* Creates state instance from data map
219
* @param data Initial state data
220
* @return New agent state instance
221
*/
222
State apply(Map<String, Object> data);
223
224
/**
225
* Creates initial data from schema channels
226
* @param channels Channel schema defining state structure
227
* @return Map with initial values based on schema
228
*/
229
default Map<String, Object> initialDataFromSchema(Map<String, Channel<?>> channels) {
230
// Default implementation returns empty map
231
return Map.of();
232
}
233
}
234
```
235
236
**Usage Examples:**
237
238
```java
239
// Simple factory using constructor reference
240
AgentStateFactory<MyAgentState> factory = MyAgentState::new;
241
242
// Custom factory with default values
243
AgentStateFactory<MyAgentState> factoryWithDefaults = (data) -> {
244
Map<String, Object> defaultData = Map.of(
245
"messages", new ArrayList<String>(),
246
"score", 0,
247
"created", System.currentTimeMillis()
248
);
249
250
// Merge provided data with defaults
251
Map<String, Object> merged = new HashMap<>(defaultData);
252
merged.putAll(data);
253
254
return new MyAgentState(merged);
255
};
256
257
// Factory with schema-based initialization
258
AgentStateFactory<MyAgentState> schemaFactory = new AgentStateFactory<MyAgentState>() {
259
@Override
260
public MyAgentState apply(Map<String, Object> data) {
261
return new MyAgentState(data);
262
}
263
264
@Override
265
public Map<String, Object> initialDataFromSchema(Map<String, Channel<?>> channels) {
266
Map<String, Object> initial = new HashMap<>();
267
268
// Initialize collections for appender channels
269
for (Map.Entry<String, Channel<?>> entry : channels.entrySet()) {
270
if (entry.getValue() instanceof AppenderChannel) {
271
initial.put(entry.getKey(), new ArrayList<>());
272
}
273
}
274
275
return initial;
276
}
277
};
278
```
279
280
### State Serialization
281
282
Control how state is serialized for checkpoints and persistence.
283
284
```java { .api }
285
/**
286
* Interface for state serialization and cloning
287
* @param <State> Type of agent state
288
*/
289
interface StateSerializer<State extends AgentState> {
290
/**
291
* Get the state factory used by this serializer
292
* @return State factory for creating instances
293
*/
294
AgentStateFactory<State> stateFactory();
295
296
/**
297
* Clone state object for immutability and persistence
298
* @param data State data to clone
299
* @return Cloned state instance
300
* @throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException
301
*/
302
State cloneObject(Map<String, Object> data) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException;
303
}
304
```
305
306
**Serializer Implementations:**
307
308
```java { .api }
309
// Java object stream serialization
310
class ObjectStreamStateSerializer<State extends AgentState> implements StateSerializer<State> {
311
ObjectStreamStateSerializer(AgentStateFactory<State> stateFactory);
312
AgentStateFactory<State> stateFactory();
313
State cloneObject(Map<String, Object> data) throws IOException, ClassNotFoundException;
314
}
315
316
// JSON-based serialization with Jackson
317
class JacksonStateSerializer<State extends AgentState> extends PlainTextStateSerializer<State> {
318
JacksonStateSerializer(AgentStateFactory<State> stateFactory);
319
JacksonStateSerializer(AgentStateFactory<State> stateFactory, ObjectMapper objectMapper);
320
}
321
322
// JSON-based serialization with Gson
323
class GsonStateSerializer<State extends AgentState> extends PlainTextStateSerializer<State> {
324
GsonStateSerializer(AgentStateFactory<State> stateFactory);
325
GsonStateSerializer(AgentStateFactory<State> stateFactory, Gson gson);
326
}
327
```
328
329
**Usage Examples:**
330
331
```java
332
// Object stream serializer
333
StateSerializer<MyAgentState> objectSerializer =
334
new ObjectStreamStateSerializer<>(MyAgentState::new);
335
336
// Jackson JSON serializer
337
ObjectMapper mapper = new ObjectMapper();
338
StateSerializer<MyAgentState> jacksonSerializer =
339
new JacksonStateSerializer<>(MyAgentState::new, mapper);
340
341
// Gson JSON serializer
342
Gson gson = new GsonBuilder().create();
343
StateSerializer<MyAgentState> gsonSerializer =
344
new GsonStateSerializer<>(MyAgentState::new, gson);
345
346
// Use in StateGraph
347
StateGraph<MyAgentState> workflow = new StateGraph<>(schema, jacksonSerializer);
348
```
349
350
### State Snapshots
351
352
Immutable state representations with execution context.
353
354
```java { .api }
355
/**
356
* Immutable snapshot of graph state at specific execution point
357
*/
358
class StateSnapshot<State extends AgentState> implements NodeOutput<State> {
359
/**
360
* Creates state snapshot from checkpoint and configuration
361
* @param checkpoint Checkpoint data
362
* @param config Runtime configuration
363
* @param stateFactory Factory for creating state instances
364
* @return State snapshot instance
365
*/
366
static <State extends AgentState> StateSnapshot<State> of(Checkpoint checkpoint, RunnableConfig config, AgentStateFactory<State> stateFactory);
367
368
// NodeOutput interface methods
369
String node();
370
State state();
371
372
// Snapshot-specific methods
373
String getCheckpointId();
374
String getNodeId();
375
String getNextNodeId();
376
RunnableConfig getConfig();
377
}
378
```
379
380
**Usage Examples:**
381
382
```java
383
// Access snapshot information
384
StateSnapshot<MyAgentState> snapshot = app.getState(config);
385
386
System.out.println("Execution at node: " + snapshot.getNodeId());
387
System.out.println("Next node will be: " + snapshot.getNextNodeId());
388
System.out.println("Checkpoint ID: " + snapshot.getCheckpointId());
389
390
// Access state data
391
MyAgentState currentState = snapshot.state();
392
Map<String, Object> stateData = currentState.data();
393
394
// Get execution context
395
RunnableConfig executionConfig = snapshot.getConfig();
396
```
397
398
## Advanced State Patterns
399
400
### Reducer Pattern
401
402
Implement custom merge logic for complex state updates.
403
404
```java { .api }
405
interface Reducer<T> {
406
T reduce(T current, T newValue);
407
}
408
409
// Custom channel with reducer
410
Channel<Integer> sumChannel = (key, current, newVal) -> {
411
int currentInt = (current != null) ? (Integer) current : 0;
412
return currentInt + (Integer) newVal;
413
};
414
415
Channel<Set<String>> setUnionChannel = (key, current, newVal) -> {
416
Set<String> currentSet = (current != null) ? (Set<String>) current : new HashSet<>();
417
Set<String> result = new HashSet<>(currentSet);
418
if (newVal instanceof Collection) {
419
result.addAll((Collection<String>) newVal);
420
} else {
421
result.add((String) newVal);
422
}
423
return result;
424
};
425
```
426
427
### Conditional State Updates
428
429
Implement state updates with conditional logic.
430
431
```java { .api }
432
// Conditional update channel
433
Channel<String> conditionalStatus = (key, current, newVal) -> {
434
String newStatus = (String) newVal;
435
String currentStatus = (String) current;
436
437
// Only update if new status has higher priority
438
Map<String, Integer> priorities = Map.of(
439
"pending", 1,
440
"processing", 2,
441
"completed", 3,
442
"failed", 4
443
);
444
445
int currentPriority = priorities.getOrDefault(currentStatus, 0);
446
int newPriority = priorities.getOrDefault(newStatus, 0);
447
448
return (newPriority >= currentPriority) ? newStatus : currentStatus;
449
};
450
```
451
452
### State Validation
453
454
Validate state updates during channel processing.
455
456
```java { .api }
457
// Validating channel
458
Channel<Integer> validatedCounter = (key, current, newVal) -> {
459
Integer newValue = (Integer) newVal;
460
if (newValue < 0) {
461
throw new IllegalArgumentException("Counter cannot be negative");
462
}
463
return newValue;
464
};
465
466
// Range-bound channel
467
Channel<Double> boundedValue = (key, current, newVal) -> {
468
Double value = (Double) newVal;
469
return Math.max(0.0, Math.min(100.0, value)); // Clamp to [0, 100]
470
};
471
```