A production pattern library for Anvia agent harnesses.
Use these patterns when an agent is moving from a demo into product code. The goal is to keep model behavior explicit while your application keeps ownership of users, permissions, data, side effects, storage, deployment, and audit trails.
This section is organized as a pattern library. Start with the common harness shape, then jump to the real case that matches the system you are building.
Most production agents follow the same boundaries:
stable provider clients, models, reusable agents, static tool catalogs, observers, and default limits are created at startup
request-local user, tenant, conversation, permission, feature flag, MCP availability, and caller timeout policy are resolved at the application boundary
tools wrap product services and enforce permissions before reading or changing data
context is assembled from instructions, static facts, request facts, retrieval, history, and memory
persistence, retries, idempotency, audit logs, and product response shapes stay in application code
import { AgentBuilder, Message } from "@anvia/core";import { model } from "./ai/model";import { createSupportTools } from "./ai/support-tools";export async function runSupportTurn(input: SupportTurnInput) { const user = await input.auth.requireUser(); const conversation = await input.conversations.load(input.conversationId); const agent = new AgentBuilder("support", model) .instructions("Answer support questions clearly. Use tools for account data.") .tools( createSupportTools({ userId: user.id, tenantId: user.tenantId, orders: input.services.orders, tickets: input.services.tickets, }), ) .context(`Current plan: ${user.plan}`, "current-plan") .defaultMaxTurns(3) .build(); const response = await agent .prompt([...conversation.history, Message.user(input.message)]) .withTrace({ name: "support-chat", userId: user.id, metadata: { tenantId: user.tenantId, conversationId: input.conversationId, }, }) .send(); await input.conversations.append(input.conversationId, response.messages); return { output: response.output, messages: response.messages, };}
The exact route, worker, queue, or UI framework can vary. The ownership boundary should not: Anvia owns the model-tool loop, and your application owns product state and side effects.