Agents and model providers
An agent is a built-in thread kind. It can call function tools or spawn workflow, agent, evaluator, and custom-thread children. Looms runs agent turns; model providers return messages and tool requests.
Connect a model#
npm install @swirls/looms @openrouter/ai-sdk-provider zodexport OPENROUTER_API_KEY=your-keyexport LOOMS_MODEL=your-provider-model-idSave the following as agent.ts and run bun agent.ts. Choose a model ID supported by your provider. This example uses memory, so exiting discards its history; use the quickstart storage configuration to persist it. Keep provider secrets on the server.
import { createOpenRouter } from '@openrouter/ai-sdk-provider'import { z } from 'zod'import { agent, defineAgent, defineTool } from '@swirls/looms/agent'import { vercelLlm } from '@swirls/looms/ai-vercel'import { createLooms } from '@swirls/looms/runtime'const apiKey = process.env.OPENROUTER_API_KEYconst modelId = process.env.LOOMS_MODELif (!apiKey || !modelId) { throw new Error('Set OPENROUTER_API_KEY and LOOMS_MODEL')}const llm = vercelLlm({ model: createOpenRouter({ apiKey }).chat(modelId) })const greeting = defineTool({ name: 'greeting', description: 'Build a greeting for a person.', input: z.object({ name: z.string() }), handler: ({ name }) => ({ text: 'Hello, ' + name }),})const assistant = defineAgent({ name: 'assistant', instructions: 'Use the greeting tool, then return its result.', input: z.string(), tools: [greeting], maxTurns: 4,})const looms = createLooms({ modules: [agent({ definitions: [assistant], llm })] })try { const { runId } = await looms.start(assistant, 'Greet Maya') console.log(await looms.getEvents(runId))} finally { await looms.stop()}Streaming and conversation history#
vercelLlm enables streaming by default when the turn supplies a text-delta callback. Subscribe through @swirls/looms/client or React projections while work executes. An awaited start result arrives after its wake cycle; use a streaming start or subscribe to a known run ID if you need events before it returns.
Set conversational: true for an agent that should accept later user messages. Use userMessage(text) with signal. For long conversations, decide how much history to send to the model and when to summarize it.
Agent sessions#
Use defineAgentSession when messages need a durable mailbox. A session admits one turn at a time, deduplicates message IDs, and keeps queued messages across restarts. Submit messages with submitAgentMessage.
Delivery can be followUp, steer, or nextTurn. A follow-up starts its own turn in FIFO order. A steer targets the active turn and falls back to a follow-up if it arrives too late. A next-turn message becomes context without starting a turn.
const session = defineAgentSession({ name: 'support-session', agent: supportAgent, idleTimeoutMs: 30 * 60_000,})const { runId } = await looms.start(session, {})await looms.signal(runId, [ submitAgentMessage('message-1042', 'Where is my order?', { delivery: 'followUp', }),])Register the session and its pinned agent definition in the agent module. Use stable message IDs when retrying ingress. Cancelling the active turn leaves queued messages in the mailbox.
Tools and child threads#
A function tool returns JSON. Use asAgentTool, asWorkflowTool, asEvaluatorTool, or asThreadTool to delegate to a child. Register that child's definition in its owning module. Child execution appears in the same run log.
Validate tool inputs and check permissions in your application. A model's request to perform an operation is not authorization. Keep approval checks in deterministic workflow code.
Limits, failures, and cancellation#
Set maxTurns and, when needed, stopWhen using stepCountIs or a custom predicate. Turn limits are not token or spending limits. Enforce provider quotas and application budgets separately; current adapter usage should not be treated as a billing ledger.
Use looms.cancel(runId, threadId?) to request cancellation. External operations already accepted by another system cannot be undone by cancellation. Tool handlers receive an abort signal where available; forward it to cancellable I/O. Inspect failure events and test your recovery policy.
Structured output and custom models#
Input schemas validate inputs; they do not automatically guarantee model output shape. Validate structured output in a tool or custom turn and return a JSON-compatible result. Implement LlmAdapter for another model API or use runTurn for a fully controlled turn. Without either, the built-in agent uses a deterministic stub; it does not contact an LLM.
Continue with human approvals and interrupted model calls.