Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3ac6e7a
merge into altk
boazdavid Dec 2, 2025
ec34c8b
continue merge
boazdavid Dec 3, 2025
7e77578
toolguard readme
boazdavid Dec 3, 2025
86d252f
readme
boazdavid Dec 3, 2025
91936c9
DCO Remediation Commit for DAVID BOAZ <DAVIDBO@il.ibm.com> I, DAVID …
boazdavid Dec 3, 2025
640ea37
DCO Remediation Commit for DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Dec 3, 2025
6b5c9dc
tg config\n Signed-off-by: DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Dec 4, 2025
f94d016
tg fix. Signed-off-by: DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Dec 4, 2025
d2cd389
tg fix
boazdavid Dec 4, 2025
f043eb6
tg fix
boazdavid Dec 4, 2025
a4df630
DCO Remediation Commit for DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Dec 7, 2025
deaa9a0
Merge branch 'main' into main
boazdavid Dec 7, 2025
ec005bc
toolguard oss
boazdavid Dec 9, 2025
1e663e1
Merge branch 'main' of https://github.com/boazdavid/agent-lifecycle-t…
boazdavid Dec 9, 2025
ce0bd8a
DCO Remediation Commit for DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Dec 9, 2025
62ec802
fix PR comments
boazdavid Dec 14, 2025
c5864af
Merge remote-tracking branch 'upstream/main'
boazdavid Dec 21, 2025
10b325f
fix merge
boazdavid Dec 21, 2025
1a6d9bc
fix merge
boazdavid Dec 21, 2025
b9c6571
DCO Remediation Commit for DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Dec 21, 2025
f4ef5c0
fix test
boazdavid Dec 25, 2025
6738fcb
toolguard extra
boazdavid Dec 25, 2025
f4dc759
Merge remote-tracking branch 'upstream/main'
boazdavid Dec 25, 2025
c54078d
closed models only
boazdavid Dec 25, 2025
c1f6a2e
toolguard dev
boazdavid Dec 29, 2025
ef00372
readme
boazdavid Dec 30, 2025
3f8f9fb
toolguard
boazdavid Dec 30, 2025
4ea9d5f
llm clients
boazdavid Jan 1, 2026
2368f47
revert get_model_id()
boazdavid Jan 8, 2026
6ac6f32
DCO Remediation Commit for DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Jan 8, 2026
5f7e2f9
toolguard runtime
boazdavid Jan 11, 2026
86dbd46
remove mellea old
boazdavid Jan 11, 2026
67b4f77
toolguard formatting
boazdavid Jan 12, 2026
6ecdaed
secrets and uv.lock
boazdavid Jan 12, 2026
5e14de9
readme installation
boazdavid Jan 14, 2026
4e9f315
Merge remote-tracking branch 'upstream/main'
boazdavid Jan 14, 2026
7201b89
fix async runtime
boazdavid Jan 25, 2026
228f8c3
fix reformat, and uv.lock
boazdavid Jan 25, 2026
4b47423
DCO Remediation Commit for DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Jan 25, 2026
e02f4bb
fix toolguard code component
boazdavid Jan 29, 2026
56761e9
DCO Remediation Commit for DAVID BOAZ <DAVIDBO@il.ibm.com>
boazdavid Jan 29, 2026
0f443fa
toolguard version 0.2.7
boazdavid Jan 29, 2026
b57a5ea
add documentation
boazdavid Jan 29, 2026
4a6a085
Merge branch 'main' into toolguard_020
boazdavid Feb 3, 2026
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
58 changes: 32 additions & 26 deletions altk/pre_tool/toolguard/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# ToolGuards for Enforcing Agentic Policy Adherence
An agent lifecycle solution for enforcing business policy adherence in agentic workflows. Enabling this component has demonstrated up to a **20‑point improvement** in end‑to‑end agent accuracy when invoking tools. This work is described in [EMNLP 2025 Towards Enforcing Company Policy Adherence in Agentic Workflows](https://arxiv.org/pdf/2507.16459), and is publiched in [this GitHub library](https://github.com/AgentToolkit/toolguard).
Enforces business policy adherence in agentic workflows. Enabling this component has demonstrated up to a **20‑point improvement** in end‑to‑end agent accuracy when invoking tools. This work is described in [EMNLP 2025 Towards Enforcing Company Policy Adherence in Agentic Workflows](https://arxiv.org/pdf/2507.16459), and is published in [this GitHub library](https://github.com/AgentToolkit/toolguard).

## Table of Contents
- [Overview](#overview)
- [ToolGuardSpecComponent](#ToolGuardSpecComponent)
- [Configuarion](#component-configuarion)
- [Configuration](#component-configuration)
- [Inputs and Outputs](#input-and-output)
- [Usage example](#usage-example)
- [ToolGuardCodeComponent](#ToolGuardCodeComponent)
- [Configuarion](#component-configuarion-1)
- [Configuration](#component-configuration-1)
- [Inputs and Outputs](#input-and-output-1)
- [Usage example](#usage-example-1)

Expand All @@ -18,16 +18,21 @@ An agent lifecycle solution for enforcing business policy adherence in agentic w
Business policies (or guidelines) are normally detailed in company documents, and have traditionally been hard-coded into automatic assistant platforms. Contemporary agentic approaches take the "best-effort" strategy, where the policies are appended to the agent's system prompt, an inherently non-deterministic approach, that does not scale effectively. Here we propose a deterministic, predictable and interpretable two-phase solution for agentic policy adherence at the tool-level: guards are executed prior to function invocation and raise alerts in case a tool-related policy deem violated.
This component enforces **pre‑tool activation policy constraints**, ensuring that agent decisions comply with business rules **before** modifying system state. This prevents policy violations such as unauthorized tool calls or unsafe parameter values.

### Installation
```
uv pip install "agent-lifecycle-toolkit[toolguard]"
```

## ToolGuardSpecComponent
This component gets a set of tools and a policy document and generated multiple ToolGuard specifications, known as `ToolGuardSpec`s. Each specification is attached to a tool, and it declares a precondition that must apply before invoking the tool. The specification has a `name`, `description`, list of `refernces` to the original policy document, a set of declerative `compliance_examples`, describing test cases that the toolGuard should allow the tool invocation, and `violation_examples`, where the toolGuard should raise an exception.
This component gets a set of tools and a policy document and generates multiple ToolGuard specifications, known as `ToolGuardSpec`s. Each specification is attached to a tool, and it declares a precondition that must apply before invoking the tool. The specification has a `name`, `description`, list of `references` to the original policy document, a set of declarative `compliance_examples`, describing test cases that the toolGuard should allow the tool invocation, and `violation_examples`, where the toolGuard should raise an exception.

This componenet supports only a `build` phase. The generate specifications are returned as output, and are also saved to a specified file system directory.
The specifications are aimed to be used as input into our next component - the `ToolGuardCodeComponent` described below.
This component supports only a `build` phase. The generated specifications are returned as output, and are also saved to a specified file system directory.
The specifications are aimed to be used as input into our next component - the `ToolGuardCodeComponent` described below.

The two components are not concatenated by design. As the geneartion involves a non-deterministic language model, the results need to be reviewed by a human. Hence, the output specification files should be reviewed and optionaly edited. For example, removing a wrong compliance example.
The two components are not concatenated by design. As the generation involves a non-deterministic language model, the results need to be reviewed by a human. Hence, the output specification files should be reviewed and optionally edited. For example, removing a wrong compliance example.

### Component Configuarion
This component expects an LLM client configuarion. Here is a concerete example using WatsonX SDK:
### Component Configuration
This component expects an LLM client configuration. Here is a concrete example using WatsonX SDK:
```python
from altk.core.llm.providers.ibm_watsonx_ai.ibm_watsonx_ai import WatsonxLLMClient
llm_client = WatsonxLLMClient(
Expand All @@ -43,7 +48,7 @@ llm_client = WatsonxLLMClient(
The component build input is a `ToolGuardSpecBuildInput` object containing the following fields:
* `policy_text: str`: Text of the policy document
* `tools: List[Callable] | List[BaseTool] | str`: List of available tools. Either as Python functions, methods, Langgraph Tools, or a path to an Open API specification file.
* `out_dir: str`: A directory in the local file system where the specification objects will be saved.
* `out_dir: str`: A directory in the local file system where the specification objects will be saved.

The component build output is a list of `ToolGuardSpec`, as described above.

Expand All @@ -53,18 +58,18 @@ see [simple calculator test](../../../tests/pre_tool/toolguard/test_toolguard_sp

## ToolGuardCodeComponent

This components enfoorces policy adherence through a two-phase process:
This component enforces policy adherence through a two-phase process:

(1) **Buildtime**: Given a set of `ToolGuardSpec`s, generates policy validation code - `ToolGuard`s.
Similar to ToolGuard Specifications, generated `ToolGuards` are a good start, but they may contain errors. Hence, they should be also reviewed by a human.

(2) **Runtime**: ToolGuards are deployed within the agent's flow, and are triggered before agent's tool invocation. They can be deployed into the agent loop, or in an MCP Gateway.
The ToolGuards checks if a planned action complies with the policy. If it violates, the agent is prompted to self-reflect and revise its plan before proceeding.
(2) **Runtime**: ToolGuards are deployed within the agent's flow, and are triggered before agent's tool invocation. They can be deployed into the agent loop, or in an MCP Gateway.
The ToolGuards check if a planned action complies with the policy. If it violates, the agent is prompted to self-reflect and revise its plan before proceeding.


### Component Configuarion
### Component Configuration

This component expects an LLM client configuarion.
This component expects an LLM client configuration.
Here is an example using a Watsonx LLM client:
```
from altk.core.llm.providers.ibm_watsonx_ai.ibm_watsonx_ai import WatsonxLLMClient
Expand All @@ -82,26 +87,27 @@ toolguard_code_component = ToolGuardCodeComponent(config)
### Input and Output
The Component has two phases:
#### Build phase
An agent owner should use this API to generate ToolGuards - Python function that enforce the given business policy.
An agent owner should use this API to generate ToolGuards - Python functions that enforce the given business policy.
The input of the build phase is a `ToolGuardCodeBuildInput` object, containing:
* `tools: List[Callable] | List[BaseTool] | str`: List of available tools. Either as Python functions, methods, Langgraph Tools, or a path to an Open API specification file.
* `toolguard_specs: List[ToolGuardSpec]`: List of specifications, optionaly generated by `ToolGuardSpecComponent` component and reviewed.
* `out_dir: str | Path`: A directory in the local file system where the ToolGuard objects will be saved.
* `toolguard_specs: List[ToolGuardSpec]`: List of specifications, optionally generated by `ToolGuardSpecComponent` component and reviewed.
* `app_name: str`: Name of the application for which guards are being generated. This will be namespace of the guards generated code.
* `out_dir: str | Path`: A directory in the local file system where the ToolGuard objects will be saved.

The output of the build phase is a `ToolGuardsCodeGenerationResult` object with:
* `out_dir: Path`: Path to the file system where the results were saved. It is the same as the `input.out_dir`.
* `domain: RuntimeDomain`: A complex object descibing the generated APIs. For example, refernces to Python file names and class names.
* `tools: Dict[str, ToolGuardCodeResult]`: A Dictionary of the ToolGuardsResults, by the tool names.
* Each `ToolGuardCodeResult` details the name of guard Python file name and the guard function name. It also reference to the generated unit test files.
* `domain: RuntimeDomain`: A complex object describing the generated APIs. For example, references to Python file names and class names.
* `tools: Dict[str, ToolGuardCodeResult]`: A Dictionary of the ToolGuardsResults, by the tool names.
* Each `ToolGuardCodeResult` details the name of guard Python file name and the guard function name. It also references the generated unit test files.

#### Runtime phase
A running agent should use the runtime API to check if a tool call complies with the given policy.
A running agent should use the runtime async API to check if a tool call complies with the given policy.
The input of the runtime phase is a `ToolGuardCodeRunInput` object:
* `generated_guard_dir: str | Path`: Path in the local file system where the generated guard Python code (The code that was generated during the build time, described above) is located.
* `tool_name: str`: The name of the tool that the agent is about to call
* `tool_args: Dict[str, Any]`: A dictionary of the toolcall arguments, by the argument name.
* `tool_invoker: IToolInvoker`: A proxy object that enables the guard to call other read-only tools. This is needed when the policy enforcement logic involves getting data from another tool. For example, before booking a flight, you need to check the flight status by calling the "get_flight_status" API.
The `IToolInvoker` interface contains a single method:
The `IToolInvoker` interface contains a single method:
```
def invoke(self, toolname: str, arguments: Dict[str, Any], return_type: Type[T]) -> T
```
Expand All @@ -110,10 +116,10 @@ The input of the runtime phase is a `ToolGuardCodeRunInput` object:
* `toolguard.runtime.ToolFunctionsInvoker(funcs: List[Callable])` where the tools are defined as plain global Python functions.
* `toolguard.runtime.ToolMethodsInvoker(obj: object)` where the tools are defined as methods in a given Python object.
* `toolguard.runtime.LangchainToolInvoker(tools: List[BaseTool])` where the tools are a list of langchain tools.


The outpput of the runtime phase is a `ToolGuardCodeRunOutput` object with an optional `violation` field.
* `violation: PolicyViolation | None`: Polpulated only if a violation was identified. If the toolcall complies with the policy, the violation is None.

The output of the runtime phase is a `ToolGuardCodeRunOutput` object with an optional `violation` field.
* `violation: PolicyViolation | None`: Populated only if a violation was identified. If the toolcall complies with the policy, the violation is None.
* `violation_level: "info" | "warn" | "error"`: Severity level of a safety violation.
* `user_message: str | None`: A meaningful error message to the user (this message can be also passed to the agent reasoning phase to find an alternative next action).

Expand Down
40 changes: 40 additions & 0 deletions altk/pre_tool/toolguard/tool_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Utility module for converting various tool formats to ToolGuard TOOLS type."""

from typing import Callable, List, cast
from langchain_core.tools import BaseTool
from toolguard.buildtime import TOOLS
from toolguard.buildtime.buildtime import OpenAPI
from toolguard.extra.langchain_to_oas import langchain_tools_to_openapi


def to_tools(tools: List[Callable] | List[BaseTool] | str) -> TOOLS:
"""Convert various tool formats to the TOOLS type expected by ToolGuard.

Args:
tools: Either a string path to an OpenAPI specification file,
a list of callable functions, or a list of BaseTool instances.

Returns:
TOOLS: The converted tools in the format expected by ToolGuard.
This can be either a list of callables or an OpenAPI specification dict.

Raises:
ValueError: If the tools input is invalid or cannot be converted.
This includes invalid OpenAPI spec files, mixed tool types in lists,
or unsupported input types.
"""
if isinstance(tools, str):
try:
return OpenAPI.load_from(tools).model_dump()
except Exception as e:
raise ValueError(f"Invalid OpenAPI spec file: {e}") from e

elif isinstance(tools, list):
if all(isinstance(tool, Callable) for tool in tools):
return cast(List[Callable], tools)
elif all(isinstance(tool, BaseTool) for tool in tools):
return langchain_tools_to_openapi(cast(List[BaseTool], tools))
else:
raise ValueError("Invalid tools list")

raise ValueError("Invalid tools input")
79 changes: 64 additions & 15 deletions altk/pre_tool/toolguard/toolguard_code_component.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,65 @@
import logging
from pathlib import Path
from typing import Any, Callable, Dict, List, cast
from enum import Enum
from pydantic import BaseModel, Field
from typing import Set
from langchain_core.tools import BaseTool
from pathlib import Path
from typing import Any, Callable, Dict, List, Set, cast

from altk.core.toolkit import ComponentConfig, ComponentInput, AgentPhase, ComponentBase
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
from toolguard.buildtime import (
generate_guards_from_specs,
ToolGuardSpec,
ToolGuardsCodeGenerationResult,
ToolGuardSpec,
generate_guards_code,
)
from toolguard.runtime import IToolInvoker, load_toolguards, PolicyViolationException
from toolguard.runtime import IToolInvoker, PolicyViolationException, load_toolguards

from altk.core.toolkit import AgentPhase, ComponentBase, ComponentConfig, ComponentInput
from altk.pre_tool.toolguard.llm_client import TG_LLMClient
from altk.pre_tool.toolguard.tool_converter import to_tools

logger = logging.getLogger(__name__)


class ToolGuardCodeComponentConfig(ComponentConfig):
"""Configuration for ToolGuardCodeComponent.

This component enforces policy adherence through code generation and runtime validation.
It requires an LLM client configuration for generating ToolGuard code from specifications.

Inherits all configuration from ComponentConfig, including llm_client settings.
"""

pass


class ToolGuardCodeBuildInput(ComponentInput):
"""Input configuration for building ToolGuard code generation.

:param tools: List of callable functions, BaseTool instances, or string path to OpenAPI spec
:param toolguard_specs: List of ToolGuard specifications to generate guards from. results from `toolguard_spec_component` component.
:param app_name: Name of the application for which guards are being generated. This will be namespace of the guards generated code.
:param out_dir: Output directory path where generated guard code will be saved
"""

tools: List[Callable] | List[BaseTool] | str
toolguard_specs: List[ToolGuardSpec]
app_name: str
out_dir: str | Path


ToolGuardBuildOutput = ToolGuardsCodeGenerationResult


class ToolGuardCodeRunInput(ComponentInput):
"""Input configuration for running ToolGuard code validation at runtime.

A running agent uses this input to check if a tool call complies with the given policy.

:param generated_guard_dir: Path in the local file system where the generated guard Python code (generated during build time) is located
:param tool_name: The name of the tool that the agent is about to call
:param tool_args: A dictionary of the tool call arguments, by the argument name
:param tool_invoker: A proxy object that enables the guard to call other read-only tools. This is needed when the policy enforcement logic involves getting data from another tool
"""

generated_guard_dir: str | Path
tool_name: str = Field(description="Tool name")
tool_args: Dict[str, Any] = Field(default={}, description="Tool arguments")
Expand Down Expand Up @@ -68,10 +95,29 @@ class PolicyViolation(BaseModel):


class ToolGuardCodeRunOutput(BaseModel):
"""Output from ToolGuard code validation at runtime.

Contains information about policy violations detected during tool call validation.
If the tool call complies with the policy, the violation field is None.

:param violation: Populated only if a violation was identified. Contains violation level and user message
"""

violation: PolicyViolation | None = None


class ToolGuardCodeComponent(ComponentBase):
"""Component for enforcing policy adherence through a two-phase process.

This component enforces policy adherence through code generation and runtime validation:

(1) **Buildtime**: Given a set of ToolGuardSpecs, generates policy validation code - ToolGuards.

(2) **Runtime**: ToolGuards are deployed within the agent's flow, and are triggered before
agent's tool invocation. They can be deployed into the agent loop, or in an MCP Gateway.
The ToolGuards check if a planned action complies with the policy.
"""

def __init__(self, config: ToolGuardCodeComponentConfig):
super().__init__(config=config)

Expand All @@ -90,27 +136,30 @@ async def _abuild(
) -> ToolGuardsCodeGenerationResult:
config = cast(ToolGuardCodeComponentConfig, self.config)
llm = TG_LLMClient(config.llm_client)
return await generate_guards_from_specs(
tools=data.tools,
return await generate_guards_code(
tools=to_tools(data.tools),
tool_specs=data.toolguard_specs,
work_dir=data.out_dir,
llm=llm,
app_name=data.app_name,
)

def _run(self, data: ToolGuardCodeRunInput) -> ToolGuardCodeRunOutput:
raise NotImplementedError("Please use the _arun() function in an async context")

async def _arun(self, data: ToolGuardCodeRunInput) -> ToolGuardCodeRunOutput:
code_root_dir = data.generated_guard_dir
tool_name = data.tool_name
tool_params = data.tool_args
with load_toolguards(code_root_dir) as toolguards:
try:
toolguards.check_toolcall(tool_name, tool_params, data.tool_invoker)
await toolguards.guard_toolcall(
tool_name, tool_params, data.tool_invoker
)
return ToolGuardCodeRunOutput()
except PolicyViolationException as e:
return ToolGuardCodeRunOutput(
violation=PolicyViolation(
violation_level=ViolationLevel.ERROR, user_message=str(e)
)
)

def _arun(self, data: ToolGuardCodeRunInput) -> ToolGuardCodeRunOutput:
return self._run(data)
Loading
Loading