SDK tutorial · 3 of 10

Add a native tool

A native tool is the smallest boundary for letting the agent call product logic while keeping authorization, transactions, and domain semantics inside your application.

Customization depth

Level 2 · Product capabilities

Hosting depth

Quickstart or conversation engine

Documentation status

Runnable reference

Assumptions

  • The product has one domain action or data lookup that should be model-visible.
  • The tool can return a JSON-safe Heddle ToolResult.
  • The host will validate model-provided input before changing product state.
Heddle owns
  • Register the tool, publish its schema to the model, invoke it, and record trace activity.
  • Apply configured tool profiles and approval policies before execution.
  • Return completed tool-call details in the turn result.
Your product owns
  • Own input validation, authenticated identity, authorization, idempotency, transactions, and domain errors.
  • Choose a stable public tool name, useful model-facing description, and minimal JSON Schema.
  • Decide which capabilities require approval or should be hidden from a particular agent.

Define the smallest useful capability

ToolDefinition is the public contract. This example has no model-provided fields, so its runtime input is intentionally empty:

TypeScript
import {
  defineHostExtension,
  type ToolDefinition,
} from '@roackb2/heddle'

const currentTime: ToolDefinition = {
  name: 'current_time',
  description: 'Return the current time as an ISO-8601 timestamp.',
  parameters: {
    type: 'object',
    properties: {},
    additionalProperties: false,
  },
  execute: async () => ({
    ok: true,
    output: new Date().toISOString(),
  }),
}

const productExtension = defineHostExtension({
  id: 'product-operations',
  tools: [currentTime],
  systemContext: 'Use current_time when the user asks for the current time.',
})

The parameters field is JSON Schema shown to the model. execute still receives untrusted unknown input at runtime. For tools with fields, validate again with the schema library your application already uses before reading or mutating product data.

Application-defined capability strings such as product.read are allowed as metadata, but Heddle's built-in selection and read-only profiles do not infer their meaning. Either use a documented built-in capability when it is semantically correct, or supply a custom policy that explicitly understands your product tags.

Add the extension to an engine

TypeScript
import { createConversationEngine } from '@roackb2/heddle'

const engine = createConversationEngine({
  workspaceRoot,
  stateRoot,
  model,
  hostExtensions: [productExtension],
})

For a small terminal evaluation, the quickstart also accepts tools: [currentTime]. For new embedded engines, prefer hostExtensions; direct ConversationEngineConfig.tools remains only for compatibility.

Design tool results for the next model step

Return one of these shapes:

TypeScript
{ ok: true, output: { recordId: 'rec_123', status: 'created' } }
{ ok: false, error: 'The requested record does not exist.' }

Return compact, structured data that helps the model decide what to do next. Do not return credentials, internal exception objects, or unbounded database rows. Large generated bodies should become artifacts rather than repeated tool-result text.

Approval and capability metadata

  • Set requiresApproval: true when the default policy should request a host decision before execution.
  • Use capabilities for tool-profile and approval-policy classification.
  • Capabilities are not authorization. The tool implementation must still enforce the authenticated product user's permissions.

Composition rules

Host extensions compose in declaration order. Extension IDs, tool names, and toolkit IDs must be unique; Heddle rejects duplicates before the first turn runs. Treat these names as stable public API because prompts, traces, policies, and result handling may refer to them.

Verification

Run a prompt that clearly requires the tool, then confirm that the activity stream shows the call and the turn result contains it in toolResults. Also test invalid input and a denied product authorization path without relying on the model to behave correctly.

Canonical sources