diff --git a/docs/runtime/event-loop.md b/docs/runtime/event-loop.md
index 3f41480e1d..c2c2efea7d 100644
--- a/docs/runtime/event-loop.md
+++ b/docs/runtime/event-loop.md
@@ -1,7 +1,7 @@
# Runtime Event Loop
@@ -151,6 +157,12 @@ execute function calls. CFC uses the Live API under the hood.
.build();
```
+=== "Kotlin"
+
+ ```kotlin
+ --8<-- "examples/kotlin/snippets/runtime/RunConfigExample.kt:streaming_config"
+ ```
+
## Configure audio and speech
diff --git a/examples/kotlin/snippets/runtime/RunConfigExample.kt b/examples/kotlin/snippets/runtime/RunConfigExample.kt
new file mode 100644
index 0000000000..e1f6192079
--- /dev/null
+++ b/examples/kotlin/snippets/runtime/RunConfigExample.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.adk.kt.examples.runtime
+
+import com.google.adk.kt.agents.LlmAgent
+import com.google.adk.kt.agents.ResumabilityConfig
+import com.google.adk.kt.agents.RunConfig
+import com.google.adk.kt.agents.StreamingMode
+import com.google.adk.kt.annotations.ExperimentalResumabilityFeature
+import com.google.adk.kt.models.Gemini
+import com.google.adk.kt.runners.InMemoryRunner
+import com.google.adk.kt.sessions.InMemorySessionService
+import com.google.adk.kt.types.Content
+import com.google.adk.kt.types.Role
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.runBlocking
+
+// --8<-- [start:basic_usage]
+val config =
+ RunConfig(
+ streamingMode = StreamingMode.SSE,
+ )
+
+// Pass it to runner.runAsync
+// runner.runAsync(..., runConfig = config)
+// --8<-- [end:basic_usage]
+
+// --8<-- [start:full_example]
+fun runWithConfig(runner: InMemoryRunner) =
+ runBlocking {
+ val config =
+ RunConfig(
+ streamingMode = StreamingMode.SSE,
+ customMetadata = mapOf("priority" to "high"),
+ )
+
+ runner
+ .runAsync(
+ userId = "user123",
+ sessionId = "session456",
+ newMessage = Content.fromText(Role.USER, "Hello!"),
+ runConfig = config,
+ ).collect { event ->
+ // handle events
+ }
+ }
+// --8<-- [end:full_example]
+
+// --8<-- [start:custom_metadata]
+val metadataConfig =
+ RunConfig(
+ customMetadata = mapOf("user_tier" to "premium"),
+ )
+// --8<-- [end:custom_metadata]
+
+// --8<-- [start:streaming_config]
+val streamingConfig =
+ RunConfig(
+ streamingMode = StreamingMode.SSE,
+ )
+// --8<-- [end:streaming_config]
+
+private val rootAgent =
+ LlmAgent(name = "my_agent", model = Gemini(name = "gemini-flash-latest"))
+
+// --8<-- [start:resumability_config]
+@OptIn(ExperimentalResumabilityFeature::class)
+val runner =
+ InMemoryRunner(
+ agent = rootAgent,
+ appName = "my_resumable_agent",
+ sessionService = InMemorySessionService(),
+ resumabilityConfig = ResumabilityConfig(isResumable = true),
+ )
+// --8<-- [end:resumability_config]
+
+// --8<-- [start:resume_usage]
+fun resumeAgent(runner: InMemoryRunner) =
+ runBlocking {
+ runner
+ .runAsync(
+ userId = "user123",
+ sessionId = "session456",
+ invocationId = "previous-invocation-id",
+ ).collect { event ->
+ // resume execution from previous state
+ }
+ }
+// --8<-- [end:resume_usage]
diff --git a/examples/kotlin/snippets/runtime/RunnerLoop.kt b/examples/kotlin/snippets/runtime/RunnerLoop.kt
new file mode 100644
index 0000000000..14fd83cc60
--- /dev/null
+++ b/examples/kotlin/snippets/runtime/RunnerLoop.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.adk.kt.examples.runtime
+
+import com.google.adk.kt.agents.InvocationContext
+import com.google.adk.kt.events.Event
+import com.google.adk.kt.events.EventActions
+import com.google.adk.kt.runners.InMemoryRunner
+import com.google.adk.kt.sessions.InMemorySessionService
+import com.google.adk.kt.types.Content
+import com.google.adk.kt.types.Role
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onEach
+
+// --8<-- [start:conceptual_loop]
+
+/**
+ * Simplified view of Runner's main loop logic in Kotlin
+ */
+fun runAsync(
+ userId: String,
+ sessionId: String,
+ newMessage: Content,
+ runner: InMemoryRunner,
+ sessionService: InMemorySessionService,
+): Flow {
+ // 1. Append newMessage to session event history (via SessionService)
+ // 2. Kick off event loop by calling the agent
+ // 3. Process generated events, commit changes, and yield upstream
+ return runner
+ .runAsync(
+ userId = userId,
+ sessionId = sessionId,
+ newMessage = newMessage,
+ ).onEach { event ->
+ // Process the event and commit changes to services (done internally by Runner)
+ // sessionService.appendEvent(...)
+ }
+}
+// --8<-- [end:conceptual_loop]
+
+// --8<-- [start:execution_logic]
+
+/**
+ * Simplified view of logic inside Agent.runAsync, callbacks, or tools in Kotlin
+ */
+suspend fun executionLogic(ctx: InvocationContext) {
+ // ... previous code runs based on current state ...
+
+ // 1. Determine a change or output is needed, construct the event
+ val updateData = mapOf("field_1" to "value_2")
+ val eventWithStateChange =
+ Event(
+ author = "my_agent",
+ actions = EventActions(stateDelta = updateData.toMutableMap()),
+ content = Content.fromText(Role.MODEL, "State updated."),
+ )
+
+ // 2. Yield the event to the Runner for processing & commit
+ // In Kotlin, this is done by emitting to the Flow
+ // emit(eventWithStateChange)
+
+ // <<<<<<<<<<<< EXECUTION PAUSES HERE >>>>>>>>>>>>
+ // (Implicitly, when the Flow consumer collects the event and processes it)
+
+ // <<<<<<<<<<<< RUNNER PROCESSES & COMMITS THE EVENT >>>>>>>>>>>>
+
+ // 3. Resume execution ONLY after Runner is done processing.
+ // Now, the state committed by the Runner is reliably reflected.
+ val val1 = ctx.session.state["field_1"]
+ println("Resumed execution. Value of field_1 is now: $val1")
+}
+// --8<-- [end:execution_logic]
+
+// --8<-- [start:state_update_timing]
+
+/**
+ * Conceptual view of state update timing in Kotlin
+ */
+suspend fun stateUpdateTiming(ctx: InvocationContext) {
+ // 1. Modify state
+ ctx.session.state["status"] = "processing"
+ val event1 =
+ Event(
+ author = "my_agent",
+ actions = EventActions(stateDelta = mutableMapOf("status" to "processing")),
+ )
+
+ // 2. Yield event with the delta (emit to flow)
+ // emit(event1)
+
+ // --- PAUSE --- Runner processes event1, SessionService commits 'status' = 'processing' ---
+
+ // 3. Resume execution
+ // Now it's safe to rely on the committed state
+ val currentStatus = ctx.session.state["status"] // Guaranteed to be 'processing'
+ println("Status after resuming: $currentStatus")
+}
+// --8<-- [end:state_update_timing]
+
+// --8<-- [start:dirty_read]
+
+/**
+ * Conceptual view of dirty reads in Kotlin
+ */
+fun dirtyRead(ctx: InvocationContext) {
+ // Code in a callback
+ ctx.session.state["field_1"] = "value_1"
+ // State is locally set to 'value_1', but not yet committed by Runner
+
+ // ... agent runs ...
+
+ // Code in a tool called later *within the same invocation*
+ // Readable (dirty read), but 'value_1' isn't guaranteed persistent yet.
+ val val1 = ctx.session.state["field_1"] // 'val' will likely be 'value_1' here
+ println("Dirty read value in tool: $val1")
+
+ // Assume the event carrying the state_delta={'field_1': 'value_1'}
+ // is yielded *after* this tool runs and is processed by the Runner.
+}
+// --8<-- [end:dirty_read]
diff --git a/tools/kotlin-snippets/files_to_test.txt b/tools/kotlin-snippets/files_to_test.txt
index 562f398067..7746771855 100644
--- a/tools/kotlin-snippets/files_to_test.txt
+++ b/tools/kotlin-snippets/files_to_test.txt
@@ -28,4 +28,7 @@ snippets/tools/function-tools/AgentTool.kt
snippets/tools/function-tools/PassData.kt
snippets/tools/LimitationsWorkaround.kt
snippets/get-started/multi_tool_agent/MultiToolAgent.kt
+snippets/runtime/RunConfigExample.kt
+snippets/runtime/RunnerLoop.kt
+