Skip to content

Conversation

@skurzyp-blockydevs
Copy link
Contributor

@skurzyp-blockydevs skurzyp-blockydevs commented Jan 16, 2026

Description:

PR: Universal Policy System for Hedera Agent Kit

Overview

This PR introduces a flexible, "DIY" policy system that allows users to define and enforce custom validation logic for on-chain transactions executed by the Hedera Agent Kit. The system is built upon the new "Multistep Tool" architecture, leveraging lifecycle hooks to inject policy checks at precise execution points.

Changes

Core Policy Infrastructure

  • src/shared/policy.ts: Defines the Policy interface, ToolExecutionPoint enum, and enforcePolicies helper function.
  • src/shared/configuration.ts: Extended Context type with optional policies?: Policy[] field.
  • src/shared/tools.ts: BaseTool now implements a lifecycle with hooks:
    1. preToolExecutionHook
    2. postParamsNormalizationHook
    3. postCoreActionHook
    4. postToolExecutionHook

Policy Execution Points

The Policy interface now includes an optional affectedPoints array, allowing policies to target specific lifecycle stages.

Execution Point Description Use Case
PreToolExecution Before any processing Access control on raw inputs
PostParamsNormalization After validation & normalization Most policies go here (standard checks on typed params)
PostCoreAction After transaction creation but before execution Deep inspection of constructed transaction
PostSecondaryAction After tool execution (including optional secondary action) Logging, auditing, specific post-tx verifications

Example Policies (Reference Implementations)

All shared policies have been explicitly configured to run at ToolExecutionPoint.PostParamsNormalization.

Policy Description Typed Params
MaxHbarTransferPolicy Limits max HBAR per transfer ❌ Uses any
RequiredMemoPolicy Enforces non-empty memos ❌ Uses any
TokenAllowlistPolicy Restricts to allowed Token IDs ❌ Uses any
ImmutabilityPolicy Blocks modification of specific accounts/tokens ✅ Uses Zod schemas
NoInfiniteSupplyPolicy Prevents infinite supply token creation ❌ Uses any

Tool Integration

Policy enforcement is now automatic for all tools inheriting from BaseTool. The BaseTool invokes enforcePolicies within its default hook implementations. Subclasses don't need to manually call enforcePolicies—they just implement normalizeParams, coreAction, secondaryAction, etc.

Example Application

  • typescript/examples/policy-example/: LangChain-based interactive demo
    • Demonstrates policy enforcement with multiple policies configured
    • Allows real-time testing of policy blocks

Important Notes for Reviewers

1. Hook-Based Architecture

The previous manual insertion of enforcePolicies is replaced by the BaseTool template method pattern. This ensures consistent enforcement across all tools:

// in BaseTool.execute()
await this.preToolExecutionHook(context, params);
const normalisedParams = await this.normalizeParams(params, context, client);
await this.postParamsNormalizationHook(context, params, normalisedParams); // <-- Most blocks happen here
const coreActionResult = await this.coreAction(normalisedParams, context, client);
await this.postCoreActionHook(context, params, normalisedParams, coreActionResult);
// ...

2. Explicit Policy Configuration

Policies now explicitly state where they apply:

relevantTools = ['transfer_hbar_tool', ...];
affectedPoints = [ToolExecutionPoint.PostParamsNormalization];

3. Testing Requirements

Warning

Before merging, we need to add:

  • Unit tests for each example policy
  • Integration tests verifying hook execution order
  • Edge case testing (e.g., policy with empty config, multiple policies blocking)

4. Documentation Needed

Important

A comprehensive tutorial is required covering:

  • How to implement custom policies
  • Understanding the tool lifecycle hooks
  • Using Zod schemas for typed params
  • Composing multiple policies

Files Changed

