Skip to content

Custom Agent

A Custom Agent is an AI agent that belongs to an app. It takes the inputs you declare (text, numbers, true/false values, one file) and returns structured output items. Calls run as the signed-in user, against the Agent’s published definition.

Get an Agent instance using getAgent from the app’s interface. Getting the instance sends no request.

Parameters
Parametertype
agentIdString
Syntax
const agent = kf.app.getAgent(agentId);

Get contract

Reads the Agent’s published inputs, outputs and readiness.

Syntax
const contract = await agent.getContract();
Return
{
AgentId: "AgXXXXXXXXXX",
Definition: {
Name: "Request summariser",
Inputs: [
{ Key: "request", Name: "Request", DataType: "Text", Required: true },
{ Key: "amount", Name: "Amount", DataType: "Number", Required: true }
],
Outputs: [
{ Key: "summary", Name: "Summary", DataType: "string" },
{ Key: "needs_review", Name: "Needs review", DataType: "boolean" }
],
MaxOutputItems: 1
},
Readiness: { Ready: true, Issues: [] }
}

Execute

Starts a run. It resolves as soon as the run is accepted, not when it finishes: use onExecutionComplete or getExecution for the result.

Parameters
ParametertypeDescription
options.inputDataObjectValues keyed by the input Keys in the contract.

An Attachment input takes one file: { Key, FileName, Size? } for a Kissflow attachment, or { Url, FileName } for an https URL. A file from kf.client.openFilePicker or uploadFile has lowercase keys, so map it; passing it as it is fails with a validation error.

const file = await kf.client.uploadFile(fileInput.files[0]);
await agent.execute({
inputData: { invoice: { Key: file.key, FileName: file.name, Size: file.size } },
});
Syntax
const { ExecutionId, Status } = await agent.execute({
inputData: { request: "Two standing desks for the design team", amount: 54000 },
});

execute is never retried. If it fails with AGENT_EXECUTE_OUTCOME_UNKNOWN, the run may still have started; AGENT_EXECUTE_NOT_SENT means it did not. App code cannot list runs, so after AGENT_EXECUTE_OUTCOME_UNKNOWN tell the user the request may still be in progress instead of starting another run.

Get execution

Reads a run’s current state. Output.items is present once the run is Completed, Error once it is Failed. More statuses may be added, so treat any status other than Completed or Failed as still in progress. People who are not the app’s developers can read only their own runs.

Syntax
const execution = await agent.getExecution(executionId);
Return
{
ExecutionId: "…",
Status: "Completed", // Queued | NotStarted | Running | Completed | Failed
Output: { items: [{ summary: "Two standing desks for the design team.", needs_review: true }] },
CreatedAt: "…", StartedAt: "…", CompletedAt: "…"
}

On execution complete

Calls onComplete once when the run finishes, with the same object getExecution returns. A Failed run is a result, so read Status. onWatchError is called instead if observation stops: the host does not support it, access was refused, or about six minutes passed (AGENT_OBSERVATION_STOPPED; the run may still finish, so read it again later). Returns a function that stops observing; it never cancels the run.

Syntax
const stop = agent.onExecutionComplete(
ExecutionId,
(execution) => {
if (execution.Status === "Completed") show(execution.Output.items);
else showFailure(execution.Error);
},
(error) => warn(error.errorCode),
);
// later, for example when the page unmounts
stop();

Errors

Methods reject (and onWatchError receives) { isError: true, error, errorCode }. There is no message: read error for the text and errorCode to decide what to do. errorCode is a platform code (KISSFLOW_ERROR_…) or an AGENT_… code, including: AGENT_HOST_TIMEOUT, AGENT_REQUEST_NOT_SENT, AGENT_EXECUTE_OUTCOME_UNKNOWN, AGENT_EXECUTE_NOT_SENT, AGENT_HOST_UNSUPPORTED, AGENT_WATCH_INVALID_EVENT, AGENT_OBSERVATION_STOPPED, AGENT_READ_TIMEOUT (Kissflow did not answer a read in time; try again), AGENT_OFFLINE (a read while the user is offline), AGENT_INVALID_REQUEST (an Agent or run id that is not an id), AGENT_NOT_IN_APP (called outside an app), AGENT_WATCH_ID_IN_USE, AGENT_UNEXPECTED_RESPONSE and AGENT_REQUEST_FAILED (a network or unclassified failure). Show the error text for a code you do not handle.

Example

The callbacks live as long as the page, component or script that registered them; stop observing when it goes away.

Form, field, gallery and table scripts are stopped after one minute, and they and page event scripts also stop when the next script starts on the same form, component or page. A pending call or observation ends with the script, and neither callback is called. In those scripts, don’t wait for the result: keep the ExecutionId (for example, on the item) and read it later with getExecution, or run the Agent from a custom component or custom page.

const agent = kf.app.getAgent("AgXXXXXXXXXX");
const { ExecutionId } = await agent.execute({
inputData: { request: "Two standing desks for the design team", amount: 54000 },
});
const stop = agent.onExecutionComplete(
ExecutionId,
(execution) => {
if (execution.Status === "Failed") return showFailure(execution.Error);
const [result] = execution.Output.items;
showSummary(result.summary, result.needs_review);
},
(error) => showNotice(`Still waiting? Check again later (${error.errorCode}).`),
);