# Agents and function calls

> Create an agent class, give it application functions and run it from a route or job.

- Package: `@db3.ai/app/ai`
- Canonical page: [https://db3.ai/docs/ai-agents](https://db3.ai/docs/ai-agents)
- Markdown: [https://db3.ai/docs/ai-agents.md](https://db3.ai/docs/ai-agents.md)
- Framework source of truth: `packages/app/src/ai/README.md`

<a id="create"></a>

## Create an agent

Create `server/agents/HelpAgent.ts`. An agent describes its task in `instructions()` and exposes functions from `tools()`. This example reads a help guide through your app’s Storage service.

Install `zod` in your app for tool argument schemas. The framework validates the model’s arguments before invoking your function. The route selects the guide path, user and scope; the model cannot choose another user’s file.

### server/agents/HelpAgent.ts

```typescript
import { z } from 'zod';
import { Agent, tool, type AgentTool, type BaseAgentContext } from '@db3.ai/app/ai';
import { app } from '@db3.ai/app/server';

/** Trusted context supplied by your route or job, never selected by the model. */
export interface HelpAgentContext extends BaseAgentContext {
	guidePath: string;
}

/** Answers questions using a help guide saved by your application. */
export class HelpAgent extends Agent<HelpAgentContext> {
	/** Gives the agent its task and source of truth. */
	async instructions(): Promise<string> {
		return 'Answer questions about this app using its saved help guide. Read the guide before answering. If the answer is absent, say so.';
	}

	/** Exposes one function with validated arguments and a server-selected file path. */
	protected tools(): AgentTool[] {
		return [Object.assign(tool({
			name: 'read_help_guide',
			description: 'Read the application help guide.',
			parameters: z.object({}),
			/** Reads only the file selected by trusted application code. */
			execute: async () => app().storage.getText(this.context.guidePath),
		}), { title: 'Read help guide', description: 'Read the application help guide.' })];
	}
}
```

<a id="run"></a>

## Run your agent

Write a guide to your configured disk, then call the agent from trusted server code. `run()` resolves with the final output and saved IDs, or throws when the run fails. `user` and `scope` are application identifiers; choose them from your authenticated request and permission checks.

### Inside your route or job

```typescript
import { app } from '@db3.ai/app/server';
import { HelpAgent } from './agents/HelpAgent';

await app().storage.put('help.md', 'Change your password on the settings page.');
const result = await new HelpAgent({ user: userId, scope: teamId, guidePath: 'help.md' }).run('How do I change my password?');
console.log(result.finalOutput, result.conversationId);
```

<a id="stream"></a>

## Stream events to your UI

Use `stream(message, emit)` to forward typed events to your HTTP transport. Events include `text.delta`, `tool.calling`, `tool.progress`, `tool.success`, `tool.error`, `run.completed` and `run.error`.

`stream()` reports a failure through `run.error` and returns null. Your renderer can display tool progress alongside text and later restore the same timeline from saved messages. Keep prompt/debug events behind an authorized diagnostic view.

- [Restore a conversation](https://db3.ai/docs/ai-conversations.md)

### Forward events through your transport

```typescript
await new HelpAgent({ user: userId, scope: teamId, guidePath: 'help.md' }).stream(message, async event => {
	await sendEvent(event);
});
```

<a id="queue"></a>

## Run an agent in the background

Register `AgentRunJob` and each concrete agent during app boot, in both your web and worker processes. Add `QueuedJob` and `FailedJob` to your migration registry if you have not already configured the database queue.

`queue()` saves the conversation and prepared run before dispatching. The worker reconstructs your agent from its serialized constructor context. Keep context serializable; registered ActiveRecord references can be restored by the framework serializer. Use stable class names for persisted jobs.

- [Start a queue worker](https://db3.ai/docs/queue-overview.md)

### Register at boot, dispatch from your feature

```typescript
import { AgentRunJob, registerQueuedAgent } from '@db3.ai/app/ai';

application.queue.registerJob(AgentRunJob);
registerQueuedAgent('HelpAgent', HelpAgent);

const run = await new HelpAgent({ user: userId, scope: teamId, guidePath: 'help.md' }).queue('Explain password settings.', { queue: 'help' });
console.log(run.jobId, run.conversationId);
```

<a id="tools"></a>

## Use other AI features in a tool

Call `this.generateImage(...)`, `this.generateEmbedding(...)` or `this.generateStructured(...)` from an agent tool. Calls made during the run inherit its conversation and parent request, so the full operation appears in one request tree. Direct `app().ai` calls inside a tool inherit the same context.

Use the exported SDK tool helpers for supported provider tools such as web search. Tool availability depends on the selected provider and model. Your functions still own authorization, input limits and any application side effects.

## Behavioural verification
- Behaviour test: `packages/app/src/ai/tests/Ai.integration.test.ts`

## Related documentation
- [Remember conversations in your app](https://db3.ai/docs/ai-conversations.md): Resume a conversation, render its history and extend the framework’s ActiveRecord models.
- [Inspect AI requests and usage](https://db3.ai/docs/ai-usage.md): Track provider work, inspect failures and account for every request in an agent run.
- [Queue](https://db3.ai/docs/queue-overview.md): Create a job in your app, dispatch it from a route or service, and let a worker handle it in the background.
- [AI API reference](https://db3.ai/docs/ai-api.md): Current emitted signatures and options for @db3.ai/app/ai.

## Framework-owned source: `packages/app/src/ai/README.md`

This is the exact source document captured by the documentation build. Use it for detailed API and workflow guidance, subject to the public package exports and behavioural evidence identified above.

````markdown
# AI

Build AI features inside your db3 application with `app().ai` and `Agent`.
The service owns provider execution, rate limits and request tracking. Your app
owns prompts, tools, authorization, storage destinations and commercial policy.

## Start in your app

The generated app configures `new App({ ai: config.ai })` and includes
`AiConversation`, `AiMessage`, `AiRequest`, `AiRateLimitBucket` and
`AiRateLimitReservation` in its migration registry. Set `OPENAI_API_KEY` and
`OPENAI_MODEL` in the server `.env`, apply migrations and restart. Existing apps
must register these models and generate a committed migration before using AI.

```ts
import { app } from '@db3.ai/app/server';

const result = await app().ai.generateTextWithResponse({
	instructions: 'Summarise this note in three short points.',
	input: note.body,
	maxOutputTokens: 400,
}, { user: userId, scope: teamId });
```

`generateText()` returns a string. `generateTextWithResponse()` also returns the
saved request and conversation IDs. Direct generation tracks a conversation but
does not replay its previous messages; use an agent for conversational memory.

## Agents and function calls

Create a class under your app's `server/agents/` directory extending `Agent`.
Implement `instructions()` and `tools()`. Use the exported `tool` helper with a
Zod parameter schema to expose an application function. Install `zod` as an app
dependency when importing it in your tools.

See [HelpAgent.ts](./examples/HelpAgent.ts) for a tested function-calling agent.
Its context selects a trusted storage path, user and scope. `run(message)`
returns final output and saved IDs; `stream(message, emit)` emits typed events
and returns null after a `run.error` event. A conversation can be resumed by
supplying its ID with matching user, scope and agent identity.

Register `AgentRunJob` on the queue and call `registerQueuedAgent(name, Class)`
at boot in both web and worker processes before using `agent.queue(message)`.
Its constructor context must be serializable. Queued agents require the normal
queue models and a worker listening on the selected queue.

## Images, embeddings and structured output

- `generateImage(input, { store })` returns image bytes and an optional app-owned
  storage reference. See [createHelpImage.ts](./examples/createHelpImage.ts).
- `generateEmbedding(text, model?, options?)` returns a vector, model and request
  ID. The app owns indexing, source revisions and authorized retrieval.
- `generateStructured({ instructions, input, schemaName, schema })` returns
  locally validated `data`, text and tracking IDs. Schemas use Zod.

Agent instances expose these same methods. Calls made inside an executing tool
inherit the current conversation and parent request, including calls through
`app().ai`. Nested calls cannot carry a second independent usage charge.

## Conversations and request records

`AiConversation`, `AiMessage` and `AiRequest` are shipped ActiveRecord classes.
Extend them in your app and configure `ai.models` with the replacement
constructors. Replace the corresponding entries in your migration registry and
generate a migration. See [Conversation.ts](./examples/Conversation.ts).
Keep table names unless you also override the related model link fields.

`user` and `scope` store optional application identifiers. An app may replace
these fields with typed model links. `scopeField` adapts an existing logical
ownership field; calls using that adapter supply that field in logging options.

Use `agentConversationTimeline(messages, toolDefinitions, requests)` to rebuild
stream-compatible history after authorizing access. The formatter includes
diagnostic prompt items; filter those from ordinary user views. It supplies
data for your renderer, not a history endpoint or UI component.

Every provider attempt retains status, input, response, usage and timing.
`AiRequest.runCostSummary(id)` aggregates recursively linked provider work
without counting the root aggregate twice. Unknown usage and costs remain
unknown. When `unpricedRequestCount` is nonzero, `totalCostUSD` is only the known
subtotal. Price estimates are not invoices or a customer credit ledger.

Request persistence is enabled by default. Prompts, responses and tool results
may contain private information. Apply your app's access and retention policy.
`saveAiResponse: false` opts direct service calls out of record persistence.
OpenAI text requests set `store: false`; that does not disable local tracking or
all provider retention. Never expose provider credentials in client code.

## Models and cost estimates

Select `gpt-6-astra` through a per-call `model` override or a named agent model
in your app's AI config. OpenAI agents already use the Responses API required
for Astra tool calls. Astra supports reasoning efforts `low` through `xhigh`
in the framework; do not configure `none` or `minimal`, or custom `temperature`,
`top_p` or log probabilities. Availability depends on the API project.

The shared `AI_MODEL_PRICING` table was checked against
[OpenAI's API prices](https://developers.openai.com/api/docs/pricing) on
2026-09-11. Standard USD prices per million tokens are:

| Model | Input | Cache read | Cache write | Output |
| --- | ---: | ---: | ---: | ---: |
| `gpt-6-astra` | $10.00 | $1.00 | $12.50 | $50.00 |
| `gpt-5.6-sol` | $4.00 | $0.40 | $5.00 | $20.00 |
| `gpt-5.6-terra` | $2.00 | $0.20 | $2.50 | $12.00 |
| `gpt-5.6-luna` | $0.20 | $0.02 | $0.25 | $1.20 |

For these models, a request above 272,000 input tokens costs 2x all input
rates and 1.5x output rates for the full request. Agent runs calculate this
per provider request, so several short turns do not trigger the higher rates
merely because their combined usage exceeds the threshold. Sol's published
promotional rates are available at least through 2026-11-21; recheck the rate
card when that period ends rather than assuming a future price.

Direct OpenAI text and agent requests select Standard processing. Estimates
exclude regional processing surcharges and other tiers. OpenRouter and xAI
use their provider-reported billed costs. Updates affect new estimates and
new request records; they do not rewrite stored costs or change app model defaults.

Image pricing includes `gpt-image-2.5-flare` and `gpt-image-2`: $5 text input,
$1.25 cached text input, $8 image input, $2 cached image input and $30 image
output per million tokens. Dated Flare snapshots resolve to the same rate card.
The models can consume different token counts at the same size and quality;
image costs use actual reported usage, not fixed per-image estimates. Apps
select Flare through `ImageGeneration` or a per-call `model` override.

## Providers and failover

Configure `ai.provider` with one provider, an ordered list or an ordered record
of provider/model pairs. Registered providers are OpenAI, OpenRouter, Groq, xAI
and DeepSeek. Provider keys, base URLs and model defaults use their corresponding
server environment variables. Calls and agent subclasses can select a chain.

Text and structured output require the Responses API. Agents also support the
registered Chat Completions providers. Images and embeddings currently use the
OpenAI adapter and do not fail over. Other protocols need a driver implementation.

Transient network, rate-limit, quota and server failures can advance to another
provider. Authentication and request validation failures do not. Agent failover
stops once the SDK emits an event, avoiding automatic replay of tool effects.
The per-request timeout defaults to 60 seconds; SDK automatic retries are off.

The SQL-backed `AIRateLimiter` coordinates provider capacity across workers.
Agents reserve capacity for each SDK HTTP request and record response headers
before consuming the stream. Reservations are released before tools execute,
so a tool can call the same model without waiting on its own parent agent.
Transport failures also release reservations; later model turns reserve again.
Quota and capacity deferrals integrate with queue retries. The `allowance` hook
checks application budgets separately. Override `usageCharge()` and
`settleUsage()` in an agent when your app needs an idempotent commercial ledger.

## Verification

### Maintenance map

- `Ai.ts`: direct provider requests, allowance hooks and tracking lifecycle.
- `Agent.ts`: conversational runs, tool events, provider attempts, queue setup
  and application extension hooks.
- `contracts/`: shared service, agent, event, provider and limiter contracts.
- `AgentHistory.ts`: transport-independent timeline reconstruction.
- `toolHelpers.ts`: typed context access, progress, errors and model attachments.
- `AgentRunJob.ts` and `registry.ts`: validation and durable reconstruction;
  applications register their own classes and model identities.
- `AiConversation.ts`, `AiMessage.ts`, `AiRequest.ts`: extensible record models.
- `AIRateLimiter.ts`, `AIProviders.ts`, `AIFailover.ts` and `OpenAIQuotaRetry.ts`:
  provider capacity, selection and retry behavior.

Keep application-specific context, authorization, billing, prompts and storage
placement in application subclasses or adapters. Shared tool helpers preserve
the application's context type; applications need not copy their implementations.

The pinned Agents SDK currently needs `sdkNodeCompatibility.d.ts` for strict
Node declaration checking. It describes the SDK emitter through its own event
contract and does not replace runtime code or augment Node globally. Staging
ships that declaration and rewrites its reference to the installed location.
Remove the shim when the unmodified SDK passes the packed consumer with
`skipLibCheck: false`; do not disable the check to upgrade the dependency.

Framework maintainers run `npm run test:service --workspace @db3.ai/app -- ai`.
Tests use real App, SDK, SQL and Storage with synthetic provider responses. The
integration suite covers tools, history, ownership, queue reconstruction, nested
request parentage, failover, image storage, embeddings and structured output.
The package is also installed and type-checked in an independent consumer.

Installed app developers run their own tests using a disposable database and
an injected `ai.fetch`. No live key is needed for simulated provider tests.
See the [AI guides](https://db3.ai/docs/ai) for app-focused examples.
````