New Files

  • src/shared/policy.ts
  • src/shared/policies/*.ts (Policy implementations)
  • examples/policy-example/ (entire directory)

Modified Files

  • src/shared/tools.ts - BaseTool updated with hooks
  • src/shared/configuration.ts - Added policies to Context
  • src/shared/index.ts - Exports

How to Test

  1. Navigate to typescript/examples/policy-example
  2. Configure .env
  3. Run npm install && npm start
  4. Try prompts like "Transfer 1 HBAR to 0.0.12345" (blocked by RequiredMemoPolicy)

Related issue(s):

Fixes #416

Notes for reviewer:

Checklist

  • Documented (Code comments, README, etc.)
  • Tested (unit, integration, etc.)

- Added standard policies like `MaxHbarTransferPolicy`, `TokenAllowlistPolicy`, and `RequiredMemoPolicy`.
- Integrated policy checks in `CREATE_ACCOUNT_TOOL` and `TRANSFER_HBAR_TOOL`.
- Added example configuration and setup for policies in the new `policy-example` directory.

Signed-off-by: skurzyp-blockydevs <[email protected]>
…policies in relevant tools

- Introduced two new policies: `ImmutabilityPolicy` to block modifications/deletions of specific accounts and tokens, and `NoInfiniteSupplyPolicy` to prevent creating tokens with infinite supply.
- Integrated policy enforcement into `UPDATE_ACCOUNT_TOOL`, `DELETE_ACCOUNT_TOOL`, `CREATE_FUNGIBLE_TOKEN_TOOL`, `CREATE_NON_FUNGIBLE_TOKEN_TOOL`, and `UPDATE_TOKEN_TOOL`.
- Updated documentation with descriptions of the new policies and their applicable tools.

Signed-off-by: skurzyp-blockydevs <[email protected]>
- Replaced manual runtime checks for `accountId` and `tokenId` with Zod schemas.
- Simplified and improved type safety for `shouldBlock` logic.

Signed-off-by: skurzyp-blockydevs <[email protected]>
@skurzyp-blockydevs skurzyp-blockydevs self-assigned this Jan 16, 2026
@skurzyp-blockydevs skurzyp-blockydevs requested review from a team as code owners January 16, 2026 10:07
@skurzyp-blockydevs skurzyp-blockydevs marked this pull request as draft January 16, 2026 10:08
@skurzyp-blockydevs skurzyp-blockydevs changed the title feat: poc policies feat: Universal Policy System for Hedera Agent Kit [PoC] Jan 16, 2026
- Removed tools: `burn_token_tool`, `wipe_token_tool`, `pause_token_tool`, `unpause_token_tool`, `freeze_token_tool`, `unfreeze_token_tool`, `grant_token_kyc_tool`, and `revoke_token_kyc_tool`.
- Improved clarity and maintainability of the allowlist.

Signed-off-by: skurzyp-blockydevs <[email protected]>
# Conflicts:
#	typescript/src/plugins/core-account-plugin/tools/account/transfer-hbar.ts
#	typescript/src/shared/configuration.ts
#	typescript/src/shared/policy.ts
…affectedPoints` to policies

- Simplified tools by removing inline policy checks.
- Introduced the `affectedPoints` attribute in policies to standardize enforcement integration.
- Adjusted relevant policy implementations to utilize `ToolExecutionPoint.PostParamsNormalization`.

Signed-off-by: skurzyp-blockydevs <[email protected]>
- Added FIXME comments to indicate policies referencing tools that currently lack policy support.
- Commented out unused tools in policy definitions for clarity.
- Adjusted `policy-example` to reflect limitations in functional policies.

Signed-off-by: skurzyp-blockydevs <[email protected]>
…s` and enhance hook implementations

- Added `PolicyValidationParams` for consistent parameter handling across policies.
- Refactored `ToolExecutionPoint` to improve clarity and add support for `PostCoreAction` and `PostSecondaryAction`.
- Updated all policies to use `validationParams`, providing access to context-specific data like raw and normalized parameters.
- Renamed `action` to `coreAction` and `submit` to `secondaryAction` in tools to improve semantic clarity.
- Adjusted tool execution flow to incorporate new hooks and parameter structure.

Signed-off-by: skurzyp-blockydevs <[email protected]>
…s` and enhance hook implementations

- Added `PolicyValidationParams` for consistent parameter handling across policies.
- Refactored `ToolExecutionPoint` to improve clarity and add support for `PostCoreAction` and `PostSecondaryAction`.
- Updated all policies to use `validationParams`, providing access to context-specific data like raw and normalized parameters.
- Renamed `action` to `coreAction` and `submit` to `secondaryAction` in tools to improve semantic clarity.
- Adjusted tool execution flow to incorporate new hooks and parameter structure.

Signed-off-by: skurzyp-blockydevs <[email protected]>
…in `TRANSFER_HBAR_TOOL`

Signed-off-by: skurzyp-blockydevs <[email protected]>
@jaycoolslm
Copy link
Contributor

Love this! Looks awesome. And really clean

@walkerlj0
Copy link
Contributor

walkerlj0 commented Jan 21, 2026

Adding in this design decision doc:

Approach 1: Single Function Injection

This approach involves injecting a single, user-defined validation function into the tool execution flow immediately after parameter normalization. It relies on a lightweight interface where users define blocking logic based on the normalized parameters of the tool.

Pros:

  • High Flexibility: Users have complete control over the validation logic and can utilize existing internal policies.
  • Minimal Code Impact: Requires very few changes to the core codebase; tools simply need to call the enforcer function at a specific line.
  • Backward Compatibility: Existing implementations remain functional without breaking changes.
  • Simplicity: The core interface is minimal and non-intrusive.

Cons:

  • Fixed Execution Timing: Policies can only be enforced at one specific moment (post-normalization), lacking granularity for pre-checks or post-execution auditing.
  • Type Safety Challenges: Hard to implement strict TypeScript support. Parameters are often treated as any, forcing users to manually cast types or inspect schemas, which increases the risk of runtime errors.
  • Implementation Burden: The logic for "enforcement" is manual; if a tool developer forgets to call the function, the policy is bypassed.

Detailed Technical Implementation

1. Context & Solution
Currently, the Hedera Agent Kit allows autonomous on-chain transactions without built-in limits. This approach introduces a "DIY" Policy system where users define a Policy interface. The enforcement point is hardcoded inside the tool execution flow: after parameter normalization but before transaction execution.

2. The Policy Interface
A simple interface allows users to define which tools a policy applies to and the logic for blocking actions.

export interface Policy {
    name: string;
    description?: string;
    relevantTools: string[]; // List of tool names this applies to
    shouldBlock: (params: any) => boolean | Promise<boolean>;
}

3. Tool Integration Pattern
The enforcement logic is manually inserted into the tool's execution function.

// src/plugins/core-account-plugin/tools/transfer-hbar.ts
const transferHbar = async (client: Client, context: Context, params: any) => {
  // 1. Normalisation
  const normalisedParams = await HederaParameterNormaliser.normaliseTransferHbarParams(params, context, client);

  // 2. Policy Check (The Injection Point)
  if (context.policies) {
    await enforcePolicies(context.policies, 'transfer_hbar', normalisedParams);
  }

  // 3. Execution
  const tx = HederaBuilder.transferHbar(normalisedParams);
  return await handleTransaction(tx, client, context);
};

4. Known Limitations (Typing & Complexity)
Because policies are generic, they interact with loosely typed parameters (any). Writing a policy that spans multiple tools (e.g., "Limit HBAR spend") is difficult because different tools structure their data differently (e.g., transfer_hbar uses hbar_transfers arrays, while create_account uses an initial_balance field). This leads to complex, conditional logic within the policy code:

// Example of the complexity required to handle multiple tools
if (params.hbarTransfers) { /* check transfer array */ }
if (params.initial_balance) { /* check balance field */ }

