Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/sessions/memory.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Memory: Long-Term Knowledge with `MemoryService`

![python_only](https://img.shields.io/badge/Currently_supported_in-Python-blue){ title="This feature is currently available for Python. Java support is planned/ coming soon."}
![python_only](https://img.shields.io/badge/Currently_supported_in-Python-blue){ title="This feature is currently available for Python. Java and TypeScript support is planned/ coming soon."}

We've seen how `Session` tracks the history (`events`) and temporary data (`state`) for a *single, ongoing conversation*. But what if an agent needs to recall information from *past* conversations or access external knowledge bases? This is where the concept of **Long-Term Knowledge** and the **`MemoryService`** come into play.

Expand Down
38 changes: 38 additions & 0 deletions docs/sessions/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,37 @@ are its key properties:
var unused = exampleSessionService.deleteSession(appName, userId, sessionId);
```

=== "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);
```

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

Expand Down Expand Up @@ -141,6 +172,13 @@ the storage backend that best suits your needs:
InMemorySessionService exampleSessionService = new InMemorySessionService();
```

=== "TypeScript"

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

2. **`VertexAiSessionService`**

* **How it works:** Uses Google Cloud Vertex AI infrastructure via API
Expand Down
194 changes: 181 additions & 13 deletions docs/sessions/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Conceptually, `session.state` is a collection (dictionary or Map) holding key-va

* Data is stored as `key: value`.
* **Keys:** Always strings (`str`). Use clear names (e.g., `'departure_city'`, `'user:language_preference'`).
* **Values:** Must be **serializable**. This means they can be easily saved and loaded by the `SessionService`. Stick to basic types in the specific languages (Python/ Java) like strings, numbers, booleans, and simple lists or dictionaries containing *only* these basic types. (See API documentation for precise details).
* **Values:** Must be **serializable**. This means they can be easily saved and loaded by the `SessionService`. Stick to basic types in the specific languages (Python/Java/TypeScript) like strings, numbers, booleans, and simple lists or dictionaries containing *only* these basic types. (See API documentation for precise details).
* **⚠️ Avoid Complex Objects:** **Do not store non-serializable objects** (custom class instances, functions, connections, etc.) directly in the state. Store simple identifiers if needed, and retrieve the complex object elsewhere.

2. **Mutability: It Changes**
Expand All @@ -31,7 +31,7 @@ Conceptually, `session.state` is a collection (dictionary or Map) holding key-va
* `DatabaseSessionService` / `VertexAiSessionService`: **Persistent.** State is saved reliably.

!!! Note
The specific parameters or method names for the primitives may vary slightly by SDK language (e.g., `session.state['current_intent'] = 'book_flight'` in Python, `session.state().put("current_intent", "book_flight)` in Java). Refer to the language-specific API documentation for details.
The specific parameters or method names for the primitives may vary slightly by SDK language (e.g., `session.state['current_intent'] = 'book_flight'` in Python, `session.state().put("current_intent", "book_flight")` in Java, or `context.state.set("current_intent", "book_flight")` in TypeScript). Refer to the language-specific API documentation for details.

### Organizing State with Prefixes: Scope Matters

Expand Down Expand Up @@ -81,19 +81,37 @@ To inject a value from the session state, enclose the key of the desired state v

**Example:**

```python
from google.adk.agents import LlmAgent
=== "Python"

```python
from google.adk.agents import LlmAgent

story_generator = LlmAgent(
name="StoryGenerator",
model="gemini-2.0-flash",
instruction="""Write a short story about a cat, focusing on the theme: {topic}."""
)

# Assuming session.state['topic'] is set to "friendship", the LLM
# will receive the following instruction:
# "Write a short story about a cat, focusing on the theme: friendship."
```

=== "TypeScript"

story_generator = LlmAgent(
name="StoryGenerator",
model="gemini-2.0-flash",
instruction="""Write a short story about a cat, focusing on the theme: {topic}."""
)
```typescript
import { LlmAgent } from "@google/adk";

# Assuming session.state['topic'] is set to "friendship", the LLM
# will receive the following instruction:
# "Write a short story about a cat, focusing on the theme: friendship."
```
const storyGenerator = new LlmAgent({
name: "StoryGenerator",
model: "gemini-2.5-flash",
instruction: "Write a short story about a cat, focusing on the theme: {topic}."
});

// Assuming session.state['topic'] is set to "friendship", the LLM
// will receive the following instruction:
// "Write a short story about a cat, focusing on the theme: friendship."
```

#### Important Considerations

Expand Down Expand Up @@ -128,6 +146,25 @@ The `InstructionProvider` function receives a `ReadonlyContext` object, which yo
)
```

=== "TypeScript"

```typescript
import { LlmAgent, ReadonlyContext } from "@google/adk";

// This is an InstructionProvider
function myInstructionProvider(context: ReadonlyContext): string {
// You can optionally use the context to build the instruction
// For this example, we'll return a static string with literal braces.
return "This is an instruction with {{literal_braces}} that will not be replaced.";
}

const agent = new LlmAgent({
model: "gemini-2.5-flash",
name: "template_helper_agent",
instruction: myInstructionProvider
});
```

If you want to both use an `InstructionProvider` *and* inject state into your instructions, you can use the `inject_session_state` utility function.

=== "Python"
Expand Down Expand Up @@ -223,6 +260,57 @@ This is the simplest method for saving an agent's final text response directly i
--8<-- "examples/java/snippets/src/main/java/state/GreetingAgentExample.java:full_code"
```

=== "TypeScript"

```typescript
import { LlmAgent, Runner, InMemorySessionService, isFinalResponse } from "@google/adk";
import { Content } from "@google/genai";

// Define agent with outputKey
const greetingAgent = new LlmAgent({
name: "Greeter",
model: "gemini-2.5-flash",
instruction: "Generate a short, friendly greeting.",
outputKey: "last_greeting" // Save response to state['last_greeting']
});

// --- Setup Runner and Session ---
const appName = "state_app";
const userId = "user1";
const sessionId = "session1";
const sessionService = new InMemorySessionService();
const runner = new Runner({
agent: greetingAgent,
appName: appName,
sessionService: sessionService
});
const session = await sessionService.createSession({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const session = await sessionService.createSession({ appName, userId, sessionId });

appName: appName,
userId: userId,
sessionId: sessionId
});
console.log(`Initial state: ${JSON.stringify(session.state)}`);

// --- Run the Agent ---
// Runner handles calling appendEvent, which uses the outputKey
// to automatically create the stateDelta.
const userMessage: Content = { parts: [{ text: "Hello" }] };
for await (const event of runner.run({
userId: userId,
sessionId: sessionId,
newMessage: userMessage
Comment on lines +298 to +301
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for await (const event of runner.run({ userId, sessionId, newMessage: userMessage }))

})) {
if (isFinalResponse(event)) {
console.log("Agent responded."); // Response text is also in event.content
}
}

// --- Check Updated State ---
const updatedSession = await sessionService.getSession({ appName: appName, userId: userId, sessionId: sessionId });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const updatedSession = await sessionService.getSession({ appName, userId, sessionId });

console.log(`State after agent run: ${JSON.stringify(updatedSession?.state)}`);
// Expected output might include: {"last_greeting":"Hello there! How can I help you today?"}
```

Behind the scenes, the `Runner` uses the `output_key` to create the necessary `EventActions` with a `state_delta` and calls `append_event`.

**2\. The Standard Way: `EventActions.state_delta` (for Complex Updates)**
Expand Down Expand Up @@ -287,6 +375,64 @@ For more complex scenarios (updating multiple keys, non-string values, specific
--8<-- "examples/java/snippets/src/main/java/state/ManualStateUpdateExample.java:full_code"
```

=== "TypeScript"

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

// --- Setup ---
const sessionService = new InMemorySessionService();
const appName = "state_app_manual";
const userId = "user2";
const sessionId = "session2";
const session = await sessionService.createSession({
appName: appName,
userId: userId,
sessionId: sessionId,
state: { "user:login_count": 0, "task_status": "idle" }
});
Comment on lines +388 to +393
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, we can skip the writing in the key: value style when key is the same as variable name

console.log(`Initial state: ${JSON.stringify(session.state)}`);

// --- Define State Changes ---
const currentTime = Date.now();
const stateChanges = {
"task_status": "active", // Update session state
"user:login_count": (session.state["user:login_count"] as number || 0) + 1, // Update user state
"user:last_login_ts": currentTime, // Add user state
"temp:validation_needed": true // Add temporary state (will be discarded)
};

// --- Create Event with Actions ---
const actionsWithUpdate: EventActions = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have createEventActions that do it for you but it is not exposed to the public API, do you think we need to expose it?

cc: @cornellgit

stateDelta: stateChanges,
artifactDelta: {},
requestedAuthConfigs: {},
requestedToolConfirmations: {},
};
// This event might represent an internal system action, not just an agent response
const systemEvent = createEvent({
invocationId: "inv_login_update",
author: "system", // Or 'agent', 'tool' etc.
actions: actionsWithUpdate,
timestamp: currentTime
// content might be null or represent the action taken
});

// --- Append the Event (This updates the state) ---
await sessionService.appendEvent({ session, event: systemEvent });
console.log("`appendEvent` called with explicit state delta.");

// --- Check Updated State ---
const updatedSession = await sessionService.getSession({
appName: appName,
userId: userId,
sessionId: sessionId
});
Comment on lines +426 to +430
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

console.log(`State after event: ${JSON.stringify(updatedSession?.state)}`);
// Expected: {"user:login_count":1,"task_status":"active","user:last_login_ts":<timestamp>}
// Note: 'temp:validation_needed' is NOT present.
```

**3. Via `CallbackContext` or `ToolContext` (Recommended for Callbacks and Tools)**

Modifying state within agent callbacks (e.g., `on_before_agent_call`, `on_after_agent_call`) or tool functions is best done using the `state` attribute of the `CallbackContext` or `ToolContext` provided to your function.
Expand Down Expand Up @@ -342,6 +488,28 @@ For more comprehensive details on context objects, refer to the [Context documen
}
```

=== "TypeScript"

```typescript
// In an agent callback or tool function
import { CallbackContext } from "@google/adk"; // or ToolContext

function myCallbackOrToolFunction(
context: CallbackContext, // Or ToolContext
// ... other parameters ...
) {
// Update existing state
const count = context.state.get("user_action_count", 0);
context.state.set("user_action_count", count + 1);

// Add new state
context.state.set("temp:last_operation_status", "success");

// State changes are automatically part of the event's stateDelta
// ... rest of callback/tool logic ...
}
```

**What `append_event` Does:**

* Adds the `Event` to `session.events`.
Expand Down