Agent SDK

Agents

The core primitive — an LLM with instructions, tools, and guardrails

Agents

An Agent is the fundamental building block of the SDK. It represents an LLM configured with a specific role, a set of tools, guardrails, and optional memory. Agents are composable — they can call other agents, hand off to specialists, or run in parallel.

Creating an Agent

import { Agent } from 'assistme-agent-sdk'
import { claude } from 'assistme-agent-sdk-provider-claude'

const agent = new Agent({
  name: 'assistant',
  model: claude('claude-sonnet-4-6'),
  instructions: 'You are a helpful assistant.',
})

Agent Configuration

interface AgentConfig {
  /** Unique name for identification and tracing */
  name: string

  /** The model to use (from any provider) */
  model: ModelProvider

  /** System instructions that define the agent's behavior */
  instructions: string | ((context: RunContext) => string | Promise<string>)

  /** Tools the agent can call */
  tools?: Tool[]

  /** Input and output guardrails */
  guardrails?: {
    input?: InputGuardrail[]
    output?: OutputGuardrail[]
  }

  /** Persistent memory configuration */
  memory?: MemoryConfig

  /** Other agents this agent can hand off to */
  handoffs?: Agent[]

  /** Lifecycle hooks */
  hooks?: AgentHooks

  /** Model parameters */
  modelParams?: {
    temperature?: number
    maxTokens?: number
    topP?: number
    stop?: string[]
  }

  /** Maximum tool-call turns before stopping */
  maxTurns?: number
}

Dynamic Instructions

Instructions can be static strings or dynamic functions that compute instructions based on runtime context:

const agent = new Agent({
  name: 'support',
  model: claude('claude-sonnet-4-6'),
  instructions: async (context) => {
    const user = await getUser(context.userId)
    const plan = user.subscription.plan

    return `You are a customer support agent for ${user.name}.
They are on the ${plan} plan.
${plan === 'enterprise' ? 'Provide priority support.' : 'Follow standard procedures.'}
Current time: ${new Date().toISOString()}`
  },
})

Agent Cloning

Create variations of an agent without redefining everything:

const baseAgent = new Agent({
  name: 'base',
  model: claude('claude-sonnet-4-6'),
  instructions: 'You are a helpful assistant.',
  tools: [webSearch, calculator],
})

// Clone with overrides
const creativeAgent = baseAgent.clone({
  name: 'creative',
  modelParams: { temperature: 0.9 },
})

const preciseAgent = baseAgent.clone({
  name: 'precise',
  modelParams: { temperature: 0.1 },
  instructions: 'You are a precise, factual assistant. Always cite sources.',
})

Agents as Tools

An agent can be used as a tool by another agent, enabling hierarchical composition:

const researcher = new Agent({
  name: 'researcher',
  model: claude('claude-sonnet-4-6'),
  instructions: 'Research topics thoroughly using web search.',
  tools: [webSearch],
})

const writer = new Agent({
  name: 'writer',
  model: claude('claude-sonnet-4-6'),
  instructions: 'Write polished articles based on research.',
  tools: [
    // Use the researcher as a tool
    researcher.asTool({
      name: 'research',
      description: 'Research a topic and return findings',
    }),
  ],
})

When an agent is used as a tool, it runs in its own isolated context. The parent agent only sees the final output, not the full tool-call chain — keeping the parent's context clean.

Structured Output

Force the agent to return data in a specific schema:

const classifier = new Agent({
  name: 'classifier',
  model: claude('claude-sonnet-4-6'),
  instructions: 'Classify the sentiment of the given text.',
  output: z.object({
    sentiment: z.enum(['positive', 'negative', 'neutral']),
    confidence: z.number().min(0).max(1),
    reasoning: z.string(),
  }),
})

const result = await Runner.run(classifier, {
  messages: [{ role: 'user', content: 'I love this product!' }],
})

// result.output is typed: { sentiment: 'positive', confidence: 0.95, reasoning: '...' }

Agent Lifecycle

┌──────────┐     ┌──────────────┐     ┌──────────────┐
│  Created  │────►│   Running    │────►│  Completed   │
└──────────┘     │              │     └──────────────┘
                 │  ┌────────┐  │
                 │  │ Tool   │  │     ┌──────────────┐
                 │  │ Calls  │  │────►│   Handoff    │
                 │  └────────┘  │     └──────────────┘
                 │              │
                 │  ┌────────┐  │     ┌──────────────┐
                 │  │Guardrail│ │────►│   Blocked    │
                 │  │Trigger │  │     └──────────────┘
                 │  └────────┘  │
                 └──────────────┘

An agent run progresses through these states:

  1. Created — Agent is configured but not yet running
  2. Running — Agent is processing, may call tools in a loop
  3. Completed — Agent produced a final response
  4. Handoff — Agent transferred control to another agent
  5. Blocked — A guardrail prevented the agent from proceeding

Max Turns

Prevent runaway loops by setting a maximum number of turns:

const agent = new Agent({
  name: 'bounded',
  model: claude('claude-sonnet-4-6'),
  instructions: 'Complete the task, but do not exceed the turn limit.',
  tools: [webSearch],
  maxTurns: 10, // Stop after 10 turns
})

When maxTurns is exceeded, the runner returns with status: 'max_turns_reached' and the partial output.

Error Handling

const result = await Runner.run(agent, {
  messages: [{ role: 'user', content: 'Hello' }],
})

switch (result.status) {
  case 'completed':
    console.log(result.output)
    break
  case 'max_turns_reached':
    console.log('Agent hit turn limit:', result.output)
    break
  case 'guardrail_blocked':
    console.log('Blocked by guardrail:', result.guardrailResult.reason)
    break
  case 'handoff':
    console.log('Handed off to:', result.handoffTarget.name)
    break
  case 'error':
    console.error('Agent error:', result.error)
    break
}

Best Practices

  1. Keep instructions focused — An agent with clear, specific instructions outperforms one with vague, broad instructions. If an agent needs to do many different things, consider splitting into specialized agents with handoffs.

  2. Limit tools per agent — 5-10 tools is the sweet spot. Too many tools degrade model performance. Use agent composition or tool search for large tool sets.

  3. Use structured output for downstream processing — When an agent's output feeds into code, always use structured output with Zod schemas.

  4. Set maxTurns — Always set a reasonable maxTurns in production to prevent runaway costs and infinite loops.

  5. Name your agents — Names appear in traces and logs. Clear names make debugging significantly easier.