Approach 2: Lifecycle Hooks (Tools V2)

This approach refactors the tool architecture to be modular, introducing a "Tools V2" specification. The execute method is broken down into distinct lifecycle steps (hooks), allowing policies to intervene at multiple stages (e.g., Pre-execution, Post-normalization, Post-execution).

Pros:

  • Extreme Modularity: Users can define exactly when a policy runs (e.g., blocking raw input vs. auditing the final transaction object).
  • Future-Proofing: Hooks are a versatile pattern that can be extended for logging, debugging, compliance auditing, or analytics in the future.
  • Automatic Enforcement: By using a BaseTool class, policy checks are built into the lifecycle. Subclasses (individual tools) do not need to manually call an enforcement function, reducing human error.
  • Backward Compatibility: Tools V1 and V2 can coexist, allowing for an optional, gradual migration.

Cons:

  • Higher Implementation Effort: Requires introducing a new BaseTool architecture and refactoring tools to "V2" to utilize the hooks.
  • Type Safety Challenges: Similar to Approach 1, strict typing across generic hooks is difficult, though slightly more manageable via the base class.
  • Migration Overhead: While optional, full policy support requires migrating tools to the new V2 standard.

Detailed Technical Implementation

1. Context & Architecture
This approach builds upon a "Multistep Tool" architecture. The BaseTool class implements a template method pattern for execution, invoking hooks at specific ToolExecutionPoints.

