defineTool and defineAgent from @trayai/helix-sdk declare the tools an agent may call and the agent itself. An agent file lives under functions/_agents/ and default-exports its definition; tools live wherever you keep shared code. This page gives exact signatures, every option with its default, and the helix.config.ts key that selects where sessions are stored.
defineTool
// @trayai/helix-sdk
function defineTool<TSchema extends ZodType>(config: DefineToolConfig<TSchema>): HelixTool<TSchema>
interface DefineToolConfig<TSchema extends ZodType> {
name: string;
description: string;
input: TSchema;
needsApproval?: boolean | ((input: z.infer<TSchema>) => boolean | Promise<boolean>);
handler: (ctx: PlatformContext & { input: z.infer<TSchema> }) => Promise<unknown> | unknown;
}
| Field | Required | Rules |
|---|---|---|
name | Yes | Matches ^[a-z][a-z0-9_]{0,63}$. Unique within any agent that lists it |
description | Yes | Non-empty. Read by the model |
input | Yes | A Zod schema. Validated before every call, from an agent or directly |
needsApproval | No | true pauses every agent call; a predicate pauses when it returns true for the validated input. Ignored on direct calls |
handler | Yes | Runs with the caller’s ctx; ctx.input is the parsed input |
A bad name, empty description, non-Zod input, or missing handler throws from defineTool itself, at module load.
HelixTool
The return value is callable and carries its definition:
interface HelixTool<TSchema extends ZodType> {
(input: z.infer<TSchema>): Promise<unknown>; // validate, then run with getContext()
readonly name: string;
readonly description: string;
readonly inputSchema: TSchema;
readonly needsApproval?: DefineToolConfig<TSchema>['needsApproval'];
readonly execute: (input: unknown, ctx: PlatformContext) => Promise<unknown>;
}
PlatformContext is the SDK’s name for the ctx a function receives, exported from @trayai/helix-sdk. Its userId and headers are optional, since a tool can run from a schedule.
A direct call outside a function execution throws, because there is no ctx to run with. Invalid input throws SdkError.inputValidationFailed naming the failing fields. Inside an agent loop a thrown handler error becomes a tool error result the model can read; on a direct call it propagates.
defineAgent
// @trayai/helix-sdk
function defineAgent(config: AgentDefinitionConfig): AgentDefinition
interface AgentDefinitionConfig {
name: string;
description: string;
model: string;
auth: string;
fallbacks?: Array<{ model: string; auth: string }>;
instructions?: string;
instructionsFile?: string;
tools?: HelixTool[];
agents?: string[];
memory?: {
scope?: 'project' | 'user';
maxSessionMessages?: number;
ttl?: number;
};
limits?: {
maxIterations?: number;
maxToolCalls?: number;
};
}
| Field | Required | Default | Rules |
|---|---|---|---|
name | Yes | Matches ^[a-z][a-z0-9-]{0,63}$. The name callers pass to ctx.agent() | |
description | Yes | Non-empty. Shown by helix agent list; the description of the agent_<name> tool when another agent delegates here | |
model | Yes | Model id for the provider behind auth | |
auth | Yes | Alias present in both authentications and aiProviders | |
fallbacks | No | [] | Tried in order when the current model throws. Never used after an abort |
instructions | Exactly one of the two | System prompt, inline | |
instructionsFile | Exactly one of the two | Path relative to the agent file. Resolved when the agent is first used | |
tools | No | [] | defineTool values. Duplicate names throw |
agents | No | [] | Agent names. Checked at first run; cycles are allowed. Delegates may not hold needsApproval tools |
memory.scope | No | 'project' | 'user' keys sessions by the userId on the execution’s ctx and makes sessions() available |
memory.maxSessionMessages | No | 200 | Positive integer. Cap on the messages sent to the model per turn. The window always starts on a user message, and a turn longer than the cap is sent whole. Storage is not limited |
memory.ttl | No | none | Positive integer of seconds. Opt-in idle retention; omitted, sessions do not expire |
limits.maxIterations | No | 25 | Model steps per call |
limits.maxToolCalls | No | 50 | Tool executions per call |
Validation runs when the file loads. The error names the agent and the failing field.
Delegation
Each name under agents becomes a tool named agent_ plus the name with hyphens replaced by underscores, taking { prompt: string }. The delegate runs its own loop in its own new session and returns its final text. Delegation depth is capped at three; beyond that the tool returns { error: 'delegation_depth_exceeded' } to the calling model.
Where agent files live
| Rule | |
|---|---|
| Directory | functions/_agents/, any depth of subdirectory |
| Export | export default defineAgent({ ... }) |
| Discovery | helix dev and the deploy build import every file in the directory. Adding or removing a file needs a dev server restart |
Files without a default defineAgent export | Skipped, with a warning naming the file |
| Registration | By name, not file name. Two agents with the same name fail the deploy. Under helix dev, one silently replaces the other |
| Maximum | 20 agent files per project |
Files under functions/_agents/ are never HTTP routes.
totalToolCallsIs
// @trayai/helix-sdk
function totalToolCallsIs(max: number): StopCondition<ToolSet>
A stop condition that ends a run once max tool executions have happened across all steps. Pair it with the AI SDK’s stepCountIs in stopWhen.
Config: agentSessions
Selects where sessions are stored. Applies to every agent in the project:
agentSessions?:
| { store: 'managed' }
| { store: 'dynamodb'; auth: string; table: string; region: string }
| Form | Meaning |
|---|---|
Omitted or { store: 'managed' } | The platform stores sessions in deployed executions; helix dev keeps them in memory |
{ store: 'dynamodb', ... } | A DynamoDB table you own, in dev and in production. auth is an authentication alias holding an AWS credential; table and region name the table |
With the DynamoDB store, agent.sessions() is unsupported and a single message is capped at 400 KB. Table layout and permissions are in bring your own session storage.
Related exports
| Export | Kind |
|---|---|
defineTool, defineAgent, totalToolCallsIs | Functions |
isToolDefinition, isAgentDefinition | Type guards for the two definition shapes |
DefineToolConfig, HelixTool, AgentDefinitionConfig, AgentDefinition | Definition types |
AgentCallOptions, AgentResult, AgentStreamResult, AgentApprovalDecision, AgentPendingApproval, HelixAgent | Call types, see ctx.agent() |
AgentSession, AgentSessionPage, AgentSessionPageOptions, AgentSessionsList, AgentSessionSummary | Session types |
stepCountIs, hasToolCall, convertToModelMessages, ModelMessage, UIMessage, ToolSet, StopCondition | AI SDK helpers and types re-exported for use with agents |
Example
// functions/_agents/billing-agent.ts
import { defineAgent } from '@trayai/helix-sdk';
import lookupInvoice from '../_shared/tools/lookup-invoice';
import applyCredit from '../_shared/tools/apply-credit';
export default defineAgent({
name: 'billing-agent',
description: 'Answers invoice questions and applies account credits.',
model: 'claude-sonnet-4-5',
auth: 'anthropicKey',
fallbacks: [{ model: 'gpt-4o', auth: 'openaiKey' }],
instructionsFile: './billing-agent.md',
tools: [lookupInvoice, applyCredit],
memory: { scope: 'user', maxSessionMessages: 80 },
limits: { maxIterations: 12 },
});
Usage is covered in defining tools and defining agents.