Turn One Sentence Into a Working Automation: Prompt-to-Workflow in Your SaaS
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 slo

Posted on Oct 30 • Originally published at blog.bytechef.io
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.
ByteChef Embedded gives your users five ways to create a workflow:
Prompt is the lowest-effort AI option, for the user and for you.
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 ); } }; Enter fullscreen mode Exit fullscreen mode All the AI lives on the server. The browser collects two strings and waits.
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; } Enter fullscreen mode Exit fullscreen mode 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." } Enter fullscreen mode Exit fullscreen mode HTTP / 1.1 200 OK Content-Type: application/json "8b0c5d1e-..." Enter fullscreen mode Exit fullscreen mode Two things to know:
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.
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.
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. Enter fullscreen mode Exit fullscreen mode 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.
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.
Anyone with a connected user's JWT can call this with any text. So ByteChef trusts nothing in the request except the two strings:
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.
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 } ` ) } /> Enter fullscreen mode Exit fullscreen mode 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.
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 ' }); Enter fullscreen mode Exit fullscreen mode 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.
Embedded workflows and workflow generation are Enterprise Edition features.
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.
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.