Skip to content

Execute and render actions

Open in ChatGPT Open in Claude

An Execute and Render action does two things: it runs work, then renders UI. After your execute(params) resolves, you implement render(data) to return a UI component (a Chatterblock) that the agent embeds directly in the chat stream. Users interact with that component, and you return data back to the agent.

This is the visual counterpart to an execute-only action. Use Execute and Render when the user needs to see or edit something in the chat: a form, a card, a confirmation dialog. Use execute-only when you need data returned as text.

sequenceDiagram
  participant U as User
  participant A as Agent
  participant E as execute(params)
  participant R as render(data, host, ...)
  U->>A: Natural-language request
  A->>E: Call execute handler
  E-->>A: Return data object
  A->>R: Pass data to render
  R->>R: Build Chatterblock UI
  R->>U: Interactive component in chat
  U->>R: Interact (edit, confirm)
  R->>A: callback(value)
  A->>U: Continue conversation with submitted data
Render function parameters mapped to the Chatterblock UI Edit Task #42 Title: Fix login redirect bug Body: Cancel Save header host data callback cancel

Your render function receives these arguments, in order.

ParameterTypeDescription
dataanyThe value returned by your execute handler (e.g., an object or array of results).
hostHTMLElementThe container element where you should append your custom UI nodes (forms, cards, lists, etc.).
headerHTMLElementA header element you can style or populate with a title, instructions, or status messages.
callback(value: string, disableOnSubmit?: boolean) => voidCall this when the user submits. Pass the return value to the agent. By default, disableOnSubmit is true (the SDK auto-disables your UI after submit). Set it to false to not disable the UI component after submit.
cancel() => voidCall this if the user aborts, so the agent resumes without new data. A default “Cancel” control is also rendered in the chat UI.

This show_task action fetches a task in execute, then renders an editable card in render. Setting awaitUserInput: true tells the agent to pause until the user saves or cancels. Save passes the edited fields to callback; cancel aborts.

foldspace("when", "ready", () => {
foldspace.agent({ /* …common setup… */ })
.addActionHandlers({
show_task: {
// 1. Fetch the task by ID
execute: async (params) => {
const { taskId } = params;
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${taskId}`
);
const data = await response.json();
// Return the task object to both agent and renderer
return data;
},
// 2. Tell the agent to wait for user input before continuing
awaitUserInput: true,
// 3. Render an editable card UI
render: (task, host, header, callback, cancel) => {
const style = document.createElement('style');
style.textContent = `
.task-card {
border: 1px solid #e3e3e3;
border-radius: 8px;
margin-top: 8px;
font-family: sans-serif;
overflow: hidden;
}
.task-card-header {
background-color: #f5f5f5;
padding: 12px 16px;
font-weight: bold;
}
.task-card-body {
padding: 16px;
}
.task-card-body label {
display: block;
margin-bottom: 4px;
font-size: 14px;
}
.task-card-body input,
.task-card-body textarea {
width: 100%;
margin-bottom: 12px;
padding: 8px;
box-sizing: border-box;
font-size: 14px;
}
.task-card-footer {
padding: 12px 16px;
text-align: right;
background-color: #fafafa;
}
.task-card-footer button {
margin-left: 8px;
padding: 8px 16px;
cursor: pointer;
}
`;
header.appendChild(style);
// 2. Build the card container
const card = document.createElement('div');
card.className = 'task-card';
// Header section (title)
const cardHeader = document.createElement('div');
cardHeader.className = 'task-card-header';
cardHeader.textContent = `Edit Task #${task.id}`;
// Body section (form fields)
const body = document.createElement('div');
body.className = 'task-card-body';
const titleLabel = document.createElement('label');
titleLabel.textContent = 'Title:';
const titleInput = document.createElement('input');
titleInput.type = 'text';
titleInput.value = task.title;
const bodyLabel = document.createElement('label');
bodyLabel.textContent = 'Body:';
const bodyInput = document.createElement('textarea');
bodyInput.rows = 4;
bodyInput.value = task.body;
body.append(titleLabel, titleInput, bodyLabel, bodyInput);
// Footer section (buttons)
const footer = document.createElement('div');
footer.className = 'task-card-footer';
const saveBtn = document.createElement('button');
saveBtn.textContent = 'Save';
saveBtn.onclick = () => {
callback({
id: task.id,
title: titleInput.value,
body: bodyInput.value,
});
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.onclick = cancel;
footer.append(cancelBtn, saveBtn);
// 3. Assemble and mount
card.append(cardHeader, body, footer);
host.appendChild(card);
}
}
});
});