Skip to main content

build-workflow

Create or modify Sim workflows through the sim CLI. Use when translating a workflow request into blocks, inputs, connections, variables, and atomic graph edits; not for only running or deploying an existing workflow.

Source facts

Repository
simstudioai/skills
Last source activity
September 29, 2026 at 20:18
Detected SKILL.md language
English
Stars
1
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
build-workflow
description
Create or modify Sim workflows through the sim CLI. Use when translating a workflow request into blocks, inputs, connections, variables, and atomic graph edits; not for only running or deploying an existing workflow.
# Build a Sim Workflow Build the smallest valid graph that satisfies the request, using the Sim CLI as the source of truth for available resources and accepted shapes. A request that is one action against one connected service needs no graph at all — call the tool directly with `sim tools execute` (see the `run-tool` skill). Build a workflow when the task needs more than one call, branching, or a schedule. For an existing workflow JSON import or environment promotion, use the `sync-workspaces` skill. It preserves registered reference identities through preview and destination binding before graph IDs are regenerated; do not recreate that flow with a sequence of graph edits. ## Establish context - Use the profile the user named. If none was named, inspect configured profiles and current context; do not silently switch accounts, workspaces, or API origins. - Find the intended workflow with `sim --output json workflows list`. Create one only when the user asked for a new workflow. - Read an existing draft with `sim --output json workflows state get <workflowId>` before editing it. Preserve blocks, edges, variables, and deployment state outside the requested change. - State reads preserve configuration for editing and can contain secrets. Keep that state private; `workflows export` is a sanitized portable representation that clears credentials and, by default, workspace resource bindings. Use state reads for in-place edits. - If the workflow is locked or read-only, stop instead of attempting an alternate mutation path. ## Design before encoding - Translate the request into an entry point, required transformations or decisions, external actions, and an observable terminal result. Decide what successful output should look like before choosing blocks. - Treat the graph as a typed program: block inputs are arguments, block outputs are return values, and references are data flow. Inspect an upstream output schema before referencing a field, and make sure its shape matches what the downstream input accepts. - Build the smallest graph that expresses the behavior: - Connect blocks directly when there is no real decision to make. - Use a Function block for deterministic parsing, validation, calculation, and reshaping. Do not spend an Agent block on work that should always produce the same result for the same input. - Use an Agent block for language understanding, generation, or dynamic tool use. - Use a Condition block for explicit predicates and a Router block for semantic classification. - Use a Loop only for iteration. Do not create graph cycles to model repetition. - Use Parallel only when branches are independent; keep data-dependent work sequential. - Every added block must be reachable from the intended entry point and contribute to a terminal path. Trace downstream references before editing or deleting an existing producer. ## Configure Agent blocks deliberately - Use an Agent for generation, summarization, extraction from natural language, diagnosis, or other work requiring judgment. Use a Function for mechanical parsing, formatting, calculation, and reshaping; do not use canned strings or random selection to imitate a language task. When fuzzy classification exists only to choose a branch, prefer a Router over Agent followed by Condition. - Prefer an integration inside `inputs.tools` when the Agent should decide whether to call it or choose its arguments. Use a standalone integration block when the action must execute deterministically at that point in the graph. Event sources remain trigger blocks. - Inspect the Agent and integration block details before configuring them. Choose a model from the Agent block's current options. For an integration tool, `type` is the catalog block id and `operation` is its catalog operation id, never the underlying executable tool id. - Store the complete tool list at `params.inputs.tools`. Supported authoring shapes are: ```json [ { "type": "<catalog-block-id>", "operation": "<catalog-operation-id>", "usageControl": "auto", "params": { "<author-fixed-input>": "<value>" } }, { "type": "custom-tool", "customToolId": "<custom-tool-id>", "usageControl": "auto" }, { "type": "mcp", "params": { "serverId": "<mcp-server-id>", "toolName": "<mcp-tool-name>" }, "usageControl": "auto" } ] ``` - Put only workflow-author-fixed values in a tool's `params`; omit arguments the model should choose. Omit `usageControl` or use `auto` when the model may decide, `force` when every Agent run must call it, and `none` to disable it. - Resolve custom-tool ids with `custom-tools list` and `custom-tools get`. Resolve MCP server and tool names with `mcp-servers list` and `mcp-servers tools list <serverId>`. New custom tools should use `customToolId`; preserve an existing legacy inline declaration but do not create another. - Skills are separate from tools: store `[{ "skillId": "<skill-id>" }]` in `params.inputs.skills`, never in `inputs.tools`. Both `tools` and `skills` are complete replacement arrays, so read state and retain entries the user did not ask to remove. - When downstream blocks need stable typed fields, configure the Agent's catalog-declared structured response format. Structured fields become top-level Agent outputs; inspect the effective output schema before referencing them instead of assuming a `content` path. ## Wire Condition and Router branches semantically - A Condition's `params.inputs.conditions` is an ordered array of `{ "title", "value" }` entries. Titles are structural tokens, not display labels: use exactly `If`, repeat `Else If` as needed, and use `Else` with an empty value for the fallback. Author their connection handles as `if`, `else-if-0`, `else-if-1`, and so on, then `else`. - A Router's `params.inputs.routes` is an ordered array of `{ "title", "value" }` entries. Route titles are free text; connection handles are positional: the first route is `route-0`, the second is `route-1`, and so on. - Always use these semantic handles in workflow operations. Persisted edges may contain internal condition or route ids; those are storage details, not handles to copy into a new operation. - Reordering Condition branches or Router routes changes the positional handle mapping. Update the block inputs and every affected outgoing connection in the same atomic batch, then read state and verify each handle still reaches its intended target. ## Name blocks and write references exactly - A block-output reference uses the block's `name`, not its catalog type, operation id, UUID, or request-local `block_id`. Its prefix is the name lowercased with whitespace and dots removed. Nothing is inserted: `AWS Alert` becomes `awsalert`, so use `<awsalert.event.text>`, never `<aws_alert.event.text>`. Existing dashes and underscores remain (`AWS_Alert` becomes `aws_alert`). - Prefer plain alphanumeric camelCase names for new blocks, such as `awsAlert`, `parseInput`, and `step1`. This keeps the stored name readable and the reference prefix predictable. Do not use the reserved normalized names `loop`, `parallel`, or `variable`, and do not create names that collide after normalization. Punctuation in a name is a live hazard rather than a style preference: a block renamed to `getP&L` left `<getP&L.contents>` unresolved and passed through as literal text, with the lint reporting nothing. - The field path after the prefix comes from the upstream block's effective output schema and is case-sensitive. Function block return values are under `result`, so use `<parseinput.result>` or `<parseinput.result.field>` only when the catalog declares that shape. - A tag resolves anywhere in a Function block's `code`, comments and docstrings included, and one that does not resolve fails the block. Describe a field in prose rather than writing tag-shaped example text beside the code that uses it. - Before applying a batch, enumerate every `<block.field>` reference in its inputs. Verify the normalized prefix against the exact upstream block name in workflow state, verify the field path against the catalog output schema, and verify the source is reachable upstream. A clean workflow operations lint does not prove block-output references resolve; its `unresolvedReferences` report covers resource-like references such as credentials, tools, and skills. ## Discover before composing - Search the catalog with `sim --output json blocks list`; inspect a candidate with `sim --output json blocks get <blockId>`. - Use the returned block id, operation ids, input ids, modes, conditions, credential fields, and outputs exactly. Never invent them from a display name or underlying tool id. - Preserve an existing block's saved type/version when editing; an unversioned catalog lookup can resolve a newer definition. - Inspect `tools list` or `tools get` only when the block response points to a tool and its parameter or output contract is needed. - Resolve credentials and resource identifiers before writing them into a graph. Do not embed raw secrets in an operations file. - In Function code, use the supported `{{KEY}}` secret references (JavaScript: `const key = {{KEY}};`) within the configured secret scope. Do not assume workspace secrets appear in `process.env`, or widen selected-secret access to work around a missing reference. - Discover trigger behavior from the catalog. A service trigger may be an integration block with trigger mode enabled, while a built-in trigger may have its own block type; never substitute a trigger configuration id for a block id. - Select models, operations, and modes from the returned schema. Do not guess an id from a label or reuse an id from another integration. ## Bind a resource through its selector field A resource input is usually exposed twice under one `canonicalParamId`: a basic-mode selector such as `tableSelector`, `writeFolderPath`, or `workflowId`, and an advanced-mode manual twin such as `manualTableId`, `manualWriteFolderPath`, or `manualWorkflowId`. Only the active member of the pair is read at execution; a value sitting on the other member is inert. - **Try the selector with the dynamic value first.** Advanced does not mean "the member that takes a reference". On the File block's write operation the `writeFolderPath` selector resolves a folder path, including its percent-encoded form, while `manualWriteFolderPath` fails for every value, so a block-output reference belongs in the selector there. Read the pair out of `blocks get <blockId>` instead of inferring which member is dynamic from its mode. - **Naming a member selects the mode.** An edit sets the pair's mode from the input keys it names rather than from their values, and an edit naming both members resolves to the advanced one. So `{"manualTableId": null}` on a selector-bound block does not clear inert residue: it flips the block to advanced with an empty id, and the next run fails on a missing required field. - **Move a block off its manual member in two operations, never combined.** First `{"manualTableId": ""}` to empty the manual value, then `{"tableSelector": "<id>"}` to bind the selector and return the pair to basic mode. That order leaves the manual member empty, so a later flip back to advanced fails loudly instead of silently reading whatever id was left behind. - **Leave the manual member empty in a graph that will be copied into another workspace.** A copy remaps a bound selector but carries a manual id verbatim, silently repointing the copy at the source workspace's resource. - **An edit merges inputs rather than replacing them.** Fields an edit does not name keep their previous values, so a block configured over several edits can still carry a stale value on the member that is no longer active. Read state afterwards and confirm the active member holds the value and its twin is empty. A value stranded on the inactive member is reported among the apply lint's field issues while the apply itself still reports success. ## Author one semantic batch Prefer `workflows operations apply` over full-state replacement. One batch can create several blocks, edit existing blocks, delete blocks, and wire their edges atomically. Every operation has one of these envelopes: ```json [ { "operation_type": "add", "block_id": "local-label", "params": { "type": "<catalog-block-id>", "name": "referenceSafeName", "inputs": {} } }, { "operation_type": "edit", "block_id": "<existing-block-id>", "params": { "inputs": {} } }, { "operation_type": "delete", "block_id": "<existing-block-id>" }, { "operation_type": "insert_into_subflow", "block_id": "local-child-label", "params": { "subflowId": "<loop-or-parallel-id>", "type": "<catalog-block-id>", "name": "referenceSafeName", "inputs": {} } }, { "operation_type": "extract_from_subflow", "block_id": "<existing-child-id>", "params": { "subflowId": "<loop-or-parallel-id>" } } ] ``` Block configuration belongs in `params.inputs`, keyed by the catalog's input ids. Put block-level `retry`, `triggerMode`, and `advancedMode` beside `inputs`, not inside it. Connections belong on the source block under `params.connections`. Keys are source handles; values are a target block id, `{ "block": "<target-id>", "handle": "<target-handle>" }`, or an array of either. `success` aliases the ordinary `source` handle. Re-sending `connections` replaces all of that block's outgoing edges. To remove only selected edges, edit with `removeEdges` entries containing `targetBlockId` and, when needed, `sourceHandle`. A non-UUID `block_id` on a new block is a request-local label. Same-batch references are remapped automatically; later requests must use the UUID returned in `mintedBlockIds`. Never rediscover a new block by matching its name. The request-local label does not become the block's variable-reference prefix; `params.name` does. ## Apply atomically, then verify Use `--dry-run --atomic` to validate a proposed batch when needed. Validation executes no blocks and does not suppress writes during a later run. Dry-run `previewBlockIds` are provisional; later requests must use the committed response's `mintedBlockIds`. Pass the batch as inline JSON on the first attempt and apply it once with atomic behavior. Do not create a staging file preemptively: ```bash sim --output json workflows operations apply <workflowId> \ --operations '<operations-json-array>' --atomic --yes ``` Only switch to `--operations @operations.json` if the inline invocation itself becomes impractical because of shell parsing or command-length limits; keep the batch unchanged when changing how it is passed to the CLI. If the atomic apply is refused, fix the batch rather than retrying a partial or guessed alternative. Read the state again and verify the requested blocks, inputs, and edges. Treat skipped operations, dropped inputs, unresolved references, and required-field lint issues as failures to correct. Report minted ids and any other advisory lint that remains. Confirm that changed references resolve, every branch reaches the intended destination, nested blocks remain in the correct loop or parallel scope, and no block was orphaned by a replaced connection set. Recompute reference prefixes from the persisted block names; do not infer them from request-local labels or reformat them as snake_case. Report the workflow id and its exact `webUrl` from the create or list response, formatting the URL as a clickable link. Do not construct a workflow URL from ids or the profile's API origin; a missing `webUrl` is a response-contract failure. When the request includes a working or tested workflow and execution is safe, use the run skill to manually test the saved draft with realistic input. Judge the returned values, not only the terminal status, and fix then rerun when behavior is wrong. Manual runs require a personal API-key profile; do not deploy merely to test. Do not execute a workflow whose external side effects have not been authorized.
View on GitHub