2. Lifecycle Execution Points
Policies can now specify an affectedPoints array to target specific stages:

Execution Point Description Use Case
PreToolExecution Before any processing. Access control on raw inputs (e.g., block specific users).
PostParamsNormalization After validation & normalization. Standard limits (e.g., Max HBAR amount).
PostCoreAction After transaction creation, pre-execution. Deep inspection of the transaction object.
PostToolExecution After execution completes. Logging, auditing, or triggering secondary actions.

3. BaseTool Logic
The BaseTool handles the orchestration, ensuring policies are always checked.

// Abstract BaseTool logic
async execute(context: Context, params: any) {
    // Hook 1: Pre-Execution
    await this.preToolExecutionHook(context, params);
    
    const normalisedParams = await this.normalizeParams(params, context);
    
    // Hook 2: Post-Normalization (Primary Policy Point)
    await this.postParamsNormalizationHook(context, params, normalisedParams); 
    
    const coreActionResult = await this.coreAction(normalisedParams, context);
    
    // Hook 3: Post-Action
    await this.postCoreActionHook(context, params, normalisedParams, coreActionResult);
    
    return coreActionResult;
}

4. Policy Configuration Example
Policies explicitly state where they apply, making the intent clearer.

export class MaxHbarAmountPolicy implements Policy {
    name = 'Max HBAR Amount';
    relevantTools = ['transfer_hbar_tool', 'create_account_tool'];
    affectedPoints = [ToolExecutionPoint.PostParamsNormalization];

    // Implementation...
}

5. Migration Strategy

  • Documentation: Clear distinction between V1 (legacy) and V2 (hook-supported) tools.
  • Examples: Providing a reference implementation of a V2 tool and a Policy-Example directory demonstrating the hook flow.

Summary & Recommendation

I recommend Approach 2, as it provides significantly greater flexibility and follows a clean, modular design. While the implementation effort is not minimal, it is reasonable and well-justified by the long-term benefits.

@walkerlj0
Copy link
Contributor

Option 2 does make the most sense. It stinks that it adds complexity, but we will be able to create better policies - e.g. stop a bad input, but also stop a transaction that violates limits.
To implement option 2, however, do we have to redefine all of the tools? What is the lift on this -- are we going to have to think through each tool and say - alright, what hooks do we want to put in, etc?

@skurzyp-blockydevs
Copy link
Contributor Author

Yes, we’ll have to redefine each tool, but the impact shouldn’t be too big. We mainly need to split the current logic of each tool into steps and add hooks between them. And for each tool the process will be mostly the same.
For the JS SDK, we have unit tests for the tools that will need to be rewritten. We’ve already decided to skip these tests in the Python SDK since they don’t bring much value there, so no changes will be needed on that side.
As a result of introducing this approach, we will need to:

  1. Update each tool to match the V2 tool abstraction
  2. Update unit tests in the JS SDK
  3. Clearly document how hooks work, what’s possible, and what’s not
  4. Provide ready-to-use example policies included in the SDK
  5. Clearly explain how the new V2 tool abstraction differs from the old one, including backward compatibility and migration guidance
  6. Explore a typing mechanism to help define policies — this may be tricky in TypeScript and likely impossible in Python, so very detailed documentation will be needed (possibly autogenerated from zod/pydantic schemas)

These are the main points I can see for now. Additional ideas may come up during development, as this architecture opens up a lot of new possibilities.
Happy to provide more detailed task descriptions later if needed.

Comment on lines 125 to 127
if (!hook.relevantTools.includes(this.method)) {
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the BaseTool shouldn't care if it should execute the hook or not, the hook implementation itself should handle it

…licies

- Added comprehensive unit test coverage for the following policies:
  - `RequiredMemoPolicy`
  - `TokenAllowlistPolicy`
  - `NoInfiniteSupplyPolicy`
  - `ImmutabilityPolicy`
  - `MaxHbarTransferPolicy`
- Enhanced policy validation logic to ensure edge case handling.
- Updated `policy-hooks` test to include additional hook coverage.

Signed-off-by: skurzyp-blockydevs <[email protected]>
@skurzyp-blockydevs skurzyp-blockydevs marked this pull request as ready for review February 11, 2026 14:38
@skurzyp-blockydevs skurzyp-blockydevs changed the title feat: Universal Policy System for Hedera Agent Kit [PoC] feat: Universal Policy System for Hedera Agent Kit Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants