Skip to main content
For agents built without a directly supported framework, handlebar-core provides the primitives to wire governance into any agent loop.

Prerequisites

Install

How it fits into your agent

A typical agent loop looks like this:
Handlebar wraps each step:
  • start_run / end_run — bracket the entire agent invocation. Handlebar uses these to group all events and enforce run-level budgets.
  • before_llm / after_llm — record the messages sent to the model and the response (including token usage) for the audit log.
  • before_tool — evaluate the proposed tool call against your configured policies. Returns a Decision that tells you whether to proceed, block, or stop the run.
  • after_tool — record the tool’s result.

The client

HandlebarClient is the top-level object that connects your agent to the Handlebar platform. It handles authentication, registers your agent, and manages the audit event stream. Initialise it once at application startup — not once per request or per run. The client is designed to be shared: it maintains a single connection to the platform and fans out events for all concurrent runs.
For sync code, use init_sync:
AgentDescriptor.slug is the stable identifier for your agent on the Handlebar platform. It is how you match runs in the dashboard to a specific agent in your codebase. Use a short, unique, URL-safe string — e.g. "travel-booking-agent".

Runs

A run represents one end-to-end invocation of your agent — from the first message in to the final response out. Everything that happens in between (LLM calls, tool calls, decisions) is attributed to that run in the audit log. Start a new run at the beginning of each invocation and end it when the agent finishes:
run_id must be unique per invocation. A UUID works well:
The run object is what you interact with throughout the agent loop. Hold a reference to it for the lifetime of the invocation — typically as a local variable in the function that runs your agent.

Agent lifecycle

Before 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.
before_llm is optional - skipping it means conversation content won’t appear in audit logs, but tool governance still works fully.

After 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 before_llm, this is optional but enables token-based budget enforcement and spend tracking on the platform.

Before tool call

before_tool is where governance actually happens. It evaluates the proposed call against your configured rules. and returns a Decision indicating whether to allow or block the call.
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 tool call

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).

Sync usage

Every async method has a _sync counterpart:

Additional config

Tool tags

Tags let you group tools by function so governance rules apply to the whole category rather than individual tool names. For example:
  • Rate-limit all "search" tools regardless of which search API they call
  • Block data exfiltration by preventing a "pii-read" result flowing into an "external" tool
  • Require human review before any "write" action
Register tags when you configure the client, and Handlebar will look them up automatically each time before_tool is called:
If the tag set for a tool varies per invocation, pass tags directly at call time instead:

End-user / actor configuration

Attach the identity of the end-user to the run so that Handlebar can:
  • Enforce per-user budgets — e.g. each user gets a fixed number of tool calls or tokens per day
  • Attribute every audit event to a specific user
  • Apply per-user rules — e.g. restrict which tools a given user role can invoke
Pass the actor when starting the run:
external_id can be any stable identifier your application uses — a database ID, a UUID, an email address, etc. Handlebar stores it opaquely and uses it to aggregate usage and enforce per-user policies. session_id is optional but recommended when an agent can be invoked multiple times within a single user session — it lets the platform group related runs together.

Misc options

Last modified on March 2, 2026