Skip to content

Commit dd830aa

Browse files
damianmomotgooglecopybara-github
authored andcommitted
feat: support pausing and resuming a session invocation
When enabled via ResumabilityConfig, an invocation that issues a long-running function call pauses and can be resumed later from durable per-agent checkpoints for LlmAgent, SequentialAgent, LoopAgent, and ParallelAgent, via a runAsync overload that takes an invocation id. A resumed transfer continues at the transferred-to sub-agent; a call a previous run never executed is replayed rather than re-requested from the model; and resuming persists the incoming message and any state delta before running. Behavior is unchanged unless resumability is enabled. PiperOrigin-RevId: 973694952
1 parent 4092a1f commit dd830aa

28 files changed

Lines changed: 6027 additions & 377 deletions

‎core/src/main/java/com/google/adk/agents/BaseAgent.java‎

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616

1717
package com.google.adk.agents;
1818

19+
import static com.google.common.base.Preconditions.checkArgument;
1920
import static com.google.common.base.Strings.isNullOrEmpty;
2021
import static com.google.common.collect.ImmutableList.toImmutableList;
2122
import static java.lang.String.format;
2223

2324
import com.google.adk.agents.Callbacks.AfterAgentCallback;
2425
import com.google.adk.agents.Callbacks.BeforeAgentCallback;
2526
import com.google.adk.events.Event;
27+
import com.google.adk.events.EventActions;
2628
import com.google.adk.plugins.Plugin;
2729
import com.google.adk.telemetry.Instrumentation;
2830
import com.google.adk.telemetry.Instrumentation.AgentInvocation;
@@ -39,6 +41,7 @@
3941
import java.util.ArrayList;
4042
import java.util.HashSet;
4143
import java.util.List;
44+
import java.util.Map;
4245
import java.util.Optional;
4346
import java.util.function.Function;
4447
import java.util.regex.Pattern;
@@ -136,10 +139,8 @@ private static void validateAgentName(String name) {
136139
throw new IllegalArgumentException(
137140
format("Agent name '%s' does not match regex '%s'.", name, IDENTIFIER_REGEX));
138141
}
139-
if (name.equals(Role.USER)) {
140-
throw new IllegalArgumentException(
141-
"Agent name cannot be 'user'; reserved for end-user input.");
142-
}
142+
checkArgument(
143+
!name.equals(Role.USER), "Agent name cannot be 'user'; reserved for end-user input.");
143144
}
144145

145146
/**
@@ -468,6 +469,44 @@ public Flowable<Event> runLive(InvocationContext parentContext) {
468469
return run(parentContext, this::runLiveImpl);
469470
}
470471

472+
/**
473+
* Records this agent's end-of-agent checkpoint and returns it as a single-event stream. The
474+
* recorded state is cleared and the agent marked finished, so a later run skips it.
475+
*
476+
* @param context Current invocation context.
477+
* @return stream of the single {@code endOfAgent = true} checkpoint event.
478+
*/
479+
final Flowable<Event> endOfAgentAndRecord(InvocationContext context) {
480+
context.setAgentState(name(), /* agentState= */ null, /* endOfAgent= */ true);
481+
return Flowable.just(checkpointEvent(context, EventActions.builder().endOfAgent(true).build()));
482+
}
483+
484+
/**
485+
* Records {@code agentState} for this agent and returns the matching checkpoint event as a
486+
* single-event stream. The agent is left unfinished, so a later run resumes from this checkpoint.
487+
*
488+
* @param context Current invocation context.
489+
* @param agentState The serialized agent state to persist.
490+
* @return stream of the single checkpoint event carrying {@code agentState}.
491+
*/
492+
final Flowable<Event> checkpointAndRecord(
493+
InvocationContext context, Map<String, Object> agentState) {
494+
context.setAgentState(name(), agentState, /* endOfAgent= */ false);
495+
return Flowable.just(
496+
checkpointEvent(context, EventActions.builder().agentState(agentState).build()));
497+
}
498+
499+
/** Builds a resumability checkpoint event authored by this agent carrying {@code actions}. */
500+
private Event checkpointEvent(InvocationContext context, EventActions actions) {
501+
return Event.builder()
502+
.id(Event.generateEventId())
503+
.invocationId(context.invocationId())
504+
.author(name())
505+
.branch(context.branch().orElse(null))
506+
.actions(actions)
507+
.build();
508+
}
509+
471510
/**
472511
* Agent-specific asynchronous logic.
473512
*

0 commit comments

Comments
 (0)