Code Workflows
Code workflows use TypeScript to define deterministic, multi-step behavior. While prompt-based workflows let the AI decide what to do, code workflows give you precise control over execution.
You don’t need to write code yourself. The Workflow Builder can generate code workflows from natural language descriptions.
When to use code workflows
Use code workflows when you need:
- Deterministic execution — Same inputs always produce the same sequence of steps
- Complex orchestration — Multi-step processes with branching logic
- Precise formatting — Structured output that follows exact templates
- Aggregation patterns — Collecting data from multiple sources before acting
Use prompt-based workflows when you need:
- Flexible reasoning — Adapting to unexpected inputs
- Open-ended tasks — “Do what makes sense”
- Simple actions — Label this email, respond to this message
How code workflows work
Workflows run TypeScript in a sandboxed environment. Code is organized into named steps that enable caching and replay.
import { step, tool, complete } from './workflow-runtime';
// Step 1: Gather emails
const emails = await step('gather', async () => {
return await tool('search_emails', {
query: 'newer_than:1d is:unread',
maxResults: 100
});
});
// Step 2: Send summary
await step('send', async () => {
const list = emails.map(e => `- ${e.subject}`).join('\n');
await tool('send_email_to_user', {
subject: `Daily Summary: ${emails.length} emails`,
body: `Here are your unread emails:\n\n${list}`
});
});
complete();Core API
step(name, fn)
Execute a named step. Steps are the unit of caching and replay.
const result = await step('step_name', async () => {
// Your code here
return someValue;
});Rules:
- All logic must be inside step callbacks
- Step names must be unique within a workflow
- Steps execute in order; each waits for the previous to complete
tool(name, params)
Call a tool. Same tools available to prompt-based workflows.
const emails = await tool('search_emails', {
query: 'from:newsletter',
maxResults: 50
});invokeWorkflow(options)
Call a named AI agent for tasks needing intelligence. See Calling Agents.
const result = await invokeWorkflow({
agent: { type: "name", name: "summary_writer" },
context: { emails, count: emails.length },
sync: true
});
if (!result.success) throw new Error(result.error);
const summary = result.result;complete()
Signal workflow completion. Every workflow must call this at the end.
complete();Available tools
Code workflows have access to the same tools as prompt-based workflows:
| Tool | Description |
|---|---|
search_emails | Search Gmail |
read_email | Read specific email content |
add_label | Add label to email |
archive_email | Archive email |
send_email_to_user | Send email to you |
query_session_history | Query what other workflows have done |
list_calendar_events | Get calendar events |
create_calendar_event | Create events with attendees |
web_search | Search the web |
Example: Daily summary
import { step, tool, invokeWorkflow, complete } from './workflow-runtime';
// Gather filtered emails from session history
const data = await step('gather', async () => {
const result = await tool('query_session_history', {
agent_slug: 'town-auto-inbox',
since_hours: 24,
tool_names: ['add_label', 'archive_email']
});
return result.toolCalls;
});
// Group by label
const formatted = await step('format', async () => {
if (data.length === 0) return null;
const grouped = {};
for (const toolCall of data) {
const label = toolCall.args?.label_name || 'Other';
if (!grouped[label]) grouped[label] = [];
grouped[label].push(toolCall);
}
let output = '';
for (const [label, items] of Object.entries(grouped)) {
output += `**${label}** (${items.length} emails)\n`;
for (const item of items) {
output += `- ${item.args?.subject || 'No subject'}\n`;
}
}
return output;
});
// AI writes intro/outro
const parts = await step('write', async () => {
if (!formatted) {
return { intro: 'All quiet today!', outro: '' };
}
const result = await invokeWorkflow({
agent: { type: "name", name: "summary_writer" },
context: { emailCount: data.length, list: formatted },
sync: true
});
if (!result.success) throw new Error(result.error || "Summary writer failed");
return result.result;
});
// Send the summary
await step('send', async () => {
await tool('send_email_to_user', {
subject: 'Auto-inbox Summary',
body: `${parts.intro}\n\n${formatted || ''}\n\n${parts.outro}`
});
});
complete();Restrictions
- No code outside steps — All logic must be inside
step()callbacks - Must call
complete()— Every workflow must end with this - Sandboxed environment —
fs,child_process,net, etc. are blocked - Tool calls may pause — Approval-required tools can pause workflow for user approval
Related
- Steps & Caching — How replay works
- Calling Agents — Using AI in code workflows