Tools & providers
valv gives your agent a small set of tools, formatted for the provider you use. This page covers the tools the model gets, how to format them for each provider, and how to turn individual tools on or off.
The tools the model gets
By default, valv exposes four read tools:
list_resourceslists the resources the caller may read.search_resourcesfinds resources by name or description.describe_resourcereturns a resource’s fields and types.queryruns a structured query.
The first three are discovery tools, and they’re policy-filtered: they only
surface what the caller may read, so the model never learns about a resource it
can’t reach. The write tools (create, update, delete) are
off by default.
Formatting tools for a provider
valv.tools.<format>(ctx, options) returns tools bound to a request context.
Choose the format that matches your provider:
valv.tools.anthropic(ctx) // Anthropic Messages API
valv.tools.openai(ctx) // OpenAI and compatible APIs
valv.tools.gemini(ctx) // Google Gemini
await valv.tools.aisdk(ctx) // Vercel AI SDK (async; needs the `ai` package)
valv.tools.neutral(ctx) // raw, framework-agnostic
The aisdk format returns self-executing tools: the SDK runs them for you, as
shown in the Quickstart. It’s async because it imports the
optional ai peer dependency.
The system prompt
The tools carry their own schemas, but the model still needs to know how to
drive them — discover a resource, describe it, then query — and which resources
it may reach. valv.instructions(ctx) returns a ready-made system-prompt block
for exactly that. Drop it into your system prompt alongside the tools:
const { text } = await generateText({
model,
system: await valv.instructions(ctx), // how to drive the tools + this caller's resources
tools: await valv.tools.aisdk(ctx),
prompt: "What's our revenue per order status this month?",
})
The block explains the workflow and the query grammar, then lists the resources
this caller may read — so the model can skip the opening list_resources
round-trip. The resource list is policy-filtered per context, just like the
discovery tools, so it never names a resource the caller can’t reach:
You answer questions by querying a set of resources through the provided tools.
Access is enforced server-side: every query is scoped to what the current caller
may read, so you never need to add tenant/owner/permission filters yourself.
Workflow:
1. Find the resource: use list_resources / search_resources.
2. Before querying an unfamiliar resource, call describe_resource for its exact
columns, types, and relations. Don't guess column names.
3. Query with the `query` tool. Do the work in the query — filter, aggregate,
groupBy, orderBy, take — rather than pulling raw rows and reducing yourself.
4. The grammar is Prisma-like: `select` is keyed by output name, and `where`
uses { field: value } / { field: { gte, lt, in, contains } } with AND/OR/NOT.
5. Read a joined resource's column with a dotted path from the root, like
"customer.name".
Resources you can query:
- orders — customer orders
- customers — people who place orders
instructions is async because it loads the schema to resolve the caller’s
resources. If you’d rather compose the resource list yourself, the static
workflow text is also exported as AGENT_INSTRUCTIONS.
Dispatching a tool call yourself
The provider formats (anthropic, openai, gemini) return tool definitions
for the API request. When the model calls one, you dispatch it with runTool:
const result = await valv.runTool(call.name, call.input, ctx)
This runs the same validated, policy-scoped pipeline regardless of provider.
Turning tools on and off
Pass an options object to enable or disable individual tools. Discovery tools are on by default; write tools are off:
// Drop the discovery tools, keep query
valv.tools.anthropic(ctx, { list: false, search: false, describe: false })
// Enable writes
await valv.tools.aisdk(ctx, { create: true, update: true })
Enabling a write tool here still requires the matching policy axis to allow it. See Writes.
Next steps
- Writes: expose create, update, and delete tools.
- MCP in your app: serve these same tools over MCP with per-request identity.
To point a standalone coding agent at a database instead, see MCP server.