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-workflowskill. If polishing a template-import draft, useimport-workflow. This skill only handles first-time creation.
Non-negotiables — read these before you call aramb_mcp.workflows_create
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>"onaramb_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(theagent_idgets stamped and the workflow is re-filed under the agent's template project). Attach and create-with-agent_idconverge 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 viaagent_idat create time, oraramb_mcp.agents_attach_workflowonce 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.
- Agent-first (default): the agent already exists (or you create it first), so
pass
Every
createis a NEW, separate workflow. NEVER replace an existing one. A project can hold many workflows side by side —aramb_mcp.workflows_createalways 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 toaramb_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 theupdate-workflowskill, never this one. Silently replacing a user's workflow is a serious failure.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_createfiles the workflow under the agent (viaagent_id, see #0) and leaves it a draft — it is NOT auto-published. The builder TESTS the draft via Preview (aramb_mcp.workflows_runworks on the draft — see thearamb-workflowsrun 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 witharamb_mcp.toolkits_check_connectionand 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.Never claim a workflow ran unless the run tool said so. When the user asks to run a workflow, call
aramb_mcp.workflows_runand 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/listreport only the workflow's definition/lifecycle state, not per-step run progress. See thearamb-workflowsskill'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 viaaramb_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 asource_task_id. Close out viaaramb_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 (
developerfor code / clone / implement,backend-tester/frontend-tester/integration-tester/checkerfor verify / review,aramb-deployer/local-deployerfor deploys). Set the node'sassigned_agentto that agent's name. - Otherwise author the sub-agent's FULL spec INLINE in the workflow's
agent_specsarray and reference it bynamefrom the node'sassigned_agent. Name it for its role (issue-triager,fix-implementer,qa-tester,pr-author) and write it to the template-grade bar: a realidentity/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. TheTemplateAgentshape isname/displayName/identity/soul/agentsDoc/skills/defaultModel/defaultBackend/defaultThinking(see thearamb-workflowsskill'sagent_specsfield + "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
- Every node in
aramb_mcp.workflows_createMUST carryrequired_toolkits. Copy the array from each source task'srequired_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_toolkitsmeans 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.)
- Failure mode: Omitting
- 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:toolkitMUST be a member of that node'srequired_toolkits. A Gmail-fetch node istoolkit:"GMAIL", required_toolkits:["GMAIL"]; a node that reads Drive then writes Sheets istoolkit:"GOOGLESHEETS", required_toolkits:["GOOGLEDRIVE","GOOGLESHEETS"](pick the one the trigger would bind to — usually the action the workflow is "about"). Omittoolkit(or passnull) only whenrequired_toolkitsis[]. The platform's MCP schema rejects atoolkitthat isn't inrequired_toolkits. - Ground every toolkit + trigger slug in the real catalog — never hallucinate. Before drafting, call
aramb_mcp.toolkits_list_toolkitsto 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_createrejects those. For Slack/Discord/Teams messaging deliverables, deliver via the chat toolkit'schat.send_dm(no toolkit). See thearamb-workflowsskill. - 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). - Do NOT declare
env_variables. Omit the field from thearamb_mcp.workflows_createcall 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-emptyenv_variablesmap. Secrets/credentials are connected through the Composio account, not declared on the workflow. - Every node's
promptMUST end with the workflow-step closing instruction so the executing agent callsaramb_mcp.workflows_update_step(with the explicitstep_idrendered 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
outputsstays NULL. The downstream step's## Upstream contextpreamble 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.
- Failure mode: Without the closing instruction, the agent finishes its LLM session and the platform's safety net auto-closes the step, but
- Call
aramb_mcp.workflows_createexactly once. Success or failure — never retry. - Close out cleanly. Task dispatch: always close with
aramb_mcp.tasks_update(status=doneon success,status=failedon any error) — never leave the taskin_progress. Chat dispatch: confirm in your reply text (success or failure). There is no task to close in chat dispatch. - 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'soutputs.summary+outputs.files. So step 1's prompt MUST instruct the agent to distill the relevant input into itsoutputs.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:
# 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:
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).
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).
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):
- 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").
- When you start designing nodes ("Designing 3 nodes — Gmail fetch → Sheet append → notify").
- 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
promptcarries 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-leveledgesarray:{ "source": <upstream unique_id>, "target": <downstream unique_id> }. Do NOT putdependencies,depends_on, ordependsOnon node objects — the platform rejects that shape. assigned_agentper 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'sagent_specsnamed for its role (issue-triager,fix-implementer,qa-tester,pr-author, …) to the template-grade bar, and set the node'sassigned_agentto that spec'sname. 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 keepagent_specsempty and point every node at the agent you are building, using its routing name fromaramb_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, itsdefaultModel); per-nodesettingsstays{}(inherit). Never stampmodelon individual nodes — no per-step Haiku/Opus/Sonnet juggling. - Carry
required_toolkitsper node — MANDATORY, never omit. List the Composio toolkit slugs that node will call (["GMAIL"],["GOOGLESHEETS","GOOGLEDRIVE"], etc.). Task dispatch: source from each task'srequired_toolkitsfield (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 viaaramb_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
toolkitper node that has any toolkits — MANDATORY whenrequired_toolkitsis non-empty. It is the node's primary toolkit (the one a trigger would bind to). Invariant:toolkit ∈ required_toolkits. Single-toolkit node →toolkitequals 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). Omittoolkit(ornull) only whenrequired_toolkitsis[]. The platform rejects atoolkitthat isn't inrequired_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 needaramb-browser(drive a logged-in website directly)? Composio is the default when it has the action; reach foraramb-browserwhen 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 needsaramb-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'spromptdescribes 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'soutputs.summary. No{{env.KEY}}/{{input.KEY}}anywhere. Step 1's prompt must explicitly tell the agent to distill the relevant input into itsoutputs.summaryfor downstream steps. - Set
default_node_settingson 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
settingstypically 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'ssettings.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_attachmentsis the default. - End every node
promptwith the closing-instruction template (next section). The agent has no other path to populateoutputs.
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:
summaryis 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.filesis 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_idandstep_idfrom the User Message under "## Current Context" (Project ID:andWorkflow 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_updatefrom a workflow-step prompt — that targets the tasks domain (different DB rows) and the run will stall on the safety net. Onlyaramb_mcp.workflows_update_stepcloses 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)