Skip to content

2. Create the agent

The agent is the part that answers. You configure it once in the console, test it there, and only then wrap it in a widget.

Open your project, go to the Agents tab, and select New Agent. The editor has three tabs: Instructions, Knowledge & Tools, and Security. Libraries and files can only be attached after the agent exists, so start with Instructions.

Name appears in the console and in the widget’s default brand name. Something concrete like Billing helper beats Assistant.

Description is a note for your team. It is not sent to the model.

Model lists every model your organization has credentials for. An advanced agent writes JavaScript and reads its own errors, so pick a model that is good at code. A weak model burns steps rewriting the same broken script.

Mode stays on Advanced, which is the default for a new agent.

An advanced agent does not answer from the model’s own knowledge. It works through a per-thread workspace of JavaScript files, using three tools:

  • write(name, code) saves a file, for example list_orders.js. It does not run it.
  • execute(name) runs a saved file and returns the result as an observation.
  • final_answer(answer) ends the turn with a reply in Markdown.

Every file ends with await urai.complete(value), and that value becomes the observation. Inside a file the agent has fetch, meta.secrets.NAME for the secrets you allow, and meta.vars.some.path for context. It loops, writing and running and reading results, until it calls final_answer or hits the step cap.

Two properties of the loop matter when you write instructions:

  • Each execute runs in a fresh runtime. Saved files persist for the thread, but variables do not survive between runs, and files do not import each other.
  • The agent’s real capability comes from what you give it: libraries it can import, skills it can load, and secrets it may read. Those are steps 3 and 4.

Simple mode is the alternative. It runs a plain chat loop over a fixed set of tools and cannot use libraries or skills. Use it for an agent that only answers questions, and Advanced for one that actually does work in your systems.

System instructions go above the mechanics in the agent’s prompt, so write about role, scope, and judgment rather than about write and execute. The runtime already explains those.

You are the in-app agent for Acme, a time-tracking product.
You help customers with timers, timesheets, approvals, and invoices. Work
through the Acme API library rather than guessing; never state an amount, a
date, or a status that did not come from a call you made.
Keep replies under four sentences unless the user asks for detail. When you
cannot complete something, say what you tried and what you need.
You never disclose secret values, internal ids, or another customer's data.
Scope every call with `meta.vars.organization_id` and `meta.vars.user_id`.
Refunds and cancellations need a human. Explain the process and point the user
at Settings > Billing rather than attempting it.

Three habits pay off:

  • Point at the library or skill you want used, by name. Advanced agents will write their own HTTP calls if you leave a gap, and hand-rolled calls are how wrong tenants get read.
  • State the refusals and the escalation path. What the agent must not do matters as much as what it should.
  • Mention the context you pass in from your app in step 7, so the model knows those vars exist.

Select Create Agent. Urai saves it and opens the Knowledge & Tools tab.

Knowledge Collections on that tab lists every collection in your organization. Select the ones this agent may read. Collections hold the PDF, Word, slide, and Markdown files you upload under Knowledge, or push with the Knowledge API.

Selecting at least one gives the agent three tools. With none selected it has none of them, so it cannot see a document at all.

Tool What it returns Arguments the model chooses
list_documents The documents that exist, grouped by collection, each with its id, pages, and chars collections, name_contains, path
search_documents The passages that match a query, each with the document it came from and a reference to cite query, collections, documents, path, limit
read_document Whole documents, up to 20 and about 40,000 characters in one call documents, document, from_seq

The model picks those arguments. You send none of them.

Which tool the agent starts with depends on the question. A fact (“what is our refund window”) is a search. A set (“total my invoices”, “compare the October reports”) starts with list_documents, because searching only tells the agent what matched one guess.

Two things follow from attaching a collection:

  • The description matters. The agent’s prompt names each attached collection with the first line of its description, so the model chooses where to look before its first call. Write that line for the model, not for your team.
  • The code the agent writes can read the same documents. It imports urai:knowledge and streams a file of any size without the text passing through the conversation. Use the tools for what the model must quote, and the library for what the code must process. See Read documents from code.

What you attach here is the minimum the agent always has. A conversation can add collections on top of it, and nothing a client sends can take an attached one away.

A collection can be organized into folders. You make them under Knowledge with New folder, and nested folders are made in one go: type policies/2026 and both levels are created. A folder name cannot contain a slash, because a slash is what separates the levels.

The three tools all understand folders.

  • list_documents names the folders a collection has, and gives paths for every document filed outside the collection root. Pass a folder as path to list it, subfolders included.
  • search_documents takes path as well, and it needs exactly one collection in collections, because a folder belongs to one collection. With more than one in scope the tool answers with an error that says so, and the agent names a collection and calls again.
  • read_document accepts a name qualified by its folder, "policies/2026/leave.pdf". That is how the agent picks between two documents that share a name. A bare name still looks through the whole collection.

A document can be filed in more than one folder. Two entries in paths are one file in two places, not two copies, and one read_document reads it.

path only narrows a search. A folder is worth naming when the user named one, and worth leaving out when the agent is not sure which folder holds the answer. A wrong folder returns nothing, and an agent that reads nothing as “we have no such document” answers wrongly with confidence.

So tell it the layout in the instructions:

Billing documents are in the `billing-policies` collection.
- Policies in force are filed under `policies/2026`.
- Superseded ones are under `policies/archive`. Read them to explain what
changed, and never quote them as current.
- Signed order forms are under `contracts/<customer slug>`.
When the user names a year or a customer, call `list_documents` with that
`path` first. When you do not know which folder applies, search without one.

The same folders work in the code the agent writes:

import { knowledge } from "urai:knowledge";
const filed = await knowledge.list({
collection: "Billing policies",
path: "policies/2026",
});
const doc = await knowledge.open("policies/2026/leave.pdf");

Open the Security tab. These four fields decide what the agent’s code can reach, and they are the difference between a useful agent and an incident.

Allowed secrets lists the secret names the agent’s code may read as meta.secrets.NAME. Nothing else resolves. Add only what this agent needs, and tell it in the instructions never to print a secret.

Network allowlist is one host per line. An empty list means no restriction, so fill it in before the agent goes anywhere near production. List your own API hosts and nothing else.

Step cap bounds the write and execute iterations in a single turn, up to 50. On the last allowed step the agent is told to answer with what it has. For a chat widget, 10 to 15 keeps replies quick; long research agents want more.

Variables is a JSON object merged into meta.vars on every run. Use it for static context that never changes per conversation:

{ "api_base": "https://api.acme.com", "region": "eu-west-1" }

Urai always injects thread_id, organization_id, and user_id alongside your variables, and the widget can add per-conversation values on top. The agent’s prompt lists the variable names it can read, so anything you put here is visible to the model by name.

Skill tags is covered in step 4.

Open the agent’s Threads tab and start a conversation. You see each write and execute inline, so you can read the code the agent produced and the observation it got back.

Watch for three failure patterns:

  • The agent invents an endpoint. It needs a library, not a better prompt.
  • The agent loops on the same error. Usually a missing secret or a host that is not on the allowlist.
  • The agent answers without running anything. Tighten the instructions to say when a call is required.

The agent runs code, reads a real observation, and answers from it. The gaps you found in this test are the libraries you write next.

Next: publish a library.