# Create Workflow

> Author a brand-new workflow definition (no workflow exists yet). Two dispatch channels: (1) task dispatch — the master consolidates the application's user tasks into a reusable workflow; (2) chat dispatch — author from the user's explicit description or the work done so far in this conversation (the master in team mode, or the solo agent in solo mode). Each node gets an agent for its role the SAME way in solo and team — reuse an existing agent, or author the sub-agent's full spec INLINE on the workflow via `agent_specs`; mode affects only close-out (chat vs tasks), never how nodes get their agents. Use when: asked to create a workflow from prior tasks, "build a workflow that…", "create a workflow based on the work done so far in this chat", or told to use the create-workflow skill. NOT for: editing an existing workflow (use update-workflow), polishing a template import (use import-workflow), executing or scheduling workflows.

- Skill: `clode-labs/create-workflow` (Agent Skill)
- Install (CLI): `npx skillmds@latest add clode-labs/create-workflow`
- Raw SKILL.md: https://api.skillmd.com/api/skills/clode-labs/create-workflow/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: clode-labs (https://skillmd.com/u/clode-labs)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/clode-labs/create-workflow

---


# Create Workflow

Construct a brand-new workflow and save it with `aramb_mcp.workflows_create`. **The
workflow does NOT exist yet** — the platform creates the row + nodes atomically on that
one call. Don't ask for a `workflow_id`; you don't have one and don't need one.
The response tells you the id the platform assigned.

> **If asked to UPDATE an existing workflow, use the `update-workflow` skill. If
> polishing a template-import draft, use `import-workflow`.** This skill only
> handles first-time creation.

## Non-negotiables — read these before you call `aramb_mcp.workflows_create`

