diff --git a/core/src/main/java/com/google/adk/plugins/GlobalInstructionPlugin.java b/core/src/main/java/com/google/adk/plugins/GlobalInstructionPlugin.java
new file mode 100644
index 000000000..1773bf701
--- /dev/null
+++ b/core/src/main/java/com/google/adk/plugins/GlobalInstructionPlugin.java
@@ -0,0 +1,118 @@
+/*
+ * 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.plugins;
+
+import com.google.adk.agents.CallbackContext;
+import com.google.adk.models.LlmRequest;
+import com.google.adk.models.LlmResponse;
+import com.google.adk.utils.InstructionUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.genai.types.Content;
+import com.google.genai.types.GenerateContentConfig;
+import com.google.genai.types.Part;
+import io.reactivex.rxjava3.core.Maybe;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Plugin that provides global instructions functionality at the App level.
+ *
+ *
Global instructions are applied to all agents in the application, providing a consistent way
+ * to set application-wide instructions, identity, or personality. Global instructions can be
+ * provided as a static string, or as a function that resolves the instruction based on the {@link
+ * CallbackContext}.
+ *
+ *
The plugin operates through the before_model_callback, allowing it to modify LLM requests
+ * before they are sent to the model by prepending the global instruction to any existing system
+ * instructions provided by the agent.
+ */
+public class GlobalInstructionPlugin extends BasePlugin {
+
+ private final Function> instructionProvider;
+
+ private static Function> createInstructionProvider(
+ String globalInstruction) {
+ return callbackContext -> {
+ if (globalInstruction == null) {
+ return Maybe.empty();
+ }
+ return InstructionUtils.injectSessionState(
+ callbackContext.invocationContext(), globalInstruction)
+ .toMaybe();
+ };
+ }
+
+ public GlobalInstructionPlugin(String globalInstruction) {
+ this(globalInstruction, "global_instruction");
+ }
+
+ public GlobalInstructionPlugin(String globalInstruction, String name) {
+ this(createInstructionProvider(globalInstruction), name);
+ }
+
+ public GlobalInstructionPlugin(Function> instructionProvider) {
+ this(instructionProvider, "global_instruction");
+ }
+
+ public GlobalInstructionPlugin(
+ Function> instructionProvider, String name) {
+ super(name);
+ this.instructionProvider = instructionProvider;
+ }
+
+ @Override
+ public Maybe beforeModelCallback(
+ CallbackContext callbackContext, LlmRequest.Builder llmRequest) {
+ return instructionProvider
+ .apply(callbackContext)
+ .filter(instruction -> !instruction.isEmpty())
+ .flatMap(
+ instruction -> {
+ // Get mutable config, or create one if it doesn't exist.
+ GenerateContentConfig config =
+ llmRequest.config().orElseGet(GenerateContentConfig.builder()::build);
+
+ // Get existing system instruction parts, if any.
+ Optional systemInstruction = config.systemInstruction();
+ List existingParts =
+ systemInstruction.flatMap(Content::parts).orElse(ImmutableList.of());
+
+ // Prepend the global instruction to the existing system instruction parts.
+ // If there are existing instructions, add two newlines between the global
+ // instruction and the existing instructions.
+ ImmutableList.Builder newPartsBuilder = ImmutableList.builder();
+ if (existingParts.isEmpty()) {
+ newPartsBuilder.add(Part.fromText(instruction));
+ } else {
+ newPartsBuilder.add(Part.fromText(instruction + "\n\n"));
+ newPartsBuilder.addAll(existingParts);
+ }
+
+ // Build the new system instruction content.
+ Content.Builder newSystemInstructionBuilder = Content.builder();
+ systemInstruction.flatMap(Content::role).ifPresent(newSystemInstructionBuilder::role);
+ newSystemInstructionBuilder.parts(newPartsBuilder.build());
+
+ // Update llmRequest with new config.
+ llmRequest.config(
+ config.toBuilder()
+ .systemInstruction(newSystemInstructionBuilder.build())
+ .build());
+ return Maybe.empty();
+ });
+ }
+}
diff --git a/core/src/test/java/com/google/adk/plugins/GlobalInstructionPluginTest.java b/core/src/test/java/com/google/adk/plugins/GlobalInstructionPluginTest.java
new file mode 100644
index 000000000..345314256
--- /dev/null
+++ b/core/src/test/java/com/google/adk/plugins/GlobalInstructionPluginTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.plugins;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+import com.google.adk.agents.CallbackContext;
+import com.google.adk.agents.InvocationContext;
+import com.google.adk.artifacts.BaseArtifactService;
+import com.google.adk.models.LlmRequest;
+import com.google.adk.sessions.Session;
+import com.google.adk.sessions.State;
+import com.google.genai.types.Content;
+import com.google.genai.types.GenerateContentConfig;
+import com.google.genai.types.Part;
+import io.reactivex.rxjava3.core.Maybe;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(JUnit4.class)
+public class GlobalInstructionPluginTest {
+ @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+ @Mock private CallbackContext mockCallbackContext;
+ @Mock private InvocationContext mockInvocationContext;
+ private final State state = new State(new ConcurrentHashMap<>());
+ private final Session session = Session.builder("session_id").state(state).build();
+ @Mock private BaseArtifactService mockArtifactService;
+
+ @Before
+ public void setUp() {
+ state.clear();
+ when(mockCallbackContext.invocationId()).thenReturn("invocation_id");
+ when(mockCallbackContext.agentName()).thenReturn("agent_name");
+ when(mockCallbackContext.invocationContext()).thenReturn(mockInvocationContext);
+ when(mockInvocationContext.session()).thenReturn(session);
+ when(mockInvocationContext.artifactService()).thenReturn(mockArtifactService);
+ }
+
+ @Test
+ public void beforeModelCallback_noExistingInstruction() {
+ LlmRequest.Builder llmRequestBuilder = LlmRequest.builder();
+ GlobalInstructionPlugin plugin = new GlobalInstructionPlugin("global instruction");
+ plugin.beforeModelCallback(mockCallbackContext, llmRequestBuilder).test().assertComplete();
+ Content systemInstruction = llmRequestBuilder.build().config().get().systemInstruction().get();
+ List parts = systemInstruction.parts().get();
+ assertThat(parts).hasSize(1);
+ assertThat(parts.get(0).text()).hasValue("global instruction");
+ assertThat(systemInstruction.role()).isEmpty();
+ }
+
+ @Test
+ public void beforeModelCallback_withExistingInstruction() {
+ LlmRequest.Builder llmRequestBuilder =
+ LlmRequest.builder()
+ .config(
+ GenerateContentConfig.builder()
+ .systemInstruction(
+ Content.builder().parts(Part.fromText("existing instruction")).build())
+ .build());
+ GlobalInstructionPlugin plugin = new GlobalInstructionPlugin("global instruction");
+ plugin.beforeModelCallback(mockCallbackContext, llmRequestBuilder).test().assertComplete();
+ Content systemInstruction = llmRequestBuilder.build().config().get().systemInstruction().get();
+ List parts = systemInstruction.parts().get();
+ assertThat(parts).hasSize(2);
+ assertThat(parts.get(0).text()).hasValue("global instruction\n\n");
+ assertThat(parts.get(1).text()).hasValue("existing instruction");
+ assertThat(systemInstruction.role()).isEmpty();
+ }
+
+ @Test
+ public void beforeModelCallback_withInstructionProvider() {
+ LlmRequest.Builder llmRequestBuilder = LlmRequest.builder();
+ GlobalInstructionPlugin plugin =
+ new GlobalInstructionPlugin(unusedContext -> Maybe.just("instruction from provider"));
+ plugin.beforeModelCallback(mockCallbackContext, llmRequestBuilder).test().assertComplete();
+ Content systemInstruction = llmRequestBuilder.build().config().get().systemInstruction().get();
+ List parts = systemInstruction.parts().get();
+ assertThat(parts).hasSize(1);
+ assertThat(parts.get(0).text()).hasValue("instruction from provider");
+ assertThat(systemInstruction.role()).isEmpty();
+ }
+
+ @Test
+ public void beforeModelCallback_withStringInstruction_injectsState() {
+ state.put("name", "Alice");
+ LlmRequest.Builder llmRequestBuilder = LlmRequest.builder();
+ GlobalInstructionPlugin plugin = new GlobalInstructionPlugin("Hello {name}");
+ plugin.beforeModelCallback(mockCallbackContext, llmRequestBuilder).test().assertComplete();
+ Content systemInstruction = llmRequestBuilder.build().config().get().systemInstruction().get();
+ List parts = systemInstruction.parts().get();
+ assertThat(parts).hasSize(1);
+ assertThat(parts.get(0).text()).hasValue("Hello Alice");
+ assertThat(systemInstruction.role()).isEmpty();
+ }
+
+ @Test
+ public void beforeModelCallback_nullInstruction() {
+ LlmRequest.Builder llmRequestBuilder = LlmRequest.builder();
+ GlobalInstructionPlugin plugin = new GlobalInstructionPlugin((String) null);
+ plugin.beforeModelCallback(mockCallbackContext, llmRequestBuilder).test().assertComplete();
+ assertThat(llmRequestBuilder.build().config()).isEmpty();
+ }
+
+ @Test
+ public void beforeModelCallback_emptyInstruction() {
+ LlmRequest.Builder llmRequestBuilder = LlmRequest.builder();
+ GlobalInstructionPlugin plugin = new GlobalInstructionPlugin("");
+ plugin.beforeModelCallback(mockCallbackContext, llmRequestBuilder).test().assertComplete();
+ assertThat(llmRequestBuilder.build().config()).isEmpty();
+ }
+
+ @Test
+ public void beforeModelCallback_withExistingInstructionAndRole_preservesRole() {
+ LlmRequest.Builder llmRequestBuilder =
+ LlmRequest.builder()
+ .config(
+ GenerateContentConfig.builder()
+ .systemInstruction(
+ Content.builder()
+ .parts(Part.fromText("existing instruction"))
+ .role("system")
+ .build())
+ .build());
+ GlobalInstructionPlugin plugin = new GlobalInstructionPlugin("global instruction");
+ plugin.beforeModelCallback(mockCallbackContext, llmRequestBuilder).test().assertComplete();
+ Content systemInstruction = llmRequestBuilder.build().config().get().systemInstruction().get();
+ List parts = systemInstruction.parts().get();
+ assertThat(parts).hasSize(2);
+ assertThat(parts.get(0).text()).hasValue("global instruction\n\n");
+ assertThat(parts.get(1).text()).hasValue("existing instruction");
+ assertThat(systemInstruction.role()).hasValue("system");
+ }
+}