Prompts

A prompt is what an llm command sends to a model: a system text, a user text, and the history of the exchange so far. Prompts are part of the agent meta, not of the definition: they are edited with the draft, validated with it and published with it. Each prompt has a name, and an llm command names the prompt it uses in its prompt field.

For example, an agent that answers questions about orders could have this prompt:

Nameorders
SystemYou answer questions about orders. Answer in under 100 words.
User{ sys.query }
Scopeconversation
DocAnswers questions about orders, with the getOrder tool.

The agent's llm command names it:

"llm": { "profile": "gpt5_mini", "prompt": "orders", "streamOutput": true }

Fields

FieldTypeDefaultDescription
systemPromptTextThe system prompt. It must evaluate to text.
userPromptTextThe user prompt. Evaluated at each LLM call that uses this prompt. It must evaluate to text, a content part ({contentType: text|image}), or a list of them.
scope"conversation" | "agent" | "local""conversation"How long the prompt's history is kept.
docstringWhat the prompt is for. Shown to draft help in place of the prompt's text.

A prompt needs a system text, a user text, or both. A prompt name follows the same rules as other names in an agent: a letter first, then letters, digits and underscores. An agent may have up to 10 prompts, and the system text, the user text and the doc may each be up to 20,000 characters.

Where prompts are edited

In the UI, an agent's prompts are on the Prompts tab of the draft page. Add prompt adds one. Prompts change only through a draft: edit them there, validate, and publish. Validation compiles each prompt with the draft, so a prompt that reads a variable the agent never sets is refused before it can run.

A publish that changes only the prompts keeps the agent's version. The agent's next run uses the new text. A prompt that an llm command names cannot be dropped: remove the command's use of it in the same draft. Publishing records which prompts were added, removed or changed in the audit log, by name, never their text.

When the published agent's prompts, const, title or run limits changed after the draft was made from it, publishing the draft lists the fields that differ. You can merge those changes into the draft, or publish again to replace them with the draft's values. See Publishing.

How a prompt is evaluated

The system and user text are written like any other string in an agent: plain text, or a Python expression in braces. An expression is evaluated when the llm command runs, in the function where that command runs. It can read:

  • local variables set earlier in the same function: { city };
  • the function's arguments: { topic } in a function called with topic;
  • agent and conversation variables: { agent.mood }, { conv.lang };
  • the sys namespace: { sys.query }, { sys.userEmail };
  • the agent's const: { const.city }.
"system": "{ 'You answer questions about ' + const.product + '. Reply in ' + const.language + '.' }",
"user": "{ f'{sys.query} (customer tier: {tier})' }"

The rules of every other expression apply: only allowlisted functions, no dunder or private names, and printable ASCII inside the braces. Text outside ASCII is fine in values, including const values, just not written directly inside { ... }. Use an escape such as \u00e9, or keep the text in const.

Prompt texts are stored in the cloud in plain form. Text that is sensitive belongs in the const, which is stored encrypted, and the prompt reads it as { const.name }.

The two texts are evaluated at different moments:

  • The system text is evaluated on every llm call that uses the prompt, and replaces the previous system text. A system text that reads a variable follows that variable.
  • The user text is evaluated once per llm command and added to the history as one user turn. The tool round trips inside that command do not add it again.
  • After an agent pauses on ask and resumes, neither text is evaluated again. The llm command continues from where it stopped.

The trace shows each call's prompt name, scope, system text and user text as an input.

Why a prompt cannot read result

A prompt is written outside every command, so at an llm call result is whatever the previous command happened to leave there. Reorder the commands, or insert one, and the prompt silently reads something else. Validation therefore refuses a prompt that reads result. Save the value into a variable before the llm command and read that variable in the prompt. Validation checks that the variable is set before every llm call that uses the prompt.

"api": { "profile": "orders", "path": "{ f'/orders/{orderId}' }" },
"var": { "order": "{ result }" },
"llm": { "profile": "gpt5_mini", "prompt": "summary" }

The prompt named summary then reads the variable in its user text:

Namesummary
SystemYou write short, friendly summaries of orders for customers.
User{ f'Summarize this order: {order}' }
Scopelocal

What the texts may evaluate to

The system text must evaluate to text.

The user text may evaluate to:

  • text: { sys.query };
  • a content part, either text, {"contentType": "text", "text": "..."}, or an image, {"contentType": "image", "data": "<base64>", "mimeType": "image/png"};
  • a list of those, which becomes one user turn with several parts.

Content parts have the same shape as the parts an agent sends to the user with the output command. An image the agent received or produced can be passed straight into a prompt. This user text sends a photo from the agent's inputs with a question:

{ ['What is in this picture?', {'contentType': 'image', 'data': sys.inputs['photo'], 'mimeType': 'image/jpeg'}] }