0. **The workflow belongs to exactly ONE agent — either create it with `agent_id`, or
   attach it to an agent once the agent exists.** A workflow is an integral part of a
   single agent: discoverable and runnable ONLY by that agent, never a standalone,
   reusable-across-agents asset. **Two equally-valid orderings** get you there:
   - **Agent-first (default):** the agent already exists (or you create it first), so
     pass `agent_id="<that agent's id>"` on `aramb_mcp.workflows_create`. That one call
     creates the workflow AND stamps the ownership edge (create-and-link in one step).
   - **Workflow-first:** the builder wants to design and TEST the workflow before
     committing to an agent. That's fine — build it, iterate and Preview it on its own,
     then when they create (or pick) the agent, link it with
     `aramb_mcp.agents_attach_workflow` (the `agent_id` gets stamped and the workflow is
     re-filed under the agent's template project). Attach and create-with-`agent_id`
     **converge on the same end state** — owned by and filed under the agent.
   If a workflow is **meant for a specific agent**, don't leave it permanently unattached —
   bind it via `agent_id` at create time, or `aramb_mcp.agents_attach_workflow` once the agent
   exists. A workflow that is **not** tied to a particular agent is fine to leave standalone;
   binding is only for the ones that belong to an agent.

1. **Every `create` is a NEW, separate workflow. NEVER replace an existing one.**
   A project can hold many workflows side by side — `aramb_mcp.workflows_create` always
   adds a new one; it never touches what's already there. If the user says "create
   a workflow" and one already exists, you still **create a new one** — do NOT fall
   back to `aramb_mcp.workflows_update`, and do NOT overwrite the existing workflow's
   definition. The ONLY time you modify an existing workflow is when the user
   explicitly asks to *change/edit* that specific one — and then you use the
   `update-workflow` skill, never this one. Silently replacing a user's workflow is
   a serious failure.

2. **The workflow is part of the agent you're building, and stays a DRAFT — do NOT
   publish it as a build step.** `aramb_mcp.workflows_create` files the workflow under the
   agent (via `agent_id`, see #0) and leaves it a **draft** — it is NOT auto-published.
   The builder TESTS the draft via Preview (`aramb_mcp.workflows_run` works on the draft —
   see the `aramb-workflows` run section), and the workflow freezes into its live
   version **automatically when the AGENT is published** (`aramb_mcp.agents_publish`).
   There is **no separate "publish this workflow" step** for you to perform — never
   call a workflow-publish tool as part of building, and never tell the user to
   "publish from the Workflows tab" (that step does not exist in this model).
   Toolkit connections matter in **two** ways. (a) For a *run* to succeed — verify every
   external system up front with `aramb_mcp.toolkits_check_connection` and tell the user
   plainly which to connect. (b) **For the workflow to go LIVE at publish** — when the
   agent is published, a workflow whose steps require third-party toolkits is published
   ONLY if those toolkits are **CONNECTED**; otherwise it stays a draft and the publish
   response reports it as blocked, naming the missing toolkits. So when a workflow you
   built needs a toolkit, tell the builder which toolkits it requires and that they must
   be CONNECTED for it to go live: "The workflow ships when you publish the agent — but
   only once its toolkits are connected. Connect them on the **Integrations** page, then
   click Publish." Never call a toolkit-using workflow "live" before its toolkits are
   connected **and** the agent is published.

3. **Never claim a workflow ran unless the run tool said so.** When the user asks to
   run a workflow, call `aramb_mcp.workflows_run` and read its result. If it returns an
   error (e.g. "not published", wrong id), report THAT — do not say "it's running",
   and never substitute a different workflow to make the action appear to succeed.
   Run exactly the workflow the user named; if you can't, say why. **And once a run
   starts, hand off to it — the platform posts real progress and the final result to the
   conversation automatically, so never narrate fabricated progress ("4/382 scored,
   working through the rest…") you can't verify.** The conversation thread is the
   source of truth for run progress; `aramb_mcp.workflows_get` / `list` report only the
   workflow's definition/lifecycle state, not per-step run progress. See the
   `aramb-workflows` skill's run section.

## Two things to figure out first — read this before anything else

There are **two independent axes**. Do NOT conflate them — confusing them is what
makes workflows come out wrong. The crucial correction: **mode (team vs solo)
does NOT decide a node's persona. The WORK each node does decides it.**

### Axis 1 — Are you the MASTER (team) or the SOLO agent? → decides only the dispatch channel, NOT personas

Check your own tool list:
- **You HAVE the `aramb_mcp.tasks_*` tools → you are the MASTER (team mode).** You route
  work through tasks and close via `aramb_mcp.tasks_update`.
- **You do NOT have `aramb_mcp.tasks_*` → you are the SOLO agent (solo mode).** You do
  the work directly in-session and close out in chat.

**That is the ONLY difference between the modes** — close-out channel (tasks vs
chat). Both modes have the full MCP surface (toolkits, triggers, browser) and
author per-role sub-agents the same way — INLINE on the workflow via `agent_specs`.
**Assigning a node's agent works IDENTICALLY in both modes** — solo vs team does
not enter into the persona decision at all. See "Per-node persona — decided by the
work" below.

### Axis 2 — Task dispatch or chat dispatch? → decides spec source + close-out

- **Task dispatch:** the platform gave you a "Your task id" block (`application_id`,
  `project_id`, `task_id`). Spec source = the application's **user tasks**
  (`aramb_mcp.tasks_list`, ALL statuses — see step 1); each node may carry a
  `source_task_id`. Close out via `aramb_mcp.tasks_update`. (Only the master is ever
  task-dispatched.)
- **Chat dispatch:** no `task_id` — an ordinary chat turn. Spec source = the
  user's explicit description, or the work done so far in THIS conversation. Close
  out by **replying in chat** (the platform persists your final assistant text).

**The axes cross.** The **master can be in chat dispatch** — you just chatted
"build a workflow" to it; that's still team mode (you route through tasks and
close via `aramb_mcp.tasks_update`). (The platform `task_id` is NOT Claude's built-in
`TaskCreate` — unrelated; a `TaskCreate` entry does not make this a task
dispatch.)

### Per-node persona — decided by the work (both modes)

**Every node gets an agent suited to that node's role — and this is decided
IDENTICALLY whether you are solo or master. Mode does NOT affect this at all.** A
workflow node is a distinct unit of work; give it an agent that owns that work.

For each node, assign an agent for its role in ONE of two ways:

- **Reuse an existing agent if one already fits the role** — e.g. a roster
  persona that's already provisioned (`developer` for code / clone / implement,
  `backend-tester` / `frontend-tester` / `integration-tester` / `checker` for
  verify / review, `aramb-deployer` / `local-deployer` for deploys). Set the
  node's `assigned_agent` to that agent's name.
- **Otherwise author the sub-agent's FULL spec INLINE in the workflow's
  `agent_specs` array** and reference it by `name` from the node's
  `assigned_agent`. Name it for its role (`issue-triager`, `fix-implementer`,
  `qa-tester`, `pr-author`) and write it to the **template-grade bar**: a real
  `identity` / `soul` / `agentsDoc` — who it is, how it thinks, its operating
  playbook with tool routing, failure modes, and an explicit output schema — not a
  one-line persona. The `TemplateAgent` shape is
  `name` / `displayName` / `identity` / `soul` / `agentsDoc` / `skills` /
  `defaultModel` / `defaultBackend` / `defaultThinking` (see the `aramb-workflows`
  skill's `agent_specs` field + "Multi-agent workflow" example for the full
  contract).

**The specs travel WITH the workflow.** They ride on the same `aramb_mcp.workflows_create`
(or `update`) call — one `agent_specs` array alongside `nodes` + `edges` — and the platform
provisions them **deterministically at claim/run**. You do NOT route bespoke node
agents through a separate agent-creation flow: the Architect never uses the
agent-runtime `create-agent` path, and this skill does not either. A node whose
`assigned_agent` names neither an existing roster agent nor an `agent_specs` entry
is a **dangling reference**: the platform rejects it at author time for an agent-bound
workflow, and there is no "main agent" to fall back to. Every node must name either an
`agent_specs` entry, an existing roster agent, or the agent you are building (by its
routing name — see below).

**Do NOT branch this decision on solo vs team.** Solo is NOT limited to a bare
`"solo"` persona — that was the old, wrong behavior. Both modes author specs the
same way; the *only* thing solo vs team changes is close-out (solo replies in chat,
master creates/closes tasks). The reuse-if-exists step naturally means master
often reuses roster personas while solo authors fresh inline specs — but that's an
artifact of which agents already exist, not a rule keyed on mode.

**Authoring a spec per role is the DEFAULT; an empty `agent_specs` is the EXCEPTION.**
Reach for the exception only for a trivial single-node workflow, or one whose nodes are
all the same pure-glue / orchestration role. A multi-step workflow where each node does
distinct work (fetch → classify → draft → send, or triage → implement → test → PR) gets
a **distinct spec per role** in `agent_specs` — each a full template-grade persona with
its own `identity` / `soul` / `agentsDoc` — referenced by that node's `assigned_agent`.
Never collapse every node onto one shared agent because it was quicker to write.

**When a node really is the agent itself, name THE AGENT YOU ARE BUILDING — by its
routing name.** Read it from `aramb_mcp.agents_get` → `benji_agent_id` (e.g.
`inbox-digest-5248d2e7`) and put that exact string in `assigned_agent`. There is **no
`"main"` agent** — it is not a routing token, and writing it either dangles or silently
dispatches to an unrelated base agent, so the agent you built never runs its own
workflow. `"master"` and `"solo"` are platform INFRA agents (team orchestrator / solo
runtime); they are never the agent you are building, so do not reach for them either.

Everything else — node schema, `required_toolkits`, per-step `toolkit`, the
closing-instruction template, `default_node_settings`, the no-placeholders /
no-`env_variables` rules, and the one-shot `aramb_mcp.workflows_create` rule — is
identical across every combination.

## MUST rules — read before anything else

1. **Every node in `aramb_mcp.workflows_create` MUST carry `required_toolkits`.** Copy the array from each source task's `required_toolkits` (task dispatch) or infer it from the action the node performs (chat dispatch). Use `[]` (not omitted) when the node touches no third-party service.
   - **Failure mode:** Omitting `required_toolkits` means workflow Evaluate cannot flag missing connections at publish time, and the Required-toolkits row in the FE node panel renders empty. Empty array `[]` is correct when the node touches no third-party service — never omit the field.
   - **Declaring is the whole job — you do not connect the accounts.** As the workflow's author you never start OAuth, never mint or paste an authorization link, and never claim a toolkit is connected. The **user** connects each account in the console (agent **Tools** page / Integrations), on their own runtime project — the only project execution resolves against; an account authorized through the builder would land on a project that never runs. A workflow whose toolkits aren't connected yet is a perfectly good deliverable: it simply stays gated until the user connects them, which is exactly what the declaration is for. (This is about toolkit *accounts* only — it is no reason to avoid authoring a workflow when the job genuinely needs one.)
2. **Every node that touches a third-party service MUST carry a singular `toolkit`** — its *primary* toolkit slug, used for trigger-binding. The invariant the platform enforces: **`toolkit` MUST be a member of that node's `required_toolkits`.** A Gmail-fetch node is `toolkit:"GMAIL", required_toolkits:["GMAIL"]`; a node that reads Drive then writes Sheets is `toolkit:"GOOGLESHEETS", required_toolkits:["GOOGLEDRIVE","GOOGLESHEETS"]` (pick the one the trigger would bind to — usually the action the workflow is "about"). Omit `toolkit` (or pass `null`) only when `required_toolkits` is `[]`. The platform's MCP schema rejects a `toolkit` that isn't in `required_toolkits`.
3. **Ground every toolkit + trigger slug in the real catalog — never hallucinate.** Before drafting, call `aramb_mcp.toolkits_list_toolkits` to confirm the exact uppercase slugs (and, when the workflow will be event-triggered, `aramb_mcp.toolkits_list_triggers("<TOOLKIT>")` for trigger slugs). Do NOT infer slugs from prose. See "Ground the slugs" below. **Never invent a toolkit binding, and never bind a platform-internal/hidden toolkit** (`composio`, `composio_search`, `browser_tool`, `slackbot`, `discord`, `discordbot`, `microsoft_teams`) — `aramb_mcp.workflows_create` rejects those. For Slack/Discord/Teams messaging deliverables, deliver via the chat toolkit's `chat.send_dm` (no toolkit). See the `aramb-workflows` skill.
4. **No placeholder syntax in any node `prompt`.** No `{{env.KEY}}`, no `{{input.KEY}}`, no template substitution of any kind. There is no substitution layer — a literal `{{env.FOO}}` reaches the agent as the literal string `{{env.FOO}}`. The platform's MCP schema **rejects** any prompt matching `{{ env.… }}`. Write what the agent should do with the context that arrives in `<run_input>` instead (see "Run input — the only per-run channel" below).
5. **Do NOT declare `env_variables`.** Omit the field from the `aramb_mcp.workflows_create` call entirely. The column has no runtime path in v2 — declaring entries reads as "I wired up your API_KEY" when nothing consumes it. The platform's MCP schema rejects a non-empty `env_variables` map. Secrets/credentials are connected through the Composio account, not declared on the workflow.
6. **Every node's `prompt` MUST end with the workflow-step closing instruction** so the executing agent calls `aramb_mcp.workflows_update_step` (with the explicit `step_id` rendered into its dispatch) at the end of its run. See "Closing instruction per node" below for the exact template.
   - **Failure mode:** Without the closing instruction, the agent finishes its LLM session and the platform's safety net auto-closes the step, but `outputs` stays NULL. The downstream step's `## Upstream context` preamble then shows "(no summary)" instead of the real hand-off — the chain works visually but with zero context flowing between steps. Outputs are load-bearing.
7. **Call `aramb_mcp.workflows_create` exactly once.** Success or failure — never retry.
8. **Close out cleanly.** Task dispatch: always close with `aramb_mcp.tasks_update` (`status=done` on success, `status=failed` on any error) — never leave the task `in_progress`. Chat dispatch: confirm in your reply text (success or failure). There is no task to close in chat dispatch.
9. **Speak to the user in plain product language — never leak internals.** The person reading your chat is a customer, not an engineer. Do NOT mention MCP tool names (`aramb_mcp.workflows_create`, `aramb_mcp.triggers_create`), raw upstream errors (`the integrations proxy 502`, `ConfigInvalid`), CLI names, internal toolkit/slug strings, or phrases like "the tool isn't in my surface." You DO have these tools — call them. If something genuinely fails, say it in human terms ("I couldn't set up the trigger — the GitHub connection looks unavailable") and stop. Internal mechanics stay in your reasoning, never in the reply.

## Run input — the only per-run channel

A workflow run receives ALL of its per-run context in a single `<run_input>`
block that the platform renders into the **first step's** prompt at dispatch. It holds
either the user's free-form instruction (manual run) or the trigger payload JSON
(trigger run) — same slot either way. There are no declared input variables, no
typed form, no substitution. The agent reads `<run_input>` and figures out what
to do.

This shapes how you write prompts:

- **Don't parameterize inputs with placeholders.** Where you'd once have written
  `Fix the issue at {{env.ISSUE_URL}}`, now write: *"The user's instruction or the
  trigger payload arrives in `<run_input>`. Extract the issue URL, repo, and any
  details from it, then open a PR that fixes the issue."* Trust the agent to parse
  JSON or free text.
- **Step 1 is the funnel.** `<run_input>` renders on the FIRST step only.
  Downstream steps see only their parent's `outputs.summary` + `outputs.files`.
  So **step 1's prompt MUST instruct the agent to distill the relevant input into
  its `outputs.summary`** — e.g. *"Pull the issue number, title, and repo out of
  `<run_input>` and state them in your summary so later steps can act on them."*
  If step 1 doesn't propagate it, step N never sees it.
- **Fail late, gracefully.** If `<run_input>` is empty (a manual run with no text),
  the step should fail with a clear *"I don't have anything to work on — give me an
  issue URL / instruction"* rather than guess. Don't add pre-flight gates; the
  agent surfaces the failure in the run history. Write step-1 prompts that say so.

## Ground the slugs — call aramb_mcp.toolkits_* before drafting

The slugs you stamp on `toolkit` / `required_toolkits` (and any trigger you wire)
MUST be real catalog values. Don't infer them from prose. Look them up:

```bash
# Confirm toolkit slugs (uppercase, exactly as the catalog reports them)
npx mcporter call aramb_mcp.toolkits_list_toolkits

# When the workflow is meant to fire on an event, read the trigger catalog for
# that toolkit so you ground the trigger slug too (the configure-trigger skill
# does the actual wiring — you just confirm the slug exists):
npx mcporter call aramb_mcp.toolkits_list_triggers toolkit="GITHUB"
```

`aramb_mcp.toolkits_*` returns toolkit + trigger slugs already normalized to uppercase —
use them verbatim. A `toolkit` or `required_toolkits` entry that isn't a real
catalog slug fails pre-flight (no connected account) and the run never starts.

## 1. Get the spec

### Task dispatch — fetch the user tasks (ALL statuses)

Append a "Reading the application's tasks" `## Progress` bullet to the task
description, then:

```bash
npx mcporter call aramb_mcp.tasks_list \
  application_id="<application_id>"
```

**Do NOT filter by `status="done"`.** The spec source is *what the user is trying
to do in this chat*, and task success is irrelevant to that intent — a `failed` or
`in_progress` task tells you just as much about the desired workflow as a `done`
one. Read the whole user-task corpus.

The result is a JSON array of task objects, each with: `task_id`, `name`,
`description`, `acceptance_criteria`, `assigned_agent`, `status`, `depends_on`,
`required_toolkits` (Composio toolkit slugs the task used), `outputs`.

**Read `required_toolkits` on every task you fetch.** You copy these into the
corresponding workflow node in step 3 — losing them here loses them forever.

Ignore tasks where `task_kind == "system"` — those are internal bookkeeping
(including the very task you're running). Read intent from `task_kind == "user"`
tasks. The list is NOT in your prompt by design — fetching it yourself keeps the
dispatch small and gives you full task detail.

**If the task corpus doesn't cohere into a single workflow** (the tasks are
unrelated, or there are too many to make sense of), don't emit a garbage graph:
**select the relevant subset and merge/split** into a sensible workflow, and if it
genuinely won't form a coherent one, say so to the user in plain language and ask
what they'd like the workflow to do.

### Chat dispatch — classify the message, then gather

First **classify the user's message**:

- *Explicit description* (e.g. "build a workflow that fetches today's emails…"): the spec **is** the message. Don't analyze conversation history. Skip ahead to step 2.
- *History-derived* (e.g. "create a workflow based on the work done so far", "based on what we just did, build a workflow", or any phrasing that points at the conversation as the evidence): consolidate from your own session. This is the same role the task-dispatch path plays — but the evidence is your conversation history, not completed tasks.

For history-derived intent, walk back through the conversation and produce, in your reasoning:

(a) ordered list of meaningful steps you/the user took,
(b) the explicit and implicit data hand-offs between them,
(c) the Composio toolkit slugs you actually called (Gmail, Sheets, Slack, etc. — be honest, infer from real tool calls),
(d) any constants or specific values that should NOT be re-parameterized (recipe baked-in vs. genuine env-vars).

**Generalize, don't transcribe.** A workflow is a *learned recipe* that should run again. If you fetched yesterday's emails as a one-off, the node should be "fetch the most recent day's emails", not "fetch emails dated 2026-05-04". Same for sheet ranges, time windows, recipient lists — bake the *shape*, not the *specifics* of this one run.

If under-specified (either path), ask **1–2** specific clarifying questions via `aramb_mcp.chat_ask_question` BEFORE designing; pick sensible defaults for the rest and tell the user what you picked. Common reasons to clarify: identity (which account / inbox / sheet / channel), notification target, cadence vs trigger (if they want a schedule, capture the cron phrase verbatim — you'll wire it in via `aramb_mcp.workflows_set_schedule` after save).

```bash
npx mcporter call aramb_mcp.chat_ask_question \
  project_id="<PROJECT_ID>" \
  application_id="<APPLICATION_ID>" \
  question="Which Gmail account should the workflow read from — the one connected to this app, or a different one?"
```

## 1.5 Pre-build checklist — confirm-then-build (one concise round)

Before you construct nodes, confirm the few things that **materially change the
build**. The failure this prevents: building the whole workflow on silent
assumptions, then leaking the gaps as broken runs and contradictory status (e.g.
guessing a Proceed threshold the user never set, never noticing Sheets/GitHub
weren't connected, never warning that a 300-item job is long and costly).

Do it as **confirm-then-build, not interrogation**: **one** concise round of **2–4
questions** total, covering only the items below that actually apply and aren't
already specified. If everything is clear, skip straight to building — don't
manufacture questions. Verify the things you CAN verify yourself (toolkit
connections) rather than asking. Ask via `aramb_mcp.chat_ask_question` (chat dispatch)
or fold into your progress narration / a single batched question (task dispatch).
Pick sensible defaults where you can and state what you picked.

- **Scoring / decision params not clearly specified.** If the workflow makes a
  judgement (a Proceed/Reject threshold, rubric weights, a pass mark, a ranking
  cutoff), confirm the value — don't guess one. A wrong threshold silently mis-sorts
  every item.
- **Toolkit connectivity — verify, don't assume.** For **every** external system the
  workflow will touch (Sheets, GitHub, Gmail, Slack, …), check the connection
  yourself with `aramb_mcp.toolkits_check_connection toolkit="<SLUG>"`. If any is not
  connected, tell the user plainly which one(s) to connect **now** — before the
  build — rather than discovering it mid-run. (The authoritative check is the
  publish/run eval gate, which can still reject on scopes/expiry; this up-front
  check just catches the common "not connected at all" case early.)
- **Scale / cost heads-up.** If the input set is large (hundreds of items, a big
  repo list, a long candidate sheet), state the rough scale and expected time/cost
  up front, and **offer a small pilot first** (e.g. "run the first 10 to validate
  the rubric, then the full set?"). Don't quietly kick off a multi-hour job.
- **Source accessibility.** Confirm the links / repos / sheets the workflow reads
  are reachable the way the run will reach them — **public vs needs auth**. A
  private repo or a permissioned sheet that looked fine in your browser will fail
  in the run. If something needs auth, say what's required (toolkit connection,
  repo link, browser login) before building.

Keep it to the items that apply. The goal is to surface the handful of unknowns
that would otherwise become failed runs — then build with confidence, not to
interrogate the user.

## Progress reports — do this throughout

**Task dispatch.** The user sees your task card in the chat sidebar. If you don't
update the task description they stare at a spinner. Append a short `## Progress`
bullet before each major step — before fetching, before designing, before saving.
Three updates is usually right; don't spam. Preserve the original description text
(append, don't replace).

```bash
npx mcporter call aramb_mcp.tasks_update \
  task_id="<your task_id>" \
  description="<full current description, including any Progress so far>
## Progress
- Read 5 user tasks
- Analyzing dependencies and agent assignments"
```

**Chat dispatch.** The user sees chat, not a task card. Write short progress
narration in your reply text at three checkpoints (the platform saves your final
assistant text as the chat row — no MCP call needed):
1. Restate the workflow you're about to build **and which evidence source you're using** ("Building from your description: 3-step Gmail → Sheet → email digest" / "Consolidating from the work we did earlier in this chat: 3 steps — fetch, write, notify").
2. When you start designing nodes ("Designing 3 nodes — Gmail fetch → Sheet append → notify").
3. Just before save ("Saving workflow…").

## 2. Analyze the spec

Study the spec (the user tasks, the explicit description, or the conversation
work). Understand: what each step accomplishes, how steps depend on each other,
**which agent each node needs (by its role — decided identically in solo and team:
reuse a fitting existing agent, else author a bespoke sub-agent spec INLINE in the
workflow's `agent_specs`; see "Per-node persona — decided by the work")**, and what
inputs/outputs flow between steps.

For chat dispatch this is also the **merge / generalize / split** pass: combine
adjacent same-agent calls into one node where it makes the workflow cleaner;
split steps that mixed responsibilities; rename concrete one-off artefacts ("the
email about Q3 review") into the recurring shape they represent.

## 3. Design the workflow

Update progress: "Designing workflow graph — N nodes, M levels".

- **Merge or split** steps where it makes the workflow cleaner. Not every source task becomes a node.
- **Concrete prompts** — each node's `prompt` carries the real business context baked in. This is a learned recipe, not a blank template. Distill what actually worked but keep the concrete subject matter.
- **Preserve dependencies** — give each node a sequential `unique_id` (integers starting at 1), then express dependencies as a separate top-level `edges` array: `{ "source": <upstream unique_id>, "target": <downstream unique_id> }`. Do NOT put `dependencies`, `depends_on`, or `dependsOn` on node objects — the platform rejects that shape.
- **`assigned_agent` per node** — one agent per role, decided IDENTICALLY in solo and team (see "Per-node persona — decided by the work"). For each node: reuse an existing agent that fits the role (a roster persona — `developer` / `*-tester` / `checker` / `*-deployer`), otherwise **author a bespoke sub-agent spec INLINE in the workflow's `agent_specs`** named for its role (`issue-triager`, `fix-implementer`, `qa-tester`, `pr-author`, …) to the template-grade bar, and set the node's `assigned_agent` to that spec's `name`. In task dispatch, you may default to the source task's persona. Authoring a spec per distinct role is the DEFAULT. A single-role workflow may keep `agent_specs` empty and point every node at **the agent you are building**, using its routing name from `aramb_mcp.agents_get` → `benji_agent_id` (never `"main"` — no such agent — and never `"master"`/`"solo"`, which are infra agents) — only for a trivial single-node / pure-glue workflow. Do NOT branch on solo vs team.
- **Do NOT pick a different model per node.** Model/effort/thinking come from the single workflow-wide `default_node_settings` (or, for an inline sub-agent, its `defaultModel`); per-node `settings` stays `{}` (inherit). Never stamp `model` on individual nodes — no per-step Haiku/Opus/Sonnet juggling.
- **Carry `required_toolkits` per node — MANDATORY, never omit.** List the Composio toolkit slugs that node will call (`["GMAIL"]`, `["GOOGLESHEETS","GOOGLEDRIVE"]`, etc.). Task dispatch: source from each task's `required_toolkits` field (primary) and the tool calls you observe in outputs (cross-check). Chat dispatch: infer from the action — Gmail action → `["GMAIL"]`, Sheets append → `["GOOGLESHEETS"]`, Slack DM → `["SLACK"]`. Empty array (`[]`) when a node only writes files / orchestrates — `[]` is REQUIRED, not optional. Slugs are uppercase and **grounded via `aramb_mcp.toolkits_list_toolkits`** (see "Ground the slugs"), not guessed from prose. The platform snapshots this list onto every run step at trigger time and the Evaluate step uses it to surface missing-connection warnings before publish.
- **Carry a singular `toolkit` per node that has any toolkits — MANDATORY when `required_toolkits` is non-empty.** It is the node's *primary* toolkit (the one a trigger would bind to). Invariant: `toolkit ∈ required_toolkits`. Single-toolkit node → `toolkit` equals the one slug. Multi-toolkit node → pick the slug the node's job is "about" (the action it exists to perform, not an incidental read). Omit `toolkit` (or `null`) only when `required_toolkits` is `[]`. The platform rejects a `toolkit` that isn't in `required_toolkits`.
- **Per-node toolkit CHOICE — Composio connection vs `aramb-browser`.** For each node that touches an external surface, decide *how* it acts: does the **Composio toolkit** cover the action, or do you need **`aramb-browser`** (drive a logged-in website directly)? Composio is the default when it has the action; reach for `aramb-browser` when Composio's coverage of that service is limited (e.g. Composio LinkedIn is read-thin → a "post to LinkedIn" or "comment on a profile" node needs `aramb-browser`, and trips the browser-login pre-check below). **Shortcut: if the work was already performed** (you can see it in the session or the task outputs), reuse whatever actually served the purpose — the user already chose the path that worked; don't second-guess it.
- **Write prompts against `<run_input>`, never placeholders.** Each node's `prompt` describes what to do with the context it receives — for step 1 that context is the `<run_input>` block (see "Run input — the only per-run channel"); for later steps it's the parent's `outputs.summary`. No `{{env.KEY}}` / `{{input.KEY}}` anywhere. Step 1's prompt must explicitly tell the agent to distill the relevant input into its `outputs.summary` for downstream steps.
- **Set `default_node_settings` on the workflow.** Always emit a sensible defaults block — see "Default node settings — workflow-level". Don't leave it empty: the FE renders the settings tray off these values.
- **Per-node `settings` typically stays empty (`{}`)** — defaults inherit from the workflow. Exception: if a node does something destructive or externally visible (posts to Linear, sends email, writes to a customer DB, deletes files), set that one node's `settings.approval_mode = "manual"`. Use sparingly — over-gating turns every run into a clickfest.
- **Per-node attachments** only when the user explicitly mentioned files in chat. Never invent attachments — empty `input_attachments` is the default.
- **End every node `prompt` with the closing-instruction template** (next section). The agent has no other path to populate `outputs`.

## Closing instruction per node — MANDATORY

Every node's `prompt` MUST end with this exact block, with `<summary>` and `<files>` substituted to match what the node will actually produce. Treat it the way the task-description template treats the closing `aramb_mcp.tasks_update` call — non-negotiable, baked into every prompt at authoring time.

Append this to every node's `prompt`:

```
When done — record your output for the next step:
  npx mcporter call aramb_mcp.workflows_update_step \
    project_id="<your Project ID from User Message>" \
    step_id="<your Workflow Run Step ID from User Message>" \
    status="done" \
    outputs='{"summary":"<one-paragraph hand-off, under 500 chars>","files":["relative/path/to/output.json"]}'

If you can't complete the step:
  npx mcporter call aramb_mcp.workflows_update_step \
    project_id="<your Project ID from User Message>" \
    step_id="<your Workflow Run Step ID from User Message>" \
    status="failed" \
    error="<concise reason + any partial progress>"
```

Why both `summary` and `files`:
- `summary` is a paragraph the next agent reads as preamble — the hand-off vocabulary that makes the chain coherent. Keep it under 500 chars; focus on what's useful downstream, not how the work was done.
- `files` is a list of paths (relative to the workspace working directory) the next agent reads to dig deeper. Empty array `[]` is correct when the node only sends a message / posts to an external service and produces no files.

Notes:
- The agent reads its `project_id` and `step_id` from the User Message under "## Current Context" (`Project ID:` and `Workflow Run Step ID:` lines) at dispatch time. The platform rejects cross-step writes (`context_drift`), so the agent MUST copy these UUIDs verbatim into the close call.
- Do NOT instruct the agent to call `aramb_mcp.tasks_update` from a workflow-step prompt — that targets the tasks domain (different DB rows) and the run will stall on the safety net. Only `aramb_mcp.workflows_update_step` closes a workflow run step.

## Git operations — route through aramb_mcp.toolkits_* + native git/gh

**When to emit this block:** any node whose described work involves anything on
github — clone, fetch, checkout, push, branch, commit, PRs, issues, releases,
comments. Everything github goes through the same surface (no API-vs-protocol
split anymore): `aramb_mcp.toolkits_execute` `{tool:"GITHUB_GET_GIT_CREDENTIAL"}` →
`GH_TOKEN` → native `git` / `gh` CLI.

**Why it matters:** github is NOT a normal Composio tool on this platform — only
the synthetic `GITHUB_GET_GIT_CREDENTIAL` action is served (other `GITHUB_*`
tools are not). The credential broker is the only way to get a usable github
token from the agent's container.

**Append this block verbatim** to the END of any node prompt that involves
github work (after the closing-instruction template):

```
### Tool routing for github operations on this step
1. Confirm the user has connected github:
   `aramb_mcp.toolkits_check_connection toolkit="GITHUB"`
   - If `connected: false` — call
     `aramb_mcp.toolkits_connect toolkit="github"` and share the
     returned `redirect_url` with the user via your reply or
     `aramb_mcp.chat_alert_user`. Close the step with `status="blocked"` until
     they finish OAuth; do not retry without confirmation.
2. Mint a token:
   `aramb_mcp.toolkits_execute` `{tool:"GITHUB_GET_GIT_CREDENTIAL"}` (returns under
   `result`: `{ token, username, account_ref, ... }`).
   - If the org has multiple github accounts in scope and the response is
     ambiguous, call `aramb_mcp.toolkits_list_connections toolkit="GITHUB"`, pick the
     right `account_ref`, then re-call with
     `{tool:"GITHUB_GET_GIT_CREDENTIAL","arguments":{"account_ref":"ca_..."}}`.
3. Export and use native CLI for everything:
   `export GH_TOKEN="<token>"`
   `git clone https://x-access-token:$GH_TOKEN@github.com/<owner>/<repo>.git`
   `git push`, `gh pr create`, `gh issue list`, `gh release create`, etc.
4. On `401` from `git` / `gh` (~8h token lifetime), re-call
   `aramb_mcp.toolkits_execute {tool:"GITHUB_GET_GIT_CREDENTIAL"}` for a fresh token.
   Cheap, no rate concerns.
5. NEVER try other `GITHUB_*` tools via `execute` — only
   `GITHUB_GET_GIT_CREDENTIAL` is served; the rest are not. Also do NOT use
   `aramb_mcp.chat_list_linked_repos`,
   `aramb_mcp.chat_clone_repo`, or `aramb_mcp.chat_git_token` — those don't exist
   on this surface anymore.
```

Emit this block on every node that touches github — there is no "API-only"
exemption anymore since both API and protocol ops go through the same
native-CLI path.

## Durable-output nodes — foreground + append-only

**When to emit this block:** any node that builds up a durable output file or
sheet over a long task — scoring/ranking many submissions into a `results.csv`,
walking a candidate list into a sheet, batch-evaluating a repo set into a report.
These are the long nodes that may hand off mid-run (a continuation picks up where
the previous attempt left off), so how the agent treats its output file decides
whether the work converges or thrashes.

**Why it matters:** a long durable-output node can be re-entered — on a
continuation handoff or a stranded-step recovery a fresh session resumes against
the same working directory and the same partially-written file. Two failure modes
seen in the wild: (1) the agent **backgrounds** the scoring loop (`run2.sh &`) and
the session closes while the child keeps running, so the next session spawns
another loop and N orphaned processes race on the same file; (2) the agent
**truncates or rewrites** the results file each run (recreate, in-place dedup,
"clean up and re-emit"), so progress oscillates and never converges instead of
growing monotonically. Foreground + append-only is what makes a resumed run safe.

**Append this block verbatim** to the END of any durable-output node prompt
(after the closing-instruction template), substituting `<output file>` with the
node's actual results path:

```
### Building your durable output (foreground + append-only)
This step writes `<output file>` incrementally over a long run, and may be
resumed by a fresh session against the same working directory. Treat the file
as a growing ledger, never a scratchpad:
1. FOREGROUND ONLY. Run the core loop in the foreground — do the scoring/work
   inline, turn by turn. NEVER background it (`script &`, `nohup`, `&` of any
   kind, detached `run.sh`). A backgrounded loop keeps running after the session
   closes and a later session will spawn a second loop that races it on the file.
2. APPEND-ONLY. Treat `<output file>` as strictly append-only. Append each new
   row as you produce it. NEVER truncate, recreate, overwrite, reorder, or do an
   in-place dedup/rewrit

…(truncated)
