Hedera Hook Creation
This skill should be used when the user asks to "create a hedera hook", "write an agent kit hook", "extend AbstractHook", "add a tool lifecycle hook", "log tool executions", "audit trail hook", "track tool calls", or needs guidance on building observation/logging/metrics extensions for Hedera Agent Kit tools. Hooks are non-blocking — for blocking validation rules use the policy skill instead.
How do I install this agent skill?
npx skills add https://github.com/hedera-dev/hedera-skills --skill hedera-hook-creationIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides documentation and code examples for implementing lifecycle hooks in the Hedera Agent Kit. No security risks or malicious patterns were detected.
- Socketpass
No alerts
- Snykwarn
Risk: MEDIUM · 1 issue
What does this agent skill do?
Creating Hedera Agent Kit Hooks
Hooks are non-blocking extensions that run around the BaseTool lifecycle. They observe and can enrich execution: log calls, write audit trails, track metrics, persist state on Context. They do not block tool execution — that's the job of policies (see agent-kit-policy skill).
Hooks fire only for tools that extend
BaseTool. A tool that implements theToolinterface directly without extendingBaseToolbypasses the hook system entirely.
When to write a hook vs. a policy
| If you want to… | Use |
|---|---|
| Log/audit/track tool calls | Hook |
Enrich Context (counters, session state) | Hook |
| Stop a call from happening when a condition holds | Policy (see agent-kit-policy) |
| Reject specific tool methods | Policy |
Built-in hooks include HcsAuditTrailHook and HolAuditTrailHook (HCS-backed audit trails) — both live in @hashgraph/hedera-agent-kit/hooks.
The 7-stage lifecycle
BaseTool.execute() runs:
[1] preToolExecutionHook ← hook fires
[2] normalizeParams (tool internal)
[3] postParamsNormalizationHook ← hook fires
[4] coreAction (tool internal)
[5] postCoreActionHook ← hook fires
[6] secondaryAction (tool internal)
[7] postToolExecutionHook ← hook fires
Hooks tap into stages 1, 3, 5, 7. They never fire at stages 2, 4, 6.
For full param shapes at each stage see references/lifecycle.md.
AbstractHook contract
import { AbstractHook } from '@hashgraph/hedera-agent-kit';
export abstract class AbstractHook {
abstract name: string; // human-readable identifier
abstract description?: string; // what the hook does
abstract relevantTools: string[]; // tool methods this hook cares about
// Override only the stages you need; defaults are no-ops.
preToolExecutionHook(params, method): Promise<any>;
postParamsNormalizationHook(params, method): Promise<any>;
postCoreActionHook(params, method): Promise<any>;
postToolExecutionHook(params, method): Promise<any>;
}
Critical: the method signature is (params, method) — params is one of four typed objects, and context lives inside it (params.context). It is not a separate first argument.
| Stage | Params type | Notable fields |
|---|---|---|
| 1 | PreToolExecutionParams | context, rawParams, client |
| 3 | PostParamsNormalizationParams | + normalisedParams |
| 5 | PostCoreActionParams | + coreActionResult |
| 7 | PostSecondaryActionParams | + toolResult |
Skeleton
import {
AbstractHook,
Context,
PreToolExecutionParams,
PostSecondaryActionParams,
} from '@hashgraph/hedera-agent-kit';
export class MyHook extends AbstractHook {
name = 'My Hook';
description = 'What this hook does, in one sentence';
relevantTools = ['transfer_hbar', 'create_token'];
async preToolExecutionHook(params: PreToolExecutionParams, method: string) {
if (!this.relevantTools.includes(method)) return;
// ... your pre-execution logic ...
// params.context, params.rawParams, params.client
}
async postToolExecutionHook(params: PostSecondaryActionParams, method: string) {
if (!this.relevantTools.includes(method)) return;
// params.toolResult is the final tool output
}
}
The first line of every hook method should filter by relevantTools. The kit dispatches all hooks for all tool calls — relevantTools is informational unless you act on it.
Registering hooks
Hooks go on Context.hooks. The toolkit threads that context through to BaseTool.execute(), where the lifecycle dispatches them.
import { AgentMode } from '@hashgraph/hedera-agent-kit';
import { allCorePlugins } from '@hashgraph/hedera-agent-kit/plugins';
import { HederaLangchainToolkit } from '@hashgraph/hedera-agent-kit-langchain';
import { MyHook } from './my-hook';
const toolkit = new HederaLangchainToolkit({
client,
configuration: {
plugins: allCorePlugins,
context: {
mode: AgentMode.AUTONOMOUS,
hooks: [new MyHook()],
},
},
});
Multiple hooks can coexist — they run in array order at each stage. Policies (see agent-kit-policy) are also AbstractHook instances and go in the same array.
Multi-tool typing patterns
Hook params are generic (any) — the kit can't infer per-tool types because one hook can target many tools with different schemas. Three patterns:
1. Universal logic
When the hook is parameter-agnostic (counting calls, logging the whole payload as JSON):
async postToolExecutionHook(params: PostSecondaryActionParams, method: string) {
console.log(`[${method}]`, JSON.stringify(params.rawParams));
}
2. Type guards (small known set)
Switch on method, narrow with a cast or guard:
async postParamsNormalizationHook(
params: PostParamsNormalizationParams,
method: string,
) {
switch (method) {
case 'transfer_hbar':
case 'transfer_hbar_with_allowance': {
const p = params.normalisedParams as { transfers: Array<{ amount: number }> };
const total = p.transfers.reduce((s, t) => s + t.amount, 0);
console.log(`[${method}] total HBAR: ${total}`);
break;
}
}
}
3. Strategy map (pluggable, third-party tools)
Accept a Record<string, (params: any) => SomeResult> in the constructor. Validate each additionalTool has a strategy, throw at construction time if missing. This is the pattern used by MaxRecipientsPolicy.
State on Context
params.context is the same Context instance for the whole agent run. Use it as a per-session scratchpad:
async preToolExecutionHook(params: PreToolExecutionParams, method: string) {
if (!this.relevantTools.includes(method)) return;
const counts = ((params.context as any).callCounts ??= {} as Record<string, number>);
counts[method] = (counts[method] ?? 0) + 1;
}
(Context is structurally typed; cast to attach ad-hoc state. If you publish the hook, document the keys you write.)
Best practices
- Filter by
relevantToolsat the top of every method — cheap and idiomatic - Wrap in
try/catchif the hook does I/O (HCS write, HTTP) — a thrown hook aborts the whole tool call - Keep hooks lightweight — they sit on the hot path
- Don't block — if you need to stop execution, write a policy
- Don't mutate
rawParamsornormalisedParamsunless you really mean to; subsequent hooks see your changes - Use
descriptionto document side-effects (HCS topic writes, external API calls) so policy authors / agent operators know what the hook does - Tag log lines with
[<hook-name>]for easy grep
Resources
references/hook-interface.md— full param-type definitions, all four hook stagesreferences/lifecycle.md— what fires when, aroundBaseTool'snormalizeParams/coreAction/secondaryActionexamples/tx-timing-hook/— minimal hook that times tool execution and logs duration
Common imports
import {
AbstractHook,
Context,
PreToolExecutionParams,
PostParamsNormalizationParams,
PostCoreActionParams,
PostSecondaryActionParams,
} from '@hashgraph/hedera-agent-kit';
// Built-in implementations live in the /hooks subpath
import { HcsAuditTrailHook, HolAuditTrailHook } from '@hashgraph/hedera-agent-kit/hooks';
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/hedera-dev/hedera-skills/hedera-hook-creation">View Hedera Hook Creation on skillZs</a>