Skip to main content
@handlebar/core is the framework-agnostic foundation that all Handlebar adapters build on. If your agent framework doesn’t have a pre-built adapter, or you’ve built a custom agent loop, you can wire Handlebar in directly with a handful of function calls. This guide covers the full integration - from initial setup through tool governance, LLM event logging, and per-user enforcement.

Installation

Set your API key (or pass it explicitly in config):

The client - initialise once

HandlebarClient is a long-lived object that manages agent registration, rule fetching, and audit event delivery. Create it once at application startup, not per-request.
Handlebar.init is async because it registers the agent with the Handlebar API and fetches its configured rules. Everything else waits on this internally - you can await hb.ready() if you need to be certain registration is complete before proceeding, but it is not normally required.

Init options

Runs - one per agent invocation

A run represents a single agent invocation from start to finish. It carries the run ID, tracks tool call history and token usage, and emits audit events throughout its lifetime. Create a run at the start of each request and end it when the agent finishes.
run.end() flushes all pending audit events before returning, so the process can exit cleanly immediately after.

End statuses

Lifecycle hooks

Lifecycle hooks are the calls you make on run as your agent progresses. They are the primary integration surface - each one corresponds to a phase of the agent loop.

Before a tool executes

Call this immediately before invoking any tool. It evaluates the call against your configured rules and returns a Decision.
The Decision shape:
BLOCK + CONTINUE means the tool should be skipped but the agent loop can continue - the blocked message is typically returned to the LLM so it can respond gracefully. BLOCK + TERMINATE means the run should stop entirely. Throw an error that propagates up through your agent loop, catch it at the top level, and call run.end("interrupted").

After a tool returns

Call this after every tool invocation, regardless of success or failure. It logs the result and evaluates any tool.after rules (e.g. inspecting output content or checking data exfiltration patterns).
afterTool also returns a Decision (evaluated at the tool.after phase). Most integrations do not need to act on it, but you can check decision.verdict if you want to apply post-execution governance.

Before an LLM call

Call this before each call to the language model, passing the messages being sent. It emits message.raw.created audit events for each message, enabling full conversation logging on the platform.
beforeLlm is optional - skipping it means conversation content won’t appear in audit logs, but tool governance still works fully.

After an LLM call

Call this after the LLM responds. It logs the response content, records token usage for cost tracking, and emits an llm.result event.
Like beforeLlm, this is optional but enables token-based budget enforcement and spend tracking on the platform.

Wiring it all together - a minimal agent loop

Passing the run through async contexts

If your tool implementations live in separate modules and can’t receive run as a parameter, use withRun to bind the run to the current async context. Any code running inside the callback can then retrieve it with getCurrentRun().
This is how the pre-built adapters (@handlebar/langchain, @handlebar/ai-sdk-v5) work internally - they bind the run in withRun at the executor level, so tool wrappers don’t need an explicit reference.

Tool metadata - tags

Tool tags are string labels that describe a tool’s nature or capability class. You attach them to tools at registration time and pass them through on every beforeTool / afterTool call. Tags are what allow you to write rules that apply to groups of tools rather than individual ones - for example: “block any external-write tool after 11pm”, or “require human review for any pii-access tool when the user is on the free tier”.

Registering tools with tags

Declare tools and their tags at init time so the Handlebar platform knows about them:
For tools added dynamically after init:

Passing tags on each call

Pass the same tags to beforeTool and afterTool so the rule engine has full context at evaluation time:
Tags registered at init and tags passed at call time are both used by the rule engine. Registering at init gives the platform a full picture of your agent’s tool inventory; passing at call time ensures correctness if tools are added dynamically or tags change at runtime.

Actor - per-user enforcement

The actor is the end user or system identity the agent is acting on behalf of during a run. Providing it enables Handlebar to enforce per-user rules - for example: rate limiting a single user’s tool usage, applying stricter data controls to users tagged "region:eu", or capping spend per user tier. Pass the actor when starting the run:

Full actor schema

Using metadata for group-based rules

Metadata is where per-user and per-group rule conditions come from. For example, attaching { tier: "free", region: "eu" } lets you write rules like “block pii-access tools for free-tier users” or “require human review for any write tool when region is eu”.
The platform registers actor metadata the first time it is provided. You don’t need to send it on every run - only when it changes.

Sessions

Group multiple runs under a single session to get end-to-end analytics across a multi-turn conversation:

Shutdown

Flush pending audit events before the process exits:
Last modified on March 2, 2026