Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions python/packages/kagent-adk/src/kagent/adk/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from kagent.core import KAgentConfig, configure_logging, configure_tracing

from . import AgentConfig, KAgentApp
from .tools import add_skills_tool_to_agent
from .tools import add_session_tool, add_skills_tool_to_agent

logger = logging.getLogger(__name__)
logging.getLogger("google_adk.google.adk.tools.base_authenticated_tool").setLevel(logging.ERROR)
Expand Down Expand Up @@ -74,7 +74,7 @@ def static(

def root_agent_factory() -> BaseAgent:
root_agent = agent_config.to_agent(app_cfg.name, sts_integration)

add_session_tool(root_agent)
maybe_add_skills(root_agent)
Comment on lines 76 to 78
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

add_session_tool() is now invoked unconditionally for all CLI entrypoints. Since this changes the prompt for every request (and may expose identifiers), consider gating it behind configuration (e.g., AgentConfig/CRD field or an env var like KAGENT_INJECT_SESSION_INFO=true) so operators can disable it in sensitive or token-constrained deployments.

Copilot uses AI. Check for mistakes.

return root_agent
Expand Down Expand Up @@ -149,6 +149,7 @@ def root_agent_factory() -> BaseAgent:
if sts_integration:
add_to_agent(sts_integration, root_agent)

add_session_tool(root_agent)
maybe_add_skills(root_agent)

return root_agent
Expand Down Expand Up @@ -213,6 +214,7 @@ async def test_agent(agent_config: AgentConfig, agent_card: AgentCard, task: str

def root_agent_factory() -> BaseAgent:
root_agent = agent_config.to_agent(app_cfg.name, sts_integration)
add_session_tool(root_agent)
maybe_add_skills(root_agent)
return root_agent

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ description: Analyze CSV/Excel files
| **ReadFile** | Read files with line numbers | `read_file("skills/data-analysis/config.json")` |
| **WriteFile** | Create/overwrite files | `write_file("outputs/report.pdf", data)` |
| **EditFile** | Precise string replacements | `edit_file("script.py", old="x", new="y")` |
| **SessionInfo**| Auto-injects session details into system instructions | *(automatic — no tool call needed)* |
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

The README calls this tool “SessionInfo” and says it’s automatic/no tool call needed, but the actual injected tool name is get_session_info and it’s registered on agents. Consider clarifying in the docs that it’s an internal request hook (not an LLM-callable tool) and documenting exactly which fields are injected (and whether user_id is included).

Copilot uses AI. Check for mistakes.

### Working Directory Structure

Expand Down
3 changes: 3 additions & 0 deletions python/packages/kagent-adk/src/kagent/adk/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .bash_tool import BashTool
from .file_tools import EditFileTool, ReadFileTool, WriteFileTool
from .session_tool import SessionInfoTool, add_session_tool
from .skill_tool import SkillsTool
from .skills_plugin import add_skills_tool_to_agent
from .skills_toolset import SkillsToolset
Expand All @@ -8,8 +9,10 @@
"SkillsTool",
"SkillsToolset",
"BashTool",
"SessionInfoTool",
"EditFileTool",
"ReadFileTool",
"WriteFileTool",
"add_session_tool",
"add_skills_tool_to_agent",
]
51 changes: 51 additions & 0 deletions python/packages/kagent-adk/src/kagent/adk/tools/session_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from google.adk.agents import BaseAgent, LlmAgent
from google.adk.tools.base_tool import BaseTool
from google.adk.tools.tool_context import ToolContext
from typing_extensions import override

if TYPE_CHECKING:
from google.adk.models.llm_request import LlmRequest

logger = logging.getLogger("kagent_adk." + __name__)


def add_session_tool(agent: BaseAgent) -> None:
if not isinstance(agent, LlmAgent):
return
existing_tool_names = {getattr(t, "name", None) for t in agent.tools}
if "get_session_info" not in existing_tool_names:
agent.tools.append(SessionInfoTool())
logger.debug(f"Added session info tool to agent: {agent.name}")


class SessionInfoTool(BaseTool):
"""Tool for retrieving information about the current agent session."""

def __init__(self):
super().__init__(
name="get_session_info",
description="Get information about the current agent session, including the session ID.",
)

@override
async def process_llm_request(
self,
*,
tool_context: ToolContext,
llm_request: LlmRequest,
) -> None:
session = tool_context.session
if not session:
return
info = (
"kagent session:\n"
f"- session_id: {session.id or 'N/A'}\n"
f"- user_id: {session.user_id or 'N/A'}\n"
Comment on lines +45 to +48
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This injects session.user_id into the LLM system instruction. If user_id is a real user identifier, this is a potentially sensitive value to send to external model providers and to propagate through logs/traces. Consider omitting user_id by default, redacting/hashing it, or making inclusion explicitly configurable.

Suggested change
info = (
"kagent session:\n"
f"- session_id: {session.id or 'N/A'}\n"
f"- user_id: {session.user_id or 'N/A'}\n"
user_id_display = "[redacted]" if getattr(session, "user_id", None) else "N/A"
info = (
"kagent session:\n"
f"- session_id: {session.id or 'N/A'}\n"
f"- user_id: {user_id_display}\n"

Copilot uses AI. Check for mistakes.
f"- app_name: {session.app_name or 'N/A'}"
)
llm_request.append_instructions([info])
Comment on lines +42 to +51
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

SessionInfoTool injects instructions on every process_llm_request call. ADK can call process_llm_request multiple times in the same user turn (e.g., sequential tool round-trips), so this will duplicate the session info in system instructions and waste tokens. Consider guarding with a tool_context.state key (similar to PrefetchMemoryTool) or checking whether the info has already been appended before adding again.

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from google.adk.tools import BaseTool
from google.adk.tools.base_toolset import BaseToolset

from ..tools import BashTool, EditFileTool, ReadFileTool, WriteFileTool
from ..tools import BashTool, EditFileTool, ReadFileTool, SessionInfoTool, WriteFileTool
from .skill_tool import SkillsTool

logger = logging.getLogger("kagent_adk." + __name__)
Expand All @@ -28,6 +28,7 @@ class SkillsToolset(BaseToolset):
3. WriteFileTool - Write/create files
4. EditFileTool - Edit files with precise replacements
5. BashTool - Execute shell commands
6. SessionInfoTool - Inject session information into system instructions

Skills provide specialized domain knowledge and scripts that the agent can use
to solve complex tasks. The toolset enables discovery of available skills,
Expand All @@ -51,18 +52,20 @@ def __init__(self, skills_directory: str | Path):
self.write_file_tool = WriteFileTool()
self.edit_file_tool = EditFileTool()
self.bash_tool = BashTool(skills_directory)
self.session_info_tool = SessionInfoTool()

@override
async def get_tools(self, readonly_context: Optional[ReadonlyContext] = None) -> List[BaseTool]:
"""Get all skills tools.

Returns:
List containing all skills tools: skills, read, write, edit, and bash.
List containing all skills tools: skills, read, write, edit, bash, and session info.
"""
return [
self.skills_tool,
self.read_file_tool,
self.write_file_tool,
self.edit_file_tool,
self.bash_tool,
self.session_info_tool,
Comment on lines 54 to +70
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

SkillsToolset now always includes SessionInfoTool, while the CLI also appends SessionInfoTool via add_session_tool(). If a consumer uses SkillsToolset and also calls add_session_tool(), the session info may be injected multiple times. A shared tool_context.state guard inside SessionInfoTool (or avoiding double registration) would prevent duplicate injection regardless of how tools are composed.

Copilot uses AI. Check for mistakes.
]
52 changes: 52 additions & 0 deletions python/packages/kagent-adk/tests/unittests/test_session_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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.

from unittest.mock import Mock

import pytest
from google.adk.models.llm_request import LlmRequest

from kagent.adk.tools.session_tool import SessionInfoTool


class TestSessionInfoTool:
@pytest.mark.asyncio
async def test_session_info_tool(self):
tool = SessionInfoTool()

context = Mock()
context.session = Mock()
context.session.id = "session-123"
context.session.user_id = "user-456"
context.session.app_name = "test-app"

llm_request = LlmRequest()
await tool.process_llm_request(tool_context=context, llm_request=llm_request)

assert "session-123" in llm_request.config.system_instruction
assert "user-456" in llm_request.config.system_instruction
assert "test-app" in llm_request.config.system_instruction
assert "get_session_info" not in llm_request.tools_dict

@pytest.mark.asyncio
async def test_session_info_tool_none_session(self):
tool = SessionInfoTool()

context = Mock()
context.session = None

llm_request = LlmRequest()
await tool.process_llm_request(tool_context=context, llm_request=llm_request)

assert llm_request.config.system_instruction is None
Loading