Files
Shahin Saadati f9ae6d937c Add Kotlin snippet for VertexAiSessionService (#2102)
* Add Kotlin snippet for VertexAiSessionService

The VertexAiSessionService section showed Python, Go and Java. Kotlin gained
the service in adk-kotlin 0.7.0, so add a Kotlin tab and Kotlin to that
section's language-support badge.

Kotlin addresses the reasoning engine differently from every sibling tab on
the page, so the snippet says so at the point of use:

- The engine is fixed at construction via `reasoningEngineId`. The 0.7.0 KDoc
  is explicit that, unlike the Python and Java ADK, `SessionKey.appName` is
  never parsed to derive the engine -- it is only a label. The Python tab
  above passes the engine through `app_name` on each call.
- `reasoningEngineId` must be the bare numeric id. The constructor rejects a
  full resource name outright (`require(reasoningEngineId.all { it.isDigit() })`),
  while the Python tab passes
  `projects/.../locations/.../reasoningEngines/...`.

A reader copying the adjacent Python idiom would therefore fail twice over.

Written inline to match the two existing Kotlin tabs on this page. Inline
snippets never reach Gradle, so this one was additionally compiled against the
0.7.0 pin in a scratch file that is not part of the commit.

* Address review: JVM-only note, wire the service to a Runner

Five fixes from self-review against the 0.7.0 sources and upstream's own
VertexAiSessionServiceExample.kt, which I should have consulted before writing
the first version:

- State that the service is JVM-only. It lives in core/src/jvmMain, so it does
  not exist on Android. Kotlin is the only language on this page where that
  distinction applies, so if the Kotlin tab omits it, nothing carries it.
- Show the service actually being used. The snippet stopped at an uncalled
  `suspend fun`; it now hands the service to an InMemoryRunner, which is what
  the section is about and what the upstream example does.
- Use `runBlocking` in a `main`, matching upstream, instead of a suspend
  function nothing calls.
- Drop `state = mapOf(...)`. It defaults to null, upstream omits it, and the
  Java tab explicitly notes no initial state is needed, so it introduced a
  concept the sibling tabs deliberately avoid.
- Widen the comparison from "the Python and Java tabs" to all the other tabs.
  The KDoc phrasing names Python and Java, but this page also has a Go tab.

The added LlmAgent needs an explicit `model`; the first draft would not have
compiled without it, which the scratch compile caught.

* Trim the snippet back to parity with the sibling tabs

The previous revision added an LlmAgent, a Gemini model and an InMemoryRunner,
taking the tab to 30 code lines against Python's 5, Go's 8 and Java's 12. No
sibling tab on this page constructs a runner or an agent, and this section's
prose never mentions one -- it is a characteristics list, not a wiring guide.

That change came from misapplying a review finding. On sessions/memory the
equivalent note was right: the prose there says "instantiating the
VertexAiMemoryBankService and passing it to the Runner" and the Python tab
shows exactly that. Neither holds here, so the runner was answering a question
this page does not ask.

Now scoped like the Java tab, the closest analogue: construct the service, then
create one session. That is still enough to demonstrate both divergences -- the
engine pinned at construction as a bare numeric id, and appName being only a
label -- since showing the second requires a SessionKey.

`runBlocking` stays, because createSession is a suspend function; it is the
direct counterpart of the Java tab's `.blockingGet()`, and the comment now says
so. Recompiled against the 0.7.0 pin in a scratch file.

* docs: update experimental annotation placement and refine context caching documentation note
2026-08-17 14:56:57 -07:00

19 KiB
Raw Permalink Blame History

Session: Tracking individual conversations

Supported in ADKPython v0.1.0TypeScript v0.2.0Go v0.1.0Java v0.1.0Kotlin v0.1.0

A Session represents a single conversation thread between a user and your agent. Just like you wouldn't start every text message from scratch, agents need context regarding the ongoing interaction. The Session object in ADK is designed specifically to track and manage these individual conversation threads.

Session objects

When a user starts interacting with your agent, the SessionService creates a Session object (google.adk.sessions.Session). This object acts as the container holding everything related to that one specific chat thread. Here are its key properties:

  • Identification (id, appName, userId): Unique labels for the conversation.
    • id: A unique identifier for this specific conversation thread, essential for retrieving it later. A SessionService object can handle multiple Session(s). This field identifies which particular session object are we referring to. For example, "test_id_modification".
    • app_name: Identifies which agent application this conversation belongs to. For example, "id_modifier_workflow".
    • userId: Links the conversation to a particular user.
  • History (events): A chronological sequence of all interactions (Event objects – user messages, agent responses, tool actions) that have occurred within this specific thread.
  • Session State (state): A place to store temporary data relevant only to this specific, ongoing conversation. This acts as a scratchpad for the agent during the interaction. We will cover how to use and manage state in detail in the next section.
  • Activity Tracking (lastUpdateTime): A timestamp indicating the last time an event occurred in this conversation thread.

Example: Examining session properties

The following code example demonstrates how to list various values stored in a session object:

=== "Python"

```py
from google.adk.sessions import InMemorySessionService, Session

# Create a simple session to examine its properties
temp_service = InMemorySessionService()
example_session = await temp_service.create_session(
    app_name="my_app",
    user_id="example_user",
    state={"initial_key": "initial_value"} # State can be initialized
)

print(f"--- Examining Session Properties ---")
print(f"ID (`id`):                {example_session.id}")
print(f"Application Name (`app_name`): {example_session.app_name}")
print(f"User ID (`user_id`):         {example_session.user_id}")
print(f"State (`state`):           {example_session.state}") # Note: Only shows initial state here
print(f"Events (`events`):         {example_session.events}") # Initially empty
print(f"Last Update (`last_update_time`): {example_session.last_update_time:.2f}")
print(f"---------------------------------")

# Clean up (optional for this example)
await temp_service.delete_session(app_name=example_session.app_name,
                            user_id=example_session.user_id, session_id=example_session.id)
print("The final status of temp_service - ", temp_service)
```

=== "TypeScript"

```typescript
import { InMemorySessionService } from "@google/adk";

// Create a simple session to examine its properties
const tempService = new InMemorySessionService();
const exampleSession = await tempService.createSession({
    appName: "my_app",
    userId: "example_user",
    state: {"initial_key": "initial_value"} // State can be initialized
});

console.log("--- Examining Session Properties ---");
console.log(`ID ('id'):                ${exampleSession.id}`);
console.log(`Application Name ('appName'): ${exampleSession.appName}`);
console.log(`User ID ('userId'):         ${exampleSession.userId}`);
console.log(`State ('state'):           ${JSON.stringify(exampleSession.state)}`); // Note: Only shows initial state here
console.log(`Events ('events'):         ${JSON.stringify(exampleSession.events)}`); // Initially empty
console.log(`Last Update ('lastUpdateTime'): ${exampleSession.lastUpdateTime}`);
console.log("---------------------------------");

// Clean up (optional for this example)
const finalStatus = await tempService.deleteSession({
    appName: exampleSession.appName,
    userId: exampleSession.userId,
    sessionId: exampleSession.id
});
console.log("The final status of temp_service - ", finalStatus);
```

=== "Go"

```go
--8<-- "examples/go/snippets/sessions/session_management_example/session_management_example.go:examine_session"
```

=== "Java"

```java
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;

String sessionId = "123";
String appName = "example-app"; // Example app name
String userId = "example-user"; // Example user id
ConcurrentMap<String, Object> initialState = new ConcurrentHashMap<>(Map.of("newKey", "newValue"));
InMemorySessionService exampleSessionService = new InMemorySessionService();

// Create Session
Session exampleSession = exampleSessionService.createSession(
    appName, userId, initialState, Optional.of(sessionId)).blockingGet();
System.out.println("Session created successfully.");

System.out.println("--- Examining Session Properties ---");
System.out.printf("ID (`id`): %s%n", exampleSession.id());
System.out.printf("Application Name (`appName`): %s%n", exampleSession.appName());
System.out.printf("User ID (`userId`): %s%n", exampleSession.userId());
System.out.printf("State (`state`): %s%n", exampleSession.state());
System.out.println("------------------------------------");


// Clean up (optional for this example)
var unused = exampleSessionService.deleteSession(appName, userId, sessionId);
```

=== "Kotlin"

```kotlin
import com.google.adk.kt.sessions.InMemorySessionService
import com.google.adk.kt.sessions.SessionKey

val sessionId = "123"
val appName = "example-app"
val userId = "example-user"
val initialState = mapOf("newKey" to "newValue")
val sessionService = InMemorySessionService()

// Create Session
val exampleSession = sessionService.createSession(
    key = SessionKey(appName, userId, sessionId),
    state = initialState
)
println("Session created successfully.")

println("--- Examining Session Properties ---")
println("ID (`id`):                ${exampleSession.key.id}")
println("Application Name (`appName`): ${exampleSession.key.appName}")
println("User ID (`userId`):         ${exampleSession.key.userId}")
println("State (`state`):           ${exampleSession.state}")
println("------------------------------------")

// Clean up (optional for this example)
sessionService.deleteSession(exampleSession.key)
```

(Note: The state shown above is only the initial state. State updates happen via events, as discussed in the State section.)

Session lifecycle

Session lifecycle

Here’s a simplified flow of how Session and SessionService work together during a conversation turn:

  1. Start or Resume: Your application needs to use the SessionService to either create_session (for a new chat) or use an existing session id.
  2. Context Provided: The Runner gets the appropriate Session object from the appropriate service method, providing the agent with access to the corresponding Session's state and events.
  3. Agent Processing: The user prompts the agent with a query. The agent analyzes the query and potentially the session state and events history to determine the response.
  4. Response & State Update: The agent generates a response (and potentially flags data to be updated in the state). The Runner packages this as an Event.
  5. Save Interaction: The Runner calls sessionService.append_event(session, event) with the session and the new event as the arguments. The service adds the Event to the history and updates the session's state in storage based on information within the event. The session's last_update_time also get updated.
  6. Ready for Next: The agent's response goes to the user. The updated Session is now stored by the SessionService, ready for the next turn (which restarts the cycle at step 1, usually with the continuation of the conversation in the current session).
  7. End Conversation: When the conversation is over, your application calls sessionService.delete_session(...) to clean up the stored session data if it is no longer required.

This cycle highlights how the SessionService ensures conversational continuity by managing the history and state associated with each Session object.

Managing sessions with a SessionService

As seen above, you don't typically create or manage Session objects directly. Instead, you use a SessionService. This service acts as the central manager responsible for the entire lifecycle of your conversation sessions.

Its core responsibilities include:

  • Starting New Conversations: Creating fresh Session objects when a user begins an interaction.
  • Resuming Existing Conversations: Retrieving a specific Session (using its ID) so the agent can continue where it left off.
  • Saving Progress: Appending new interactions (Event objects) to a session's history. This is also the mechanism through which session state gets updated (more in the State section).
  • Listing Conversations: Finding the active session threads for a particular user and application.
  • Cleaning Up: Deleting Session objects and their associated data when conversations are finished or no longer needed.

SessionService implementations

ADK provides different SessionService implementations, allowing you to choose the storage backend that best suits your needs:

InMemorySessionService

  • How it works: Stores all session data directly in the application's memory.
  • Persistence: None. All conversation data is lost if the application restarts.
  • Requires: Nothing extra.
  • Best for: Quick development, local testing, examples, and scenarios where long-term persistence isn't required.

=== "Python"

```py
from google.adk.sessions import InMemorySessionService
session_service = InMemorySessionService()
```

=== "TypeScript"

```typescript
import { InMemorySessionService } from "@google/adk";
const sessionService = new InMemorySessionService();
```

=== "Go"

```go
import "google.golang.org/adk/v2/session"
inMemoryService := session.InMemoryService()
```

=== "Java"

```java
import com.google.adk.sessions.InMemorySessionService;
InMemorySessionService exampleSessionService = new InMemorySessionService();
```

=== "Kotlin"

```kotlin
import com.google.adk.kt.sessions.InMemorySessionService
val sessionService = InMemorySessionService()
```

VertexAiSessionService

Supported in ADKPython v0.1.0Go v0.1.0Java v0.1.0Kotlin v0.7.0
  • How it works: Uses Google Cloud Agent Platform infrastructure via API calls for session management.
  • Persistence: Yes. Data is managed reliably and scalably via Agent Runtime.
  • Requires:
    • A Google Cloud project.
    • The gcp extra, installed with pip install google-adk[gcp].
    • A Google Cloud storage bucket that can be configured by this step.
    • An Agent Runtime resource name/ID that can setup following this tutorial.
    • If you do not have a Google Cloud project and you want to try the VertexAiSessionService, see Agent Platform Express Mode.
  • Best for: Scalable production applications deployed on Google Cloud, especially when integrating with other Agent Platform features.

=== "Python"

```py
# Requires: pip install google-adk[gcp]
# Plus GCP setup and authentication
from google.adk.sessions import VertexAiSessionService

PROJECT_ID = "your-gcp-project-id"
LOCATION = "us-central1"
# The app_name used with this service should be the Reasoning Engine ID or name
REASONING_ENGINE_APP_NAME = "projects/your-gcp-project-id/locations/us-central1/reasoningEngines/your-engine-id"

session_service = VertexAiSessionService(project=PROJECT_ID, location=LOCATION)
# Use REASONING_ENGINE_APP_NAME when calling service methods, e.g.:
# session = await session_service.create_session(app_name=REASONING_ENGINE_APP_NAME, ...)
```

=== "Go"

```go
import "google.golang.org/adk/v2/session"

// 2. VertexAIService
// Before running, ensure your environment is authenticated:
// gcloud auth application-default login
// export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
// export GOOGLE_CLOUD_LOCATION="your-gcp-location"

modelName := "gemini-flash-latest" // Replace with your desired model
vertexService, err := session.VertexAIService(ctx, modelName)
if err != nil {
  log.Printf("Could not initialize VertexAIService (this is expected if the gcloud project is not set): %v", err)
} else {
  fmt.Println("Successfully initialized VertexAIService.")
}
```

=== "Java"

```java
// Please look at the set of requirements above, consequently export the following in your bashrc file:
// export GOOGLE_CLOUD_PROJECT=my_gcp_project
// export GOOGLE_CLOUD_LOCATION=us-central1
// export GOOGLE_API_KEY=my_api_key

import com.google.adk.sessions.VertexAiSessionService;
import java.util.UUID;

String sessionId = UUID.randomUUID().toString();
String reasoningEngineAppName = "123456789";
String userId = "u_123"; // Example user id
ConcurrentMap<String, Object> initialState = new
    ConcurrentHashMap<>(); // No initial state needed for this example

VertexAiSessionService sessionService = new VertexAiSessionService();
Session mySession =
    sessionService
        .createSession(reasoningEngineAppName, userId, initialState, Optional.of(sessionId))
        .blockingGet();
```

=== "Kotlin"

`VertexAiSessionService` is JVM-only in ADK Kotlin. It is not available on
Android; use it from a server-side agent.

```kotlin
import com.google.adk.kt.sessions.SessionKey
import com.google.adk.kt.sessions.VertexAiSessionService
import kotlinx.coroutines.runBlocking

// The reasoning engine is pinned here, at construction. In the other tabs
// the engine is chosen per call, through `app_name`; in Kotlin `appName`
// is never parsed for it and is only a label on the session.
val sessionService =
    VertexAiSessionService(
        project = "your-gcp-project-id",
        location = "us-central1",
        // The bare numeric engine id. A full
        // "projects/.../reasoningEngines/..." resource name is rejected;
        // project and location are separate arguments.
        reasoningEngineId = "1234567890",
    )

// Session methods are suspend functions; `runBlocking` here is the
// counterpart of the Java tab's `.blockingGet()`.
val mySession = runBlocking {
    // A null id lets the service assign one.
    sessionService.createSession(SessionKey("example-app", "u_123", id = null))
}
```

For more information on connecting to Google Cloud from ADK agents, see Connect to Google Cloud and Agent Platform.

DatabaseSessionService

Supported in ADKPython v0.1.0Go v0.1.0
  • How it works: Connects to a relational database (e.g., PostgreSQL, MySQL, SQLite) to store session data persistently in tables.
  • Persistence: Yes. Data survives application restarts.
  • Requires: A configured database and the db extra, installed with pip install google-adk[db].
  • Best for: Applications needing reliable, persistent storage that you manage yourself.
from google.adk.sessions import DatabaseSessionService
# Example using a local SQLite file:
# Note: The implementation requires an async database driver.
# For SQLite, use 'sqlite+aiosqlite' instead of 'sqlite' to ensure async compatibility.
db_url = "sqlite+aiosqlite:///./my_agent_data.db"
session_service = DatabaseSessionService(db_url=db_url)

Concurrency and locking

The DatabaseSessionService ensures data integrity during concurrent operations through a two-tiered locking architecture:

  • In-Process locking: The service uses an internal, in-process lock to serialize append_event calls for the same session. This prevents race conditions when multiple requests try to update the same session simultaneously within the same process.
  • Row-Level locking: For PostgreSQL, MySQL, and MariaDB, the service uses row-level locking (via SELECT ... FOR UPDATE) to prevent race conditions when multiple processes or replicas try to update the same session simultaneously.

!!! warning "Async driver requirement"

`DatabaseSessionService` requires an async database driver. When using
SQLite, you must use `sqlite+aiosqlite` instead of `sqlite` in your
connection string. For other databases (PostgreSQL, MySQL), ensure you're
using an async-compatible driver, such as `asyncpg` for PostgreSQL,
`aiomysql` for MySQL.

!!! note "Session database schema change in ADK Python v1.22.0"

The schema for the session database changed in ADK Python v1.22.0, which
requires migration of the Session Database. For more information, see
[Session database schema migration](/sessions/session/migrate/).

Troubleshoot session errors

During execution, ADK can raise specific exceptions to help you identify configuration or state issues.

SessionNotFoundError

Raised when a runner attempts to access or execute a session that does not exist in the active session store. Inherits from ValueError for backward compatibility.

  • Common causes: an invalid, expired, or missing session_id; running a session before it has been created.
  • How to resolve: ensure the session exists first via create_session(...), or construct the Runner with auto_create_session=True.