doo.ai

doo.ai lets a script ask a Tabidoo AI agent a question and use the answer like any other value β€” summarize a long note, classify an incoming request, translate a text, extract data from a document, draft a reply. You write the prompt, the agent answers, your script decides what to do with the answer.

It is the same agent technology the AI assistant in the application uses; doo.ai is the way to reach it from your own scripts.


Where you can use it

Place

Works

Workflow β€”

Run Script

step (server)

yes

Button field, Free HTML field, form scripts (browser)

yes

Calculated field

no β€” a calculated field is a single synchronous expression,

callAgent

is asynchronous

Always await the call:

const res = await doo.ai.callAgent('Summarize the text in three sentences.');

callAgent

The short form

Pass a string and you get the answer from the default agent:

const res = await doo.ai.callAgent(`Classify this request as Complaint, Question or Order:\n${doo.model.text?.value}`);
console.log(res.message);

The full form

Pass an object when you need more control:

const res = await doo.ai.callAgent({
prompt: `Summarize the following note in two sentences:\n${doo.model.note?.value}`,
context: 'You write in Czech, in a neutral business tone.',
agentShortName: 'my-agent',
useWebSearchTool: false
});

Property

Meaning

prompt

The question or instruction. Required.

context

Extra instructions for the agent β€” role, tone, output format.

model

Code of the LLM model to use. Leave out to use the agent's own model.

agentShortName

Short name of the agent to call (from the AI Agents table).

agentId

Id of the agent record β€” an alternative to

agentShortName

.

agentApplicationId

The application the agent is defined in, when it is not the current one.

useSystemAgents

Allows Tabidoo's built-in agents to be used.

useTabidooDocumentationTool

The agent may search the Tabidoo documentation.

useTabidooGetStructureTool

The agent may read the structure of the application (tables, fields).

useTabidooGetTsDefinitionTool

The agent may read the TypeScript definitions of the application.

useWebSearchTool

The agent may search the web.

attachments

Files for the agent:

[{ fileName, fileData, mimetype }]

,

fileData

in base64.

chatState

The conversation so far β€” see

Asking follow-up questions

below.

Every tool you switch on makes the call larger and more expensive, so switch on only what the prompt really needs.

What you get back

{
message, // the agent's answer as text
inputTokens, // tokens sent to the model
outputTokens, // tokens the model produced
credits, // the cost of this call, counted against your AI limit
chatState, // the conversation, to pass into the next call
fullAgentResult,// the raw result of the run
debugLogs // which tools the agent loaded and used
}

Examples

Write the answer into a record

const res = await doo.ai.callAgent({
prompt: `Summarize this customer note in one sentence:\n${doo.model.note?.value}`
});
if (!res?.message) {
doo.workflow.stop();
}
await doo.table.updateFields('tickets', doo.model.id, { summary: res.message });

Ask for a result you can process

The answer is plain text, so ask for the shape you want and check it before you use it:

const res = await doo.ai.callAgent({
prompt: `Return only JSON in the form {"category":"...","priority":1-5} for this request:\n${doo.model.text?.value}`
});
let data = null;
try {
data = JSON.parse(res?.message || '');
} catch (e) {
console.log('The agent did not return valid JSON: ' + res?.message);
}

Asking follow-up questions

chatState carries the conversation. Send back what the previous call returned and the agent remembers what was said:

const first = await doo.ai.callAgent('List the three main risks of this project.');
const second = await doo.ai.callAgent({
prompt: 'Now write one mitigation measure for each of them.',
chatState: first.chatState
});

Store chatState in a field (as text) when the conversation should continue in a later run.

Sending a file to the agent

const file = doo.model.attachment?.value?.[0];
const f = await doo.table.getFileBase64('tickets', file.fileId);
const res = await doo.ai.callAgent({
prompt: 'Read the invoice and return the invoice number and the total amount.',
attachments: [{ fileName: f.fileName, fileData: f.content, mimetype: f.mimeType }]
});

Your script as a tool of an agent

The other direction is possible as well: a script can be a tool that an agent calls (AI Tools, tool type Simple Script). Inside such a script:

  • doo.ai.aiToolParam holds the parameters the agent passed in,
  • doo.ai.writeToAiToolResult(value) returns the result to the agent.
const id = doo.ai.aiToolParam?.orderNumber;
const rows = (await doo.table.getData('orders', { filter: `number(eq)${id}`, limit: 1 })).data;
doo.ai.writeToAiToolResult(rows[0]?.fields ?? 'Order not found.');

Cost and limits

Every call is paid for in AI tokens. The credits value of the response is what is counted against the AI token limit of your plan. In a browser script you can read the current consumption with doo.environment.getCurrentUserTechnicalLimits() β€” item aiTokensCount, with the limit of your plan next to it.

Even a short prompt is not cheap. An agent carries its instructions and its tools into every call, so a one-word answer can still mean thousands of input tokens. In a measured example a single trivial call cost roughly 14,000 input tokens. The more tools an agent has switched on, the higher the number.

A workflow run is stopped after 90 seconds. One AI call takes seconds, so plan for one or two per run. Never call the agent in a loop over hundreds of records β€” process them in batches or in one prompt.

The answer is not deterministic. The same prompt can return a differently worded answer next time. Anything you write into a field or compare against should be checked in the script first.


Troubleshooting

message comes back empty. The call did not fail and no exception was raised β€” check that agentShortName, agentIdand agentApplicationId point to an agent that really exists and that you have access to. Always test res?.messagebefore you use it.

The answer is in the wrong language or format. Put the requirement in context ("Answer in Czech, return only the number"), not only in the prompt.

Nothing happens in the workflow. Open the run log of the workflow. An AI call that was not written with await ends as an unhandled rejection, and the rest of the step runs without an answer.

Too many requests / the AI limit is reached. aiTokensCount is a limit of your plan, not a bug. Reduce the number of calls, shorten the prompts, or switch off the tools the agent does not need.