Anything else is refused before the model is called, with an error that names the prompt and the field. That includes a number, true or false, a dict that is not a content part, an unknown contentType, an image without a mimeType, and a list inside a list:

  • prompts.chat.user must be text, a content part or a list of them, not int.
  • prompts.chat.user item 2 must be text, a content part or a list of them, not int.
  • prompts.chat.user is an image part without a mimeType.
  • The expression at prompts.chat.system is expected to evaluate to str but it evaluated to int.

To put a number or a structure into a prompt, convert it yourself, for example { str(count) } or { json.dumps(order) }. You then decide how it reads.

Scope

Each prompt keeps a history: the user turns and the model's answers. The scope decides how long that history is kept and which agents share it.

ScopeHistory
conversationThe default. One history for the whole conversation, shared with every agent in the conversation that has a prompt of the same name and scope. A second agent can see what was said to the first.
agentThis agent's own history for the whole conversation. Another agent in the same conversation has a separate history for its prompts.
localA history for one run only. It starts empty on every run, survives an ask and resume within that run, and is shared by every function in the run that uses the prompt.
  • Use local for one-off tasks inside a run, such as classifying, extracting or summarizing, where earlier runs should not influence the answer.
  • Use agent when the agent should remember its own exchanges with the user but not mix them with other agents'.
  • Use conversation when several agents take turns in one conversation and should share context.

An agent may have several prompts, and each keeps its own history. Several prompts can be used in one run. Using the same prompt twice in a run continues its history.

What stays in the history

When an llm command finishes with an answer, the history keeps the user turn and the answer. The tool calls the model made along the way, and their results, are removed. A later call, or the next agent in the conversation, sees what was asked and what was answered, not the steps in between. To keep something a tool returned, store it in an agent. or conv. variable.

If an llm command fails, its user turn is removed again, so a retry does not leave a duplicate question in the history.

An llm command may run with an empty user text only when the prompt already has a history and the command offers tools. That lets the model continue working with tools without a new question. Otherwise an empty user text is an error.

Sharing a history between agents

A conversation can move from one agent to another, as it does when a follow-up search matches a different agent. Nothing links the two agents. What connects them is the prompt name: the second agent continues the first agent's exchange only when it has a prompt with the same name and the conversation scope. A prompt with another name starts a history of its own, and the model behind it knows nothing of what was said before.

Agents in an account are written by different developers at different times, so the name has to be agreed in advance. Choose one standard prompt name for the account, such as chat, and give every agent that should see the earlier exchange a prompt with that name and the conversation scope. An agent that should not share, because its exchange is private to it or is a one-off task, uses another name with the agent or local scope.

When the second agent's prompt has a system text, that text replaces the first agent's at the second agent's first llm call, so each agent gives the model its own instructions while the history of user turns and answers continues. The history holds what was asked and answered, not the tool calls in between; see What stays in the history.

The history is one of two things a later agent inherits. The other is the conversation variables, conv., which any agent in the conversation can set and read. See Agents building on each other.

Prompts in functions, loops, tools and parallel branches

Prompts work anywhere an llm command can run:

  • Functions: the expression sees the function's own local variables and arguments.
  • Loops: each iteration evaluates the user text again, so { item } follows the loop variable.
  • Tool functions: a function the model calls as a tool can run its own llm command, and its prompt sees the tool function's locals.
  • Parallel branches: each branch can run its own llm command.

One rule applies to all of these: two llm commands that run at the same time must use different prompts. A tool function cannot use the prompt of the llm command that called it, and two parallel branches cannot use the same prompt. Doing so fails with The prompt 'chat' is already in use by another llm command that has not finished: one in another parallel branch, or the llm command that called this tool. llm commands running at the same time must use different prompts. Give each one its own prompt, for example summarise_left and summarise_right.

Errors you will see, and when

When you validate a draft, before anything runs:

  • a syntax error in a prompt expression, reported at the field, for example prompts.chat.user;
  • a variable that may not be set when the llm command runs: 'city' may not be set at the llm call at main.commands.if.then.llm;
  • an agent or conversation variable that nothing sets: 'agent.mood' is not set anywhere in the agent;
  • a prompt that reads result;
  • a function that is not in the allowlist, or a secret that does not exist;
  • an llm command that names a prompt the agent does not have;
  • a const key the agent's const does not have;
  • more prompts than the limit, or a text longer than the limit.

When the agent runs:

  • an expression that fails, such as a missing dict key or an index out of range: Could not evaluate the Python expression at prompts.chat.user: list index out of range;
  • a system or user text of the wrong type, as listed above;
  • a prompt already in use by another llm command, as listed above.

Documentation

The doc field is plain text that says what the prompt is for. Draft with AI sees each prompt's name and doc, never its text, so a useful doc helps the AI choose the right prompt for each llm command.