agents/ that exports a system prompt, optional tools,
optional memory, and optional skills. The runtime auto-discovers it on startup
and exposes it via getAgent(id) or a route created with createAgUiHandler().
For the normal path, omit model and let runtime conventions choose: local
inference by default, Veryfront Cloud when VERYFRONT_API_TOKEN plus project
context are set.
Prerequisites
- A Veryfront project running locally (see Create project).
- A provider configured for inference (see Providers).
- The
agents/directory exists. If you customisedai.agents.discovery.pathsin Configuration, use that directory instead.
Define an agent
Create a file inagents/:
id is how you reference the agent later with getAgent("assistant").
You can also define an agent with markdown when the agent only needs persona,
model, and step configuration:
agents/support.md registers
support and can be invoked through the same project runtime and control-plane
surfaces as agents/support.ts.
Per-agent skills and tools
A markdown agent can own its skills and tools by using a directory instead of a single file. Put the agent definition inAGENT.md and colocate its
capabilities beside it:
agents/{id}.md form still works
for agents that do not own skills or tools, and both layouts can coexist.
Colocated capabilities are registered with owner metadata and namespaced
{agentId}--{name}. Ownership controls visibility everywhere: an agent only
ever sees unowned (project-global) capabilities plus its own - never another
agent’s. This one rule applies to skills: and tools: for every agent kind
(TypeScript, flat markdown, and directory markdown):
- Omit
skillsor useskills: trueto advertise and authorize every skill visible to the agent. Useskills: []to advertise none and to authorize no project or configured skill forload_skill. - The difference between omitting
skillsandskills: trueshows only when a project has no skills at all. An agent that omitted it gets no skill tools, because there is nothing for them to load.skills: trueis a declaration, so the tools stay whatever the registry holds. tools: true- every currently scoped tool is authorized, while non-bootstrap schemas are deferred behindtool_searchuntil the agent searches for them.skills: [..]/tools: [..]- each entry resolves as the agent’s own short name first, then as a global id. A colocated short name that shadows a global id is reported at discovery so the reference stays unambiguous.- Use
denied-tools: [..]in Markdown agent frontmatter to preserve explicit tool denials.deniedToolsis also accepted for serialized definitions. Do not combine either form withtools: true: the serialized runtime cannot represent “all except”, so it fails closed and disables every project tool. List the allowed tools explicitly when you also need denials. - Duplicate agent ids (flat file + directory) and agent ids whose sanitized
namespaces collide are reported as discovery errors.
The same catalog metadata is used by local and hosted runtime paths. Hosted
skill loading uses the catalog
sourcePath, not a path reconstructed from the namespaced id, soload_skill("researcher--cite")resolves to the actual colocatedSKILL.md.
Add tools
Agents call tools to take actions or fetch data. Reference tools by name: the framework resolves them from thetools/ directory:
temperature controls model sampling and defaults to 0. It does not guarantee
repeatable output. Runtime provider capabilities may omit or normalize the value
for models that reject generic sampling parameters or require mode-specific
values.
maxSteps limits how many tool-call iterations the agent can perform per
request. See Tools for how to define getWeather.
Load broad tool catalogs progressively
Thetools selector controls both authorization and initial schema exposure:
- Omit
toolsto expose no project tools. - Use an explicit map to expose only those selected schemas immediately.
- Use
tools: trueto authorize every tool in the current scope while initially exposing only bootstrap tools andtool_search.
tool_search fallback is provider-neutral. It searches the
authorized tools catalog and configured providerTools that the selected
model supports. Provider-native entries contain only a name and description
until a search loads them. The runtime attaches the provider’s native schema on
the next model step.
Search ranks an exact tool name first, followed by normalized substrings in the
tool name, description, and input parameter descriptions. It returns at most
five names and descriptions. Results never include schemas, and tool_search
has no pagination options.
Loading a schema never authorizes a tool. The runtime rechecks authorization
before execution. It also filters restored loaded-tool state against the
currently authorized catalog.
You can use deferred loading with a direct provider and its API key without
Veryfront Cloud. Hosted durable runs additionally require the Veryfront API
durable run-event contract. The hosted runtime stores loaded-tool state in a
private checkpoint and waits for that checkpoint before continuing. Private
checkpoint data does not appear in public messages or replay. Configured,
supported provider-native tools use the same private exposure checkpoint.
Provider replay is not part of this feature.
See Tools for the search and execution flow.
Enable provider tools
Provider tools are executed by the selected model provider. They are not local tools and they are not MCP tools.Connect MCP servers
UsemcpServers for remote MCP-compatible tool servers. Put visibility policy
on the server that owns the tools. When tools is an explicit object, include
the remote MCP tool name in tools and authorize it with the server
toolPolicy.
Explicitly named tools that are not local are resolved from the Veryfront API
MCP server when mcpServers is omitted and the server bootstrap is available.
This lets a project pulled from Studio run locally without repeating transport
configuration. VERYFRONT_API_URL selects the API endpoint;
VERYFRONT_API_TOKEN and VERYFRONT_PROJECT_SLUG provide server-side identity.
These environment variables do not grant tools by themselves.
tools/list remains authoritative, and browser AG-UI context
cannot replace server identity. Set mcpServers: [] to opt out. An explicit
mcpServers list overrides the default; use { kind: "veryfront-api" } with a
toolPolicy when the connection policy should travel with the agent.
Use skills
Skills are reusable instruction packs discovered from your project’sskills/
directory. Every agent receives the visible skill catalog and load_skill
automatically.
skills: ["incident-response", "repo-maintainer"] to advertise and
authorize only those skills. Use skills: [] to advertise no skills and to
authorize none for load_skill. An explicit selector is an authorization
boundary for load_skill, not just a prompt filter.
Local and project runtimes also expose load_skill_reference and
execute_skill_script. Hosted chat reads an advertised reference through
load_skill({ load: { skillId, file } }) and does not execute skill scripts
directly.
See Project structure for skills/ conventions and
Configuration for discovery paths.
Skill execution flow
When an agent uses a skill, the flow is:- Call
load_skill({ load: { skillId } })to load the skill instructions and policy. - Read an advertised reference with
load_skill_reference(...)on local and project runtimes, orload_skill({ load: { skillId, file } })in hosted chat. - On local and project runtimes, optionally call
execute_skill_script(...)to run scripts fromscripts/. - Continue with normal tool calls. Loading a skill does not change which tools the run may call.
load_skill with other tool calls. The runtime runs the calls
in the order the model emitted them. A successful load_skill changes only
which skill’s instructions are loaded and which reference and script files
load_skill_reference and execute_skill_script can reach for later calls.
Ordinary tools are unaffected, whether they were emitted before or after
load_skill, and a failed load_skill does not block the rest of the batch.
Skill script execution
Skill scripts run in one of two modes, selected automatically:- Local (development): When no Veryfront Cloud sandbox credentials are
available, scripts run as direct subprocesses on your machine via
runCommand(). No remote sandbox is needed. - Cloud (production): When
SANDBOX_AUTH_TOKEN,VERYFRONT_API_TOKEN, or request-scoped Veryfront credentials are available, scripts are uploaded to and executed inside a remote sandbox session.
Skill safety model
allowed-toolsinSKILL.mdis not enforced. The Agent Skills specification defines it as pre-approval metadata (tools an agent may run without prompting), not an authorization boundary, so Veryfront records the declaration and does not restrict the run. Narrow a run by configuring the agent’s tools, not by declaringallowed-toolsin a skill.- Skill file reads are restricted to the skill root and allowed subdirectories:
references/,resources/,assets/, andscripts/. - Symlinked paths are rejected for skill file access.
- Script execution timeout defaults to
60000ms and is capped at300000ms.
Connect to a route
Expose a registered agent throughcreateAgUiHandler() when a browser or
external client needs AG-UI streaming.
Use Create agent for the copyable
quick-start route. Use Chat UI to pair that route with
useChat().
If a route returns Agent not found, ensure the agent file is in agents/ and
its id matches the value passed to createAgUiHandler().
Non-streaming response
For server-side generation (e.g., ingetServerData), use generate().
getAgent() returns Agent | undefined, so narrow the result before calling
it. Without the guard, the sample fails typecheck under the "strict": true
tsconfig that veryfront init writes.
One-shot calls
For a single call with no tools and no follow-up turn - an extraction, a classification, a rewrite - you do not need an agent at all. Usegenerate
from veryfront/llm:
outputSchema an agent does.
Reach for an agent instead when you need the thing itself rather than the
answer: a registered id other code resolves, tools, memory across turns, or a
system prompt built at request time. To hold such an agent to a single
tool-free turn, say so:
maxSteps: 1 stops the runtime from taking a second turn it has no use for.
skills: false removes the load_skill family from the request in a project
that does have skills - an agent with one job should not be offered a catalog
it will never open.
Structured output
SetoutputSchema to constrain every response to a schema. Veryfront maps it to
the selected provider’s native structured-output field, then parses and
validates the model’s text back into response.object, typed from the schema
with no annotation of your own.
outputSchema to generate() or stream() to constrain a single request
instead; a per-call schema replaces the configured one. generate() returns an
object typed from the per-call schema when you pass one. A raw JSON Schema
object is accepted in both places and is sent to the provider unchanged.
A requested schema is never dropped silently. A model runtime that does not
support structured output rejects the request, and output that does not parse or
does not validate raises rather than returning a partial object.
A run that stops at the step limit still returns its partial result instead of
raising. On that path the final assistant text is parsed best effort: a
successful parse sets response.object, and a parse or validation failure sets
response.metadata.outputSchemaError next to the max-steps warning so the
failure stays visible.
Runtime UTC context
Veryfront captures UTC once at the start of everygenerate(), stream(), and
respond() run. The runtime adds the same server-authored system block before
each model step:
result.metadata?.runtimeContext; streaming runs emit them in the initial data
event named veryfront.runtime_context for durable replay and diagnostics.
Dynamic system prompts
Thesystem property accepts a string, a function, or an async function:
resolveRuntimeState
instead of relying on system() to run again mid-turn.
request.system is always a string, so existing text transformations remain
compatible. When the runtime has structured system messages, use
request.structuredSystem to read their provider metadata and return
structuredSystem to replace them without flattening that metadata.
Agent configuration
Each agent middleware invocation receives a single-use
next() continuation.
Its lifecycle is:
- Call
next()at most once while the middleware is active. - The continuation becomes invalid when that middleware’s returned promise
settles. Calling it again or after settlement rejects with the registered
middleware-error. - If you call
next()after the middleware function returns, downstream dispatch starts on the next microtask so settlement revocation wins races with an already-fulfilled middleware promise. - If you intentionally detach that promise, attach its rejection handler in the same turn or a microtask; detached-failure reporting runs on the next macrotask after that grace window.
- Handlers attached after the macrotask grace window will not suppress the report. Synchronous throws also settle the middleware invocation and revoke its continuation.
- Direct self-resolution of a continuation is rejected with
TypeError. Indirect promise-adoption cycles are outside this middleware contract and are not diagnosed by the chain. - Re-entrant
next()calls made by Promise species or other framework-internal result-observation hooks are rejected; they are not middleware-body calls.
Verify it worked
Save the agent file and restartveryfront dev. The quickest server-side
check is a throwaway debug route:
RunFinished event.