Auto-instrumentation
Collapse the five-step Klent boilerplate into a single function call — and run a full Anthropic tool-use loop end-to-end with one call to runAnthropicAgent.
The raw API gives you five primitives per tool call: log an action_requested,
call evaluateAction, branch on deny, execute the tool, log action_executed
or error. That is explicit and exactly what you want when you control the
call site. But for most agent loops it is repetitive.
Two helpers collapse it.
runTool — one call per tool invocation
import { runTool, KlentClient } from '@klent/sdk';
const klent = new KlentClient({ apiKey: process.env.KLENT_API_KEY! });
const result = await runTool(klent, {
execution_id,
tool: 'transfer_funds',
input: { amount: 50_000, currency: 'USD' },
execute: async (input) => transferFunds(input),
});
switch (result.status) {
case 'allowed':
console.log('output:', result.output);
break;
case 'denied':
console.log('blocked:', result.reason);
break;
case 'error':
console.log('tool threw:', result.error);
break;
}from klent_sdk import KlentClient, run_tool
klent = KlentClient(api_key=os.environ["KLENT_API_KEY"])
result = run_tool(
klent,
execution_id=execution_id,
tool="transfer_funds",
input={"amount": 50_000, "currency": "USD"},
execute=lambda inp: transfer_funds(**inp),
)
if result["status"] == "allowed":
print("output:", result["output"])
elif result["status"] == "denied":
print("blocked:", result["reason"])
elif result["status"] == "error":
print("tool threw:", result["error"])Under the hood, a single runTool call performs:
logEvent({ type: 'action_requested' })evaluateAction(...)- On
deny→ returns{ status: 'denied' }. Klent has already recorded anaction_blockedevent via the evaluate endpoint. - On
modify→ applies the modifications to the input, then callsexecute(modifiedInput). - On
allow→ callsexecute(input). - On success →
logEvent({ type: 'action_executed' }). - On thrown error →
logEvent({ type: 'error' })and returns{ status: 'error' }.
runTool is framework-agnostic — use it wherever you were previously doing
the boilerplate by hand, regardless of LLM provider.
runAnthropicAgent — full loop in one call
For Anthropic tool-use agents the loop is mechanical: call messages.create,
if stop_reason === 'tool_use' iterate tool_use blocks, feed results back,
repeat. Klent ships that loop pre-built and plumbed through the policy engine.
Install the Anthropic SDK alongside Klent — it is an optional peer.
npm install @klent/sdk @anthropic-ai/sdkimport Anthropic from '@anthropic-ai/sdk';
import { KlentClient } from '@klent/sdk';
import { runAnthropicAgent, type KlentTool } from '@klent/sdk/anthropic';
const klent = new KlentClient({ apiKey: process.env.KLENT_API_KEY! });
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const tools: KlentTool[] = [
{
name: 'transfer_funds',
description: 'Move money between accounts.',
input_schema: {
type: 'object',
properties: {
to_account: { type: 'string' },
amount: { type: 'number' },
currency: { type: 'string' },
},
required: ['to_account', 'amount', 'currency'],
},
handler: async (input) => transferFunds(input),
},
];
const result = await runAnthropicAgent({
client: anthropic,
klent,
agentId: 'billing-agent',
model: 'claude-sonnet-4-6',
tools,
messages: [{ role: 'user', content: 'Pay the $10k invoice from Acme Corp.' }],
});
console.log(result.finalText); // assistant's final message
console.log(result.executionId); // view the timeline in the dashboard
console.log(result.turns); // how many LLM round-trips
console.log(result.stopReason); // 'end_turn', 'max_tokens', …pip install "klent-sdk[anthropic]"from anthropic import Anthropic
from klent_sdk import KlentClient
from klent_sdk.anthropic import run_anthropic_agent, KlentTool
klent = KlentClient(api_key=os.environ["KLENT_API_KEY"])
anthropic = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
tools = [
KlentTool(
name="transfer_funds",
description="Move money between accounts.",
input_schema={
"type": "object",
"properties": {
"to_account": {"type": "string"},
"amount": {"type": "number"},
"currency": {"type": "string"},
},
"required": ["to_account", "amount", "currency"],
},
handler=lambda inp: transfer_funds(**inp),
),
]
result = run_anthropic_agent(
client=anthropic,
klent=klent,
agent_id="billing-agent",
model="claude-sonnet-4-6",
tools=tools,
messages=[{"role": "user", "content": "Pay the $10k invoice from Acme Corp."}],
)
print(result.final_text) # assistant's final message
print(result.execution_id) # view the timeline in the dashboard
print(result.turns)
print(result.stop_reason)The orchestrator:
- Starts an execution for you (
agent_id, model, tool count captured as metadata). - Runs the message → tool_use → tool_result cycle until the model stops asking
for tools (or until
maxTurns, default 8). - Evaluates every tool call via
runTool. Denied calls are surfaced back to the model asis_error: truetool_results with the policy's reason — so the model can apologize, ask for a lower amount, or pick a different tool. - Logs a
decisionevent on every turn with the stop reason and any text emitted by the model. The full decision tree shows up in the timeline.
Options
| Option | Default | Description |
|---|---|---|
client | required | An Anthropic instance. |
klent | required | A KlentClient instance. |
agentId | required | Identifier used as agent_id on the execution. |
model | required | Any Anthropic model string. |
tools | required | List of KlentTool — schema + handler. |
messages | required | Opening message list (same shape as messages.create). |
maxTurns | 8 | Safety cap on the message loop. |
maxTokens | 1024 | Passed through to messages.create. |
system | — | System prompt. |
metadata | — | Merged into every event emitted by this run. |
runOpenAIAgent — same idea, OpenAI chat.completions
OpenAI's chat.completions tool-use follows a slightly different shape
(tool_calls[] on the assistant message, JSON-string arguments, role: 'tool'
response messages) but the Klent wrapping is identical.
npm install @klent/sdk openaiimport OpenAI from 'openai';
import { KlentClient } from '@klent/sdk';
import { runOpenAIAgent, type KlentOpenAITool } from '@klent/sdk/openai';
const klent = new KlentClient({ apiKey: process.env.KLENT_API_KEY! });
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const tools: KlentOpenAITool[] = [
{
name: 'transfer_funds',
description: 'Move money between accounts.',
parameters: {
type: 'object',
properties: {
to_account: { type: 'string' },
amount: { type: 'number' },
currency: { type: 'string' },
},
required: ['to_account', 'amount', 'currency'],
},
handler: async (input) => transferFunds(input),
},
];
const result = await runOpenAIAgent({
client: openai,
klent,
agentId: 'billing-agent',
model: 'gpt-4o',
tools,
messages: [{ role: 'user', content: 'Pay the $10k invoice from Acme Corp.' }],
});
console.log(result.finalText);
console.log(result.finishReason); // 'stop' | 'length' | 'tool_calls' | …The argument JSON-parsing, the role: 'tool' reply messages, and the loop
until finish_reason !== 'tool_calls' are all handled. Denials surface back
to the model as a tool message containing the policy reason.
When to reach for an orchestrator vs. the raw API
Use runAnthropicAgent / runOpenAIAgent when the agent loop is your
code — your application delegates a task to the LLM, the LLM picks tools, and
the loop ends with a final text response.
Use the raw API (evaluateAction, logEvent) when you already have an
orchestrator (LangGraph, Mastra, your own runner) and you only want Klent on
the evaluate hot path. You will write more code, but you keep full control
of the loop.