Tool capabilities
Keep tool calls and their results usable when a chain changes tiers.
A tier serving a tool-carrying turn must understand tool definitions, emit calls, and replay tool results. Returning text alone does not satisfy that contract.
Declare capabilities honestly
| Adapter field | Contract |
|---|---|
supportsTools | Exposes available tools, emits call events, and handles their transcript results. |
canForceTool | Can constrain a provider request to a specific tool when forceTool is supplied. |
Known models in the shipped catalog declare both. Custom model definitions and custom adapters must declare the behavior they actually support.
Require tools across the whole chain
When an agent declares tools, every tier must declare supportsTools: true.
defineAgent throws KeelError if even one fallback is incompatible. The
message names the agent, chain, and offending tiers. A text-only chain remains
valid for an agent with tools: [].
TypeScript rejects known incompatibilities too. customModel, provider
classes and factories, and defineChain preserve literal capability values.
Everything in this block compiles except the final defineAgent call — the
error it demonstrates:
import { z } from "zod";
import {
customModel,
defineAgent,
defineChain,
defineTool,
} from "@keel-dev/core";
import { OpenAIProvider } from "@keel-dev/core/providers/openai";
const lookupTool = defineTool({
id: "lookup_order",
description: "Look up an order by its ID.",
access: "public",
input: z.object({ orderId: z.string() }),
execute: (input) => ({ result: { found: false, query: input } }),
});
const primary = new OpenAIProvider({
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-5.1",
});
const textModel = customModel({
provider: "openai",
id: "my-text-model",
windowTokens: 32_000,
maxOutputTokens: 4_000,
supportsTools: false,
canForceTool: false,
});
const chain = defineChain({
id: "assistant-chain",
tiers: [
primary,
new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY, model: textModel }),
],
});
// TypeScript error: every tier must declare supportsTools: true.
defineAgent({
id: "assistant",
chain,
prompt: "Help the user.",
reads: [],
emits: [],
tools: [lookupTool],
});For hand-written adapters, use satisfies ModelAdapter to check their shape
while preserving literal capabilities. An explicit ModelAdapter or
ChainDef annotation can widen that information. Dynamic booleans and tool
arrays are validated when defineAgent executes, even if TypeScript cannot
prove incompatibility.
Keep the execution guard
The low-level chain runner also checks the actual available tool list. It
skips incompatible tiers loudly, emitting a fallback chain event whose
detail.reason is "no-tool-support"; the skipped tier is also recorded in
fallbacksTaken. This remains a backstop for direct chain calls and
runtime-added submission tools; it does not allow a mixed chain through
defineAgent for an agent declaring tools.
Forced tool selection and delivery
For a required deliverable, the runtime can request a particular submission
tool during bounded recovery rounds. Adapters that implement forced selection
translate forceTool into provider configuration.
A capability flag alone does not prove delivery. The close gate still checks whether the required deliverable was submitted. See Failure and recovery and Turn.
Inspect in Studio
First verify that a tool-using agent with a text-only fallback fails during configuration. With a compatible chain, run a turn in Studio and inspect MODEL to confirm the selected tier receives tools and replays their results. Custom adapters should cover both behaviors in their tests.
Next: Limits and usage.
