Studio: an agent with no code
Build an agent in the console: its instructions, requests to your API, the fields it collects; tried, deployed, and written as code.
Studio builds an agent in the console, with no class, no project and nothing to run: its instructions, how it opens a call, the requests it may make to your own API, and what it collects from the caller. You talk to it on the same page, deploy it to production with a button, and point a number at it. When it outgrows the form, Download code turns it into a class you keep building in your editor.
How it works#
A Studio agent is one definition, kept by the gateway, which answers the agent's calls itself. For everything else it is an agent like any other: it has settings (the voice, the models, the language), a knowledge base, a lexicon, numbers, personas, judges and calls you read in the console.
Every save is a release, numbered and never edited. The sandbox is where you build and try it; Deploy to production copies a release there, where your callers reach it.
Make one#
Open the sandbox's Agents#
https://cloud.pinecall.io/sandbox/agents, or pinecall console and Agents.
New agent#
A name in lower-case words and dashes (front-desk), free among the org's agents and apps. It
starts from a short template and opens on its Studio row.
Write it, try it, save it#
The form is on the left, a live chat with the agent on the right. Save keeps the form as the next release, and the next message of the chat already runs on it.
A Studio agent is a hosted app: it counts against the org's hosted apps in the environment, the sandbox and production each their own. The free trial holds one in the sandbox; in production each is billed as an app is.
Instructions#
The agent's prompt: who it is, what it does, how it speaks. It is read once when a call starts and cached for every turn, so the call's own values are filled in then:
| you write | the model reads |
|---|---|
{{call.from}} |
the caller's number, or the web visitor's id |
{{call.to}} |
the number they called, or the agent |
{{call.today}} |
today's date where the agent is |
{{call.channel}} |
phone, web or whatsapp |
{{call.direction}} |
inbound or outbound |
{{caller.name}} |
the contact's name, when it is known (caller.phone, caller.email too) |
{{metadata.order}} |
a value the call was opened with, by whoever opened it |
A value that is not there is left empty. Pinecall adds its own rules after yours, the same every agent gets: invent nothing a tool did not say, one question at a time, answer in the caller's language, and how to write for the channel the call is on.
Greeting#
How a call opens: nobody speaks first, say these words (said as written, the same every call), or let the model open with this instruction ("greet them by name if you know it"). Set here, it wins over the agent's settings.
Tools#
A tool is one HTTP request to your API that the model may make, and what your API answers is what the model reads.
| field | what it is |
|---|---|
| name | one word the model calls it by: letters, digits and _ |
| what it does | the description the model chooses it by. Say when to call it |
| method | GET, POST, PUT, PATCH or DELETE |
| URL | https:// only. {id} in the path is filled from the parameter of that name |
| headers | sent with every request. A secret is named as {{secrets.AGENDA_KEY}} |
| parameters | what the model fills: a name, a type (string, number, integer, boolean), a description, whether it is required |
| says first | a line the agent says as the request starts, when its own turn said nothing |
| waits | seconds before the request is given up: 10 unless you say, 30 at most |
A parameter named in the URL's path goes there, encoded; the others are the query of a GET or a
DELETE, and the JSON body of the rest. A status past 2xx is an error the model reads with the
body, so it can tell the caller what went wrong.
Try sends the tool's request once, with the values you type, as a call would send it — the form as it is, saved or not — and shows the status, the time and the body.
Secrets#
A key your API wants is a secret of the org's, set in Agents ▸ Secrets or with
pinecall secrets set AGENDA_KEY, in each environment: the sandbox can use a test key under the same
name. It is named in a header's value and nowhere else — a secret in the instructions, a description or
a URL is refused when you save — never shown back, and never reaches the prompt, the call's log or a
tool's result: an API that echoes the header it was sent has the value shown as *** in the answer,
in Try, in the log and to the model. A definition naming a secret the environment does not have is
refused when you save it; a deploy checks production's.
What a request may reach#
The gateway sends the request, so it is held to the public internet: https alone, every address
the name resolves to public (never a private network, the cloud's metadata address or a name of the
platform's own), no redirect followed, and the answer read up to 256 KB.
Data collection#
Fields the agent gathers from the caller, each a name, a type, a description and whether it is
required. The model gets a collect tool and saves each value as the caller gives it; the call's
state holds them, and the console shows them beside the conversation. Every turn the agent is told
what it has and what is still missing, one question at a time.
| option | what it does |
|---|---|
| end the call once the required fields are in | the model may hang up then, after the closing |
| closing | what the agent does once every required field is in: "Thank them, say goodbye and end the call" |
| send the results to | an https:// URL the results are posted to when the call ends, with its own headers |
What is posted, as JSON:
{
"agent": "front-desk",
"call": "call_4aae7acb2ba74b26",
"env": "production",
"from": "+59899123456",
"to": "+59821000000",
"channel": "phone",
"fields": { "name": "Ana Ruiz", "reason": "a check-up" },
"complete": true,
"reason": "agent_hung_up",
"transcript": [ { "role": "agent", "text": "…" }, { "role": "user", "text": "…" } ]
}
Test#
A Studio agent's tests are its cases: the same rows the Cases screen shows for any agent, kept by the gateway — a real call a judge broke on, or one a person wrote. Test, under the definition, lists them. New case writes one by hand: what the caller says, one line each, and what the agent must do — say this, never say that, call this tool. Run plays every approved case not held out against the agent the gateway holds, with no terminal and nothing to start, and says under each whether it held or broke, with the judge's reasons where it broke. A case that waits for a decision, was dismissed or is held out is listed and not played, and says why.
Voice and models#
Studio does not choose them: they are the agent's settings, in Configure, linked from the Studio row. A realtime model works there as for any agent.
Deploy to production#
Deploy to production copies the sandbox's newest release into production as its next release, and production's calls run on it from the next one. It takes a person who may act in production, and production's own secrets: a key set in the sandbox alone is said before production answers. In production the Studio row is read-only: the release answering, and edit it in the sandbox, then Deploy.
A call keeps the release it opened on. To go back, Agents ▸ Apps rolls back to any kept release; Stop takes it off the line without losing anything.
Download code#
The agent as a TypeScript project of the framework's, zipped:
| file | what it holds |
|---|---|
agents/<name>/agent.tsx |
the class: the instructions as its docstring (or in render() when they name the call's values), the greeting, one @tool method per request, and collect with its view |
package.json · tsconfig.json |
@pinecall/agents at this platform's version |
AGENTS.md |
what the agent is, its tools and the secrets it reads, for you and for a coding assistant |
README.md |
how to run it |
The call's own values the instructions name become the class's: {{call.from}}, {{call.to}},
{{call.channel}}, {{call.direction}} and {{call.today}} read this.call, {{caller.name}},
{{caller.phone}} and {{caller.email}} its caller, {{metadata.order}} a key of its metadata, and
{{secrets.NAME}} the process's environment. A {{metadata.…}} deeper than one key is not on
this.call, so the class names each one it had to leave empty in a comment above it, never silently.
From there it is a class like any other: add stages, state and
conditions in render(), and run it with pinecall start or
pinecall deploy. Its secrets are the same org secrets, read from the
process's environment. Once the class answers, remove the Studio agent of the same name: a name is
one agent.
What Studio does not do#
No stages, no conditions in the prompt, and no state of your own beyond the fields it collects: those are what a class is for, and Download code is the way there. Neither are client-side tools or MCP servers as tools.
From a terminal and from an assistant#
The same definition, from anywhere:
$ pinecall studio new front-desk
$ pinecall studio pull front-desk # front-desk.studio.json
$ pinecall studio push front-desk --note "books the visit"
$ pinecall studio try front-desk find_slot day=2026-10-21
$ pinecall chat front-desk # a written call, as from the console
$ pinecall cases --agent front-desk # its cases, the ones Test lists
$ pinecall test --case no-dice-un-precio --agent front-desk # one of them, played as Run plays it
$ pinecall studio deploy front-desk
$ pinecall studio code front-desk # front-desk.zip
Every verb is in pinecall studio; cases and
test --case read and play a Studio agent's cases as any agent's, through the
gateway. The MCP's studio tool builds one end to end
from your assistant (An agent with no code). The endpoints
underneath are Studio's API.