Originally published on the ByteChef blog. Part 10 of the ByteChef Embedded series.
Some automation requests are fuzzy. Those deserve a conversation. But a lot of them aren't:
"When a lead comes in, post it to #sales and email them a welcome note."
That's already a full spec. A chat would only slow the user down.
So we built New from Prompt: the user writes a sentence, clicks Generate, and gets a finished, validated workflow back. Your app makes one HTTP call. No streaming, no transcript, no client-side AI code.
Here's how it works and what it takes to ship it in your own product.
TL;DR
- One request:
POST /api/embedded/v1/automation/workflows/generatewith{prompt, systemPrompt?}, signed with the connected user's JWT. - ByteChef creates a draft workflow in the user's own project, runs its workflow agent in autonomous mode, and returns the new workflow's UUID once it's built and validated.
- The agent only uses integrations you enabled plus connection-free components (webhooks, schedules, conditions, loops).
- The result is a draft. Nothing runs until the user reviews and publishes it.
Where it fits
ByteChef Embedded gives your users five ways to create a workflow:
| Option | The user... | Best when |
|---|---|---|
| New from Template | Picks a workflow you built | You know the common automations up front |
| New from Embedded Workflow Builder | Drags nodes on ByteChef's canvas | The user is comfortable with visual builders |
| New from Prompt | Writes one description, gets one workflow | The request is short and clear |
| New from Chat | Talks it through with an agent | The request is fuzzy |
| New from Custom Workflow Builder | Fills in a UI you designed | You want full control |
Prompt is the lowest-effort AI option, for the user and for you.
The dialog
Two fields: what the user wants, and an optional system prompt with your house rules.
The form rejects anything under ten characters, so "Slack" alone doesn't hit the server:
While ByteChef works, the button reads "Generating...". When the UUID comes back, the dialog closes and the list reloads. Our lead workflow took 47 seconds.
The component is a plain react-hook-form dialog:
const formSchema = z.object({
prompt: z.string().min(10, 'Please describe the workflow in a sentence or two.'),
systemPrompt: z.string().optional(),
});
const handleSubmit = async (values: FormValues) => {
setError(null);
setIsSubmitting(true);
try {
const workflowUuid = await generateWorkflow(values.prompt, values.systemPrompt);
form.reset();
onClose();
onWorkflowGenerated(workflowUuid);
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to generate workflow. Please try again.');
} finally {
setIsSubmitting(false);
}
};
All the AI lives on the server. The browser collects two strings and waits.
One call
export async function generateWorkflow(prompt: string, systemPrompt?: string): Promise<string> {
const trimmed = systemPrompt?.trim();
const response = await fetchWithAuth('/api/embedded/v1/automation/workflows/generate', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(trimmed ? {prompt, systemPrompt: trimmed} : {prompt}),
});
if (!response.ok) {
throw new Error(`Failed to generate workflow: ${response.status}`);
}
const uuid = (await response.text()).trim();
return uuid.startsWith('"') && uuid.endsWith('"') ? uuid.slice(1, -1) : uuid;
}
On the wire:
POST /api/embedded/v1/automation/workflows/generate
Authorization: Bearer <connected user's JWT>
X-Environment: DEVELOPMENT
Content-Type: application/json
{
"prompt": "When a new lead arrives on a webhook with name, email and company, post it to the #sales Slack channel and send the lead a short welcome email from Gmail.",
"systemPrompt": "Always notify the team in Slack, never by email. Keep workflows under five steps."
}
HTTP/1.1 200 OK
Content-Type: application/json
"8b0c5d1e-..."
Two things to know:
- It's synchronous. ByteChef answers when the agent is done. Small workflows take under a minute, big ones several. The server waits up to ten minutes.
-
Send
X-Environment. It defaults toPRODUCTION, which is rarely what you want while developing.
Four shapes of the same endpoint
| Called from | Create new | Rebuild existing |
|---|---|---|
| Browser (user's JWT) | POST /automation/workflows/generate |
POST /automation/workflows/{uuid}/generate |
| Your backend (API key) | POST /{externalUserId}/automation/workflows/generate |
POST /{externalUserId}/automation/workflows/{uuid}/generate |
The rebuild form powers a "Change it with AI" button: same UUID, rewritten definition. The backend form lets your server generate a starter automation right after onboarding, so the user's Automations page isn't empty on day one.
What happens on the server
- Resolve user and project. ByteChef reads the external user ID from the JWT and finds (or creates) their personal project for that environment.
- Create a draft. An empty workflow is saved first, labeled with the user's prompt (cut to 80 chars).
- Build the allow-list. Every component with an enabled integration in this environment, plus every connection-free component.
-
Run the agent autonomously. The same BUILD agent behind New from Chat and ByteChef's Copilot, with
autonomous: true. - Return the UUID. On failure or timeout, the draft is deleted. A failed generation leaves nothing behind.
Autonomous mode
In autonomous mode the agent:
- never asks follow-up questions; it picks the most common, sensible default
- never pauses to show a plan
- keeps the label it was given
- saves once, at the end, after the full workflow validates
Otherwise it's the same agent: it searches the catalog (searchTask, listTasks), reads definitions (getTaskDefinition, getTaskProperties), validates each task (validateTask), then validates the whole workflow before saving. What you get back isn't a guess at JSON. It passed the same validator as a hand-built workflow.
Where the chat agent would stop and show a channel picker or a "Connect Slack" card, the prompt agent picks a value, builds the step, and leaves the connection for the user to choose in the editor.
Your house rules: the system prompt
The user decides what the workflow does. You decide how it's built:
Always notify the team in Slack, never by email.
Keep workflows under five steps.
Use the Europe/Berlin time zone for every schedule unless the user names another.
Never delete records in the CRM; update a status field instead.
Note how this plays with our example. The user asked for a welcome email to the lead; the rule says "never by email". The agent read the rule the way a colleague would: it's about notifying the team. It built three steps: webhook trigger, Slack message to #sales, Gmail welcome email to the lead.
How ByteChef treats it:
- Trimmed and capped at 4,000 characters.
- Applies to one generation only. Nothing is stored, so send it every time.
- Appended under a heading marking it as integrator instructions, applied only where it doesn't conflict with build rules, the workflow contract or safety constraints. It steers the agent; it can't hijack it.
In the sample it's a text box so you can play with it. In production, generate it in your code from your conventions and what you know about the user. Don't hand it to end users.
Security boundaries
Anyone with a connected user's JWT can call this with any text. So ByteChef trusts nothing in the request except the two strings:
- The workflow is always the user's. Rebuild looks the UUID up inside the user's project only.
- Agent state is built server-side. Mode, workflow ID, project and environment don't come from the request.
- The agent stays in the user's project. No touching other workflows, no project-level changes.
- The allow-list is enforced everywhere. Components outside it don't show up in search, can't be looked up, and are rejected on validation, including inside conditions, loops and AI agent nodes.
That last one matters more here than in chat. A chat agent asked for "Salesforce deals to Slack" when you don't offer Salesforce can ask what to do instead. A prompt agent can't ask, so it never produces nodes for apps your users can't connect.
Handing off to the editor
The generated workflow is a real ByteChef workflow, so send the user straight to the editor:
const router = useRouter();
<GenerateWorkflowDialog
// ...
onWorkflowGenerated={(workflowUuid) => router.push(`/automations/${workflowUuid}`)}
/>
Here's what the agent built from the lead prompt:
It set the Slack channel to sales, wrote a message referencing the webhook payload (${autoRespondWithHTTP200_1.body.name} and friends), addressed the Gmail step to the lead, and even added a workflow description.
The editor shows what's left:
-
2 errors: the Slack and Gmail connections. The agent can't pick accounts for the user. With
connectionDialogAllowed={true}, they connect right there. -
5 warnings: webhook fields. A webhook payload has no fixed shape, so the editor reminds you to check
body.name,body.emailandbody.company.
Draft to running
Generated workflows are drafts. Nothing runs until published and enabled, which is the right default for something an AI just wrote:
await fetchWithAuth(`/api/embedded/v1/automation/workflows/${workflowUuid}/publish`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({description: 'Generated from prompt'}),
});
await fetchWithAuth(`/api/embedded/v1/automation/workflows/${workflowUuid}/enable`, {method: 'POST'});
Prompt vs. Chat
| New from Prompt | New from Chat | |
|---|---|---|
| Requests | One POST, one JSON response | One AG-UI stream per turn |
| Agent mode | Autonomous, picks defaults | Interactive, asks questions |
| Missing connection | Builds the step, user connects in editor | Shows a Connect card in the thread |
| Code in your app | A form and a fetch | A chat component and its deps |
| Best for | Clear, short requests | Fuzzy requests |
They combine well: generate a first version from a prompt, then offer "Change it with AI" via the rebuild endpoint.
Prerequisites
bytechef.ai.copilot.enabled=true- An AI provider configured for the environment
- Integrations enabled for the environment (without them, the agent can still build schedules, webhooks and logic)
Embedded workflows and workflow generation are Enterprise Edition features.
Before you ship it
- Raise timeouts on every proxy, load balancer and serverless function in the path (many stop at 30 or 60 seconds), or call the backend endpoint from a job and notify the user.
- Show real progress. "Generating..." is fine for ten seconds, not two minutes.
- Map errors to messages users can act on.
- Redirect to the editor with the returned UUID.
- Own the system prompt in code.
-
Send
X-Environmentexplicitly. - Rate-limit generation. Every request burns model tokens on your AI provider account.
- Seed better examples. Use your own integrations in the placeholder, or offer clickable starter prompts.
Who owns what
You: the button, the form, the system prompt, which integrations are enabled, the waiting experience, what happens with the UUID.
ByteChef: the per-user project and draft, the agent and its tools, catalog search and validation, the allow-list and security boundary, the editor, ConnectDialog and credential storage, and the engine that runs the result.
Your code is a form, one fetch and a redirect. Behind it is an agent that knows every action in your catalog.
Try it
Enable the copilot and an AI provider in ByteChef, run the embedded sample app, and open Automations → New from Prompt. Describe something small with one of your connected apps and click Generate.
ByteChef is open source (Apache 2.0). If this was useful, a ⭐ on GitHub helps a lot.
The full series: connect apps, ComponentKit, AI chat assistant, event triggers, workflows as APIs, MCP agents, custom builder, templates, New from Chat, and New from Prompt. All on the ByteChef blog.







Top comments (0)