Hedera Policy Creation — Claude Code skill: how to install
Guides developers in creating blocking validation rules for Hedera Agent Kit tools. Useful when setting transaction thresholds, recipient allowlists, or execution limits.
npx skills add hedera-dev/hedera-skills --skill hedera-policy-creation -a claude-code
npx skills add hedera-dev/hedera-skills --skill agent-kit-policy -a claude-codeWhat Hedera Policy Creation does
This skill guides the creation of blocking validation rules that abort tool execution in Hedera Agent Kit by throwing errors. Rules inherit from AbstractPolicy, extending the core hook system. Developers override specific lifecycle stage checks such as shouldBlockPreToolExecution for raw inputs, shouldBlockPostParamsNormalization for normalized arguments, or shouldBlockPostCoreAction before transaction submission.
Policies run within the Context.hooks array in registration order. The first rule returning true terminates execution and returns the error to the LLM agent. Because the toolkit dispatches checks across every tool call, each handler must filter methods against relevantTools explicitly.
Tools that implement the Tool interface directly without extending BaseTool bypass policies entirely. For non-blocking tasks like event logging or context enrichment, the kit requires observation hooks instead.
What it helps with
- block Hedera tool execution when specific safety conditions fail
- cap recipient counts for token transfers and airdrops
- enforce maximum transaction amount thresholds for HBAR
- restrict agent tool calls to business hours
Example prompts for the agent
- "Write a Hedera Agent Kit policy class that blocks transfers exceeding a threshold"
- "Create an AbstractPolicy rule to restrict recipients to an account allowlist"
- "How do I implement rate limiting for Hedera Agent Kit tools using policies?"
How to install Hedera Policy Creation
1Skills CLI (Vercel)
npx skills add hedera-dev/hedera-skills --skill hedera-policy-creation -a claude-codenpx skills add hedera-dev/hedera-skills --skill agent-kit-policy -a claude-code2Manually: put the files in place
~/.claude/skills/agent-kit-policy/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/agent-kit-policy/
1Skills CLI (Vercel)
npx skills add hedera-dev/hedera-skills --skill agent-kit-policy -a codex2Manually: put the files in place
.agents/skills/agent-kit-policy/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add hedera-dev/hedera-skills --skill agent-kit-policy -a cursor2Manually: put the files in place
.cursor/skills/agent-kit-policy/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install hedera-dev/hedera-skills agent-kit-policy2Manually: put the files in place
.github/skills/agent-kit-policy/SKILL.md1Skills CLI (Vercel)
npx skills add hedera-dev/hedera-skills --skill agent-kit-policy -a gemini-cli2Platform CLI
gemini skills install https://github.com/hedera-dev/hedera-skills --path plugins/agent-kit-plugin/skills/agent-kit-policyManually: put the files in place
.devin/skills/agent-kit-policy/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add hedera-dev/hedera-skills --skill agent-kit-policy -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about Hedera Policy Creation
How to install Hedera Policy Creation in Claude Code?
Run the command npx skills add hedera-dev/hedera-skills --skill hedera-policy-creation -a claude-code.
What does Hedera Policy Creation do?
It provides patterns and architecture rules for building blocking validation policies that halt Hedera Agent Kit tools before transactions execute.
What is the difference between a policy and a hook in Hedera Agent Kit?
Hooks handle passive observation, telemetry, and context enrichment without stopping execution. Policies extend hooks specifically to abort tool runs by throwing errors.
Source text
SKILL.md · Apache-2.0
---
name: Hedera Policy Creation
description: This skill should be used when the user asks to "create a hedera policy", "block a tool", "limit recipients", "extend AbstractPolicy", "reject a tool", "enforce a threshold", "rate limit a tool", "block transfers over X", "allowlist tools", or needs guidance on writing blocking validation rules for Hedera Agent Kit tools. Policies block tool execution by throwing — for non-blocking observation use the hook skill instead.
version: 1.0.0
---
# Creating Hedera Agent Kit Policies
Policies are blocking validation rules layered on the `BaseTool` lifecycle. A policy checks the call at one or more stages and **throws** to abort execution — the error propagates back to the LLM agent. Use them to enforce thresholds, allowlists/blocklists, rate limits, business hours, compliance rules, etc.
> Policies fire **only** for tools that extend `BaseTool`. A tool that implements the `Tool` interface directly without extending `BaseTool` bypasses policies entirely.
## Policy vs. hook
| Goal | Use |
|---|---|
| Stop a call when a condition holds | **Policy** (this skill) |
| Reject specific tool methods | **Policy** |
| Threshold / rate-limit / allowlist / blocklist | **Policy** |
| Log, audit, enrich context | **Hook** (see `agent-kit-hook` skill) |
`AbstractPolicy` extends `AbstractHook`, so policies live in the same `Context.hooks` array and run through the same lifecycle.
Built-in policies live in `@hashgraph/hedera-agent-kit/policies`:
- `RejectToolPolicy(relevantTools: string[])` — blocks the listed tool methods unconditionally
- `MaxRecipientsPolicy(maxRecipients, additionalTools?, customStrategies?)` — caps recipient count for transfer/airdrop tools
## How blocking actually works
You **never** override `preToolExecutionHook` or any of the other `*Hook` methods on a policy — those are wired up by the base class to call your `shouldBlock...` method and throw if it returns `true`.
Override one or more of these instead:
```typescript
abstract class AbstractPolicy extends AbstractHook {
abstract name: string;
abstract description?: string;
abstract relevantTools: string[];
protected shouldBlockPreToolExecution(params, method): boolean | Promise<boolean>;
protected shouldBlockPostParamsNormalization(params, method): boolean | Promise<boolean>;
protected shouldBlockPostCoreAction(params, method): boolean | Promise<boolean>;
protected shouldBlockPostSecondaryAction(params, method): boolean | Promise<boolean>;
}
```
Defaults all return `false` (allow). Returning `true` causes the base class to throw a generic block error. Throwing your own error from `shouldBlock...` lets you control the message.
> **Don't override `preToolExecutionHook` etc.** — those are `@internal` on `AbstractPolicy`. They drive the block flow. If you override them, your policy will never block.
## Picking the stage
| Stage | What's available | Good for |
|---|---|---|
| `shouldBlockPreToolExecution` | `rawParams` only | allowlist/blocklist by tool method, time-based, rate limits |
| `shouldBlockPostParamsNormalization` | `+ normalisedParams` | thresholds on resolved values, recipient counts |
| `shouldBlockPostCoreAction` | `+ coreActionResult` | inspect the built `Transaction` before submission |
| `shouldBlockPostSecondaryAction` | `+ toolResult` | very rare — call already executed; usually too late to "block" |
Most policies live at stage 1 (cheapest) or stage 3 (after defaults are filled in).
## Skeleton
```typescript
import {
AbstractPolicy,
PreToolExecutionParams,
PostParamsNormalizationParams,
} from '@hashgraph/hedera-agent-kit';
export class MyPolicy extends AbstractPolicy {
name = 'My Policy';
description = 'What this policy blocks, in one sentence';
relevantTools = ['transfer_hbar', 'transfer_fungible_token'];
// Stage 1 — block by raw input or method
protected shouldBlockPreToolExecution(
params: PreToolExecutionParams,
method: string,
): boolean {
if (!this.relevantTools.includes(method)) return false;
// throw new Error('<custom message>') for tailored errors
return /* condition */ false;
}
// Stage 3 — block on resolved/normalised values
protected shouldBlockPostParamsNormalization(
params: PostParamsNormalizationParams,
method: string,
): boolean {
if (!this.relevantTools.includes(method)) return false;
return /* condition */ false;
}
}
```
`relevantTools` is informational unless you check it — the kit dispatches your policy for every tool call. Always filter by `method` first thing in the method.
## Registering policies
Policies are `AbstractHook` instances. They go in the same `Context.hooks` array as observation hooks:
```typescript
import { AgentMode } from '@hashgraph/hedera-agent-kit';
import { allCorePlugins } from '@hashgraph/hedera-agent-kit/plugins';
import { RejectToolPolicy, MaxRecipientsPolicy } from '@hashgraph/hedera-agent-kit/policies';
import { HederaLangchainToolkit } from '@hashgraph/hedera-agent-kit-langchain';
import { MyPolicy } from './my-policy';
const toolkit = new HederaLangchainToolkit({
client,
configuration: {
plugins: allCorePlugins,
context: {
mode: AgentMode.AUTONOMOUS,
hooks: [
new RejectToolPolicy(['delete_account', 'freeze_token']),
new MaxRecipientsPolicy(5),
new MyPolicy(),
],
},
},
});
```
Policies run in array order at each stage — the first one to return `true` (or throw) wins.
## Common patterns
### Threshold
```typescript
protected shouldBlockPostParamsNormalization(
params: PostParamsNormalizationParams,
method: string,
): boolean {
if (!this.relevantTools.includes(method)) return false;
const p = params.normalisedParams as { amount?: number };
return (p.amount ?? 0) > this.maxAmount;
}
```
### Allowlist / Blocklist
```typescript
protected shouldBlockPreToolExecution(
params: PreToolExecutionParams,
method: string,
): boolean {
if (!this.relevantTools.includes(method)) return false;
const accountId = (params.rawParams as { accountId?: string }).accountId;
return accountId !== undefined && this.blockedAccounts.has(accountId);
}
```
### Time-based
```typescript
protected shouldBlockPreToolExecution(
_params: PreToolExecutionParams,
method: string,
): boolean {
if (!this.relevantTools.includes(method)) return false;
const hour = new Date().getUTCHours();
return hour < 9 || hour > 17; // outside business hours
}
```
### Rate limiting (uses `Context` as scratchpad)
```typescript
protected shouldBlockPreToolExecution(
params: PreToolExecutionParams,
method: string,
): boolean {
if (!this.relevantTools.includes(method)) return false;
const counters = ((params.context as any).__rateCounts ??= {} as Record<string, number>);
counters[method] = (counters[method] ?? 0) + 1;
return counters[method] > this.maxCallsPerSession;
}
```
### Strategy map (pluggable for custom tools)
When a single policy needs to support tools the kit doesn't know about, accept a `Record<string, (params) => SomeMetric>` and validate that every tool added via `additionalTools` has a strategy. This is the pattern of `MaxRecipientsPolicy`:
```typescript
constructor(
private maxRecipients: number,
additionalTools: string[] = [],
private customStrategies: Record<string, (p: any) => number> = {},
) {
super();
this.relevantTools = [...DEFAULT_TOOLS, ...additionalTools];
for (const tool of additionalTools) {
if (!this.customStrategies[tool]) {
throw new Error(`Custom tool "${tool}" requires a strategy function`);
}
}
}
```
## Custom error messages
The default block error is generic. To control the message, **throw** from your `shouldBlock...` method instead of returning `true`:
```typescript
protected shouldBlockPostParamsNormalization(
params: PostParamsNormalizationParams,
method: string,
): boolean {
if (!this.relevantTools.includes(method)) return false;
const p = params.normalisedParams as { amount?: number };
if ((p.amount ?? 0) > this.maxAmount) {
throw new Error(
`[max-amount-policy] Refusing ${method}: amount ${p.amount} exceeds limit ${this.maxAmount}`,
);
}
return false;
}
```
The agent receives the thrown message verbatim — make it informative.
## Best practices
1. **Filter by `relevantTools`** at the top of every `shouldBlock...` method
2. **Pick the earliest stage** that has the data you need — stage 1 is cheapest
3. **Throw with a clear message** when blocking; the LLM sees this
4. **Don't override `*Hook` methods** — only `shouldBlock...`
5. **Don't do I/O on the hot path** unless you must — policies run synchronously in the lifecycle
6. **Document `relevantTools` defaults** so consumers understand what your policy guards
7. **Tag the error message** with `[<policy-name>]` for easy log correlation
## Resources
- **`references/policy-interface.md`** — full `AbstractPolicy` contract, when each `shouldBlock` fires
- **`examples/max-amount-policy/`** — threshold policy that blocks transfers above a cap
## Common imports
```typescript
import {
AbstractPolicy,
Context,
PreToolExecutionParams,
PostParamsNormalizationParams,
PostCoreActionParams,
PostSecondaryActionParams,
} from '@hashgraph/hedera-agent-kit';
// Built-in implementations live in the /policies subpath
import { RejectToolPolicy, MaxRecipientsPolicy } from '@hashgraph/hedera-agent-kit/policies';
```
© hedera-dev · Apache-2.0 · hedera-dev/hedera-skills