SDK

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:

  1. logEvent({ type: 'action_requested' })
  2. evaluateAction(...)
  3. On deny → returns { status: 'denied' }. Klent has already recorded an action_blocked event via the evaluate endpoint.
  4. On modify → applies the modifications to the input, then calls execute(modifiedInput).
  5. On allow → calls execute(input).
  6. On success → logEvent({ type: 'action_executed' }).
  7. 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/sdk
import 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 as is_error: true tool_results with the policy's reason — so the model can apologize, ask for a lower amount, or pick a different tool.
  • Logs a decision event on every turn with the stop reason and any text emitted by the model. The full decision tree shows up in the timeline.

Options

OptionDefaultDescription
clientrequiredAn Anthropic instance.
klentrequiredA KlentClient instance.
agentIdrequiredIdentifier used as agent_id on the execution.
modelrequiredAny Anthropic model string.
toolsrequiredList of KlentTool — schema + handler.
messagesrequiredOpening message list (same shape as messages.create).
maxTurns8Safety cap on the message loop.
maxTokens1024Passed through to messages.create.
systemSystem prompt.
metadataMerged 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 openai
import 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.