Workflows

A workflow is a readable, versionable description of a recurring job: when it wakes up, which integrations it uses, what it may do on its own, and where it has to stop and ask you. This page is the config format, the five templates, and how workflows move between machines.

From a sentence

You do not usually write a workflow by hand. In Chat, or on the Talk page, describe the chore:

Every weekday at 8am, triage my inbox. Real asks from teammates become Linear tickets. Newsletters get archived. Mail from my manager gets a draft reply. Anything you're not sure about, ask me.

The assistant checks which integrations you have, writes the config, and shows you a plain-language preview — when it runs, what it handles silently, what it will ask about — before saving anything. In the typed chat a saved workflow starts as a draft you can review; by voice it is saved active so it is ready to run.

The config

Every workflow is one JSON object. This is the shipped inbox-triage template, trimmed:

{
  "workflow_id": "inbox-triage-morning",
  "name": "Morning Inbox Triage",
  "description": "Triage unread mail before standup: file real asks, archive noise, draft replies to my manager, and ask me about anything genuinely unclear.",
  "status": "active",
  "trigger": { "type": "cron", "schedule": "0 8 * * 1-5", "timezone": "America/New_York" },
  "mcp_tools": ["gmail", "linear", "slack"],
  "steps": [ ... ],
  "completion": { "notify": "slack", "channel": "#daily-triage", "message": "..." },
  "memory": { "learn_from_decisions": true, "preference_key": "inbox_triage_rules" },
  "confidence_threshold": 0.7
}

Top-level fields

Field Meaning
workflow_id Stable identifier, used in URLs, the CLI and schedules. Lower-case with hyphens.
name What the UI shows.
description The sentence you would say to a colleague. It is also part of the executor's instructions for the run.
status active runs on its trigger; paused keeps the schedule but does not fire; draft has not been switched on yet.
confidence_threshold Optional. Overrides CONFIDENCE_THRESHOLD for this workflow only. A read-only monitor can be bolder than one that files tickets.

trigger

Field Meaning
type cron, webhook, event or manual.
schedule Five-field cron for cron triggers: minute, hour, day of month, month, day of week. Validation rejects anything else.
timezone An IANA zone such as America/New_York. Defaults to UTC. The welcome screen asks for yours so generated schedules are right.
path For webhooks: the path the signal arrives on.

A webhook or event trigger whose payload is empty never reaches the executor — the graph's trigger → executor edge carries that condition, so an empty signal costs no model call.

mcp_tools

The integrations this workflow may use, by registry name: gmail, linear, slack, github, web (a credential-free page fetcher), browser and code_interpreter (AgentCore's built-ins). An unknown name is a validation error; a known one without credentials is a warning, so you can design ahead of connecting.

steps

A list of objects the executor reads as its plan. The templates use a small vocabulary:

  • A fetch step: {"id": "fetch_emails", "action": "gmail.search_threads", "params": {"query": "is:unread newer_than:12h", "max_results": 20}}
  • A recall step: {"id": "recall", "action": "memory.recall_preferences", "params": {"preference_key": "inbox_triage_rules"}}
  • A classify step: {"id": "classify", "action": "llm_classify", "input_from": "fetch_emails", "categories": ["teammate_request", "newsletter", "manager", "automated", "ambiguous"]}
  • Automatic actions: {"id": "auto_actions", "rules": [{"category": "newsletter", "action": "gmail.archive", "auto": true}, ...]}
  • The human gate: {"id": "human_gate", "category": "ambiguous", "action": "interrupt", "present": ["email_summary", "sender", "suggested_action", "reasoning", "confidence"], "options": ["file_ticket", "archive", "draft_reply", "skip"]}

A workflow with no human_gate step validates with a warning: it will act entirely on its own, which is fine for read-only work and risky for anything irreversible.

completion

Where the run reports when it finishes. notify is console, slack or sns; channel is the Slack channel; message is a template that may use {auto_count}, {interrupt_count} and {memory_count} — how many items it handled alone, how many you decided, how many it handled from rules you set earlier.

memory

learn_from_decisions turns each of your answers into a rule; preference_key names the bucket those rules live in, so a Slack digest never applies rules learned from your inbox.

Validation

Every path to a saved workflow — the chat, the Talk page, a workspace import — runs the same validation. Blocking errors: a missing workflow_id, name, trigger or steps; an unknown trigger type; a cron trigger without a schedule or with the wrong number of fields; an unknown integration; anything the schema rejects. Warnings: no integrations, an unconfigured integration, no human gate.

The five templates

They are loaded into your workspace the first time Handoff starts, ready to run or edit:

Template Trigger Uses
Morning Inbox Triage weekdays 08:00 gmail, linear, slack
PR Review Triage weekdays 09:00 github, slack
Weekly Competitor Pricing Watch Mondays 09:00 web, browser, slack
Slack Channel Digest weekdays 18:00 slack, linear
Meeting Follow-up event gmail, linear

The source files are in src/handoff/workflows/. The competitor watch reads a live page through the credential-free web server, so a fresh clone can exercise one real integration with no key at all.

Running

Run now on the overview or the workflow page starts a run and streams a live feed under the row. From the terminal, handoff run <workflow_id> starts one and returns when it completes or stops for you. Schedules fire from an in-process scheduler while handoff serve or the desktop app is open; for schedules that must fire when your laptop is shut, see Deploy.

workspace.yml: export and import

A workspace — its workflows, custom skills, custom agents and tool servers — renders as one YAML document you can edit, version and hand to a teammate. Credentials are never in it.

handoff workspace export workspace.yml        # the document
handoff workspace export team.zip             # a bundle: the document plus one file per skill and agent
handoff workspace import workspace.yml        # or team.zip

The same three actions are on the workspace's Settings page. Import has the file is the truth semantics: things the file names are created or updated, and things the workspace had that the file no longer lists are removed. Edit the YAML in place and save it, and the workspace follows.