{"id":"536ef99c-d16e-4203-8d30-59c6c7c2b368","slug":"skillflow","kind":"skill","name":"skillflow","summary":"Run your Claude skills as a dependency graph, with real handoffs between them.","owner":"meaydira","repoUrl":"https://github.com/meaydira/skillflow","license":"MIT","licenseTier":"permissive","trust":"community","stars":0,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-09-22T18:10:58.000Z","addedAt":"2026-10-21T11:40:55.857Z","logoUrl":"https://avatars.githubusercontent.com/u/20203160?u=55eead288667addde8cc5bcbd263d3e92b567da2&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["developer-tools"],"tags":["ai-agents","automation","claude","claude-code","dag","workflow-orchestration"],"runtime":"node","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-22T17:37:15.250Z","sample":false,"url":"https://conduits.dev/tools/skillflow","language":"TypeScript","transports":["stdio"],"install":{"npm":"skillflow"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# skillflow\n\nA board for your Claude agents. Assign a task, watch an agent pick it up, give\nfeedback, decide when it is done. Chain agents into workflows when the steps are\nknown in advance.\n\n![The board](docs/board.png)\n\nTwo units of work, because work arrives in two shapes:\n\n- **A task** is a thing you want done. You assign it to one of your agents or\n  skills, it works, and it comes back to In Review. You read what it did and\n  either accept it or send it back with feedback, and it tries again knowing\n  what you said.\n- **A workflow** is a chain you already know the shape of. Agent one creates the\n  accounts, agent two matches the interactions, agent three sources the\n  contacts. Each step hands typed artifacts to the next.\n\nA task can be assigned to a workflow, so a five step chain sits on the board as\none card.\n\nAnything that cannot be undone can be held for a human. The agent works out what\nit wants to change and writes it down, you read the actual proposed values, and\nonly then does the next step apply exactly what you approved.\n\nEverything lives in files under `.skillflow/`. No database, no account, nothing\nleaves your machine except the model calls.\n\n## Install\n\n### Desktop app (macOS)\n\n```bash\ngit clone https://github.com/meaydira/skillflow && cd skillflow\nnpm install && npm run dist\n```\n\nThat produces `release/mac-arm64/skillflow.app`. Drag it to Applications and\nopen it. The app bundles its own Claude Code, so nothing else needs installing,\nbut it does need to be signed in once (see below).\n\nIt keeps its tasks and runs in a workspace folder, `~/skillflow` by default.\nChange it from the skillflow menu, or point it at a folder you already have.\n\nUnsigned, so a copy you build yourself opens normally, but a copy you send\nsomeone else will be stopped by Gatekeeper until it is signed and notarised.\n\n### Command line\n\n```bash\ngit clone https://github.com/meaydira/skillflow && cd skillflow\nnpm install && npm run build && npm link\n```\n\n`npm link` puts `skillflow` on your PATH. Skip it and use `npx tsx src/cli.ts`\nin place of `skillflow` everywhere below.\n\n### Signing in\n\nEither way, Claude Code has to be authenticated once:\n\n```bash\nnpm install -g @anthropic-ai/claude-code\nclaude auth login\n```\n\nAn `ANTHROPIC_API_KEY` in the environment works instead. The login is stored in\nyour keychain and shared with the desktop app, so this is a one-time step.\n\nskillflow cannot borrow the credentials of a Claude desktop session, so run it\nfrom a terminal, from the app, or from cron, rather than from inside another\nagent.\n\n## Try it\n\n```bash\nskillflow ui\n```\n\nOpen http://127.0.0.1:4600, click **New task**, describe something small,\nassign it to one of your agents, and press Run.\n\nOr run a workflow from the terminal:\n\n```bash\nskillflow run examples/hello-chain.yaml -i topic=\"the Dutch tulip mania\"\n```\n\nThree nodes, nothing external touched. Then look at\n`.skillflow/runs/<id>/nodes/write/` to see what the last node was actually\nhanded. That directory is the whole idea.\n\nFor the pattern that matters, run the gated one:\n\n```bash\nskillflow run examples/review-gate.yaml\n```\n\nIt proposes changes to six records, then stops and waits for you.\n\n## The board\n\n```bash\nskillflow ui        # http://127.0.0.1:4600\n```\n\nDrag cards between columns to change status. Click one to open it.\n\n![A task, its agent, and the conversation on it](docs/task.png)\n\nInside a task you set the assignee, priority and resource locks, edit the\ndescription, and read everything that has happened on it. **Run with agent**\nstarts the work. The agent posts as it goes and moves the card to In Review when\nit is finished.\n\nFrom there you either **Accept and finish**, or write what is wrong and **Send\nback with feedback**. Sending back re-runs the agent with your feedback and its\nown previous answer in context, so the second attempt is a revision rather than a\nrepeat. Tasks are linkable at `#TASK-12`.\n\n### Approving what cannot be undone\n\nWhen a run hits an approval gate, the gate appears on the task with the proposed\nchanges rendered in full:\n\n![Reviewing a changeset before it is applied](docs/ui-approval.png)\n\nAbove: the agent proposed nine corrections, flagged one as an inference that\ncould misroute mail, and left two clean records alone. Nothing has been written.\nApproving lets the next node apply exactly this and nothing else.\n\nThe board binds to loopback and has no login, because it is a window onto files\nyou already own on a machine you are already sitting at.\n\n## Using skills and agents you already have\n\n```bash\nskillflow list            # everything installed on this machine\nskillflow list skills --search deck\nskillflow list agents\n```\n\nPut the name it prints on a node:\n\n```yaml\n- id: research\n  skill: docsend-downloader     # a skill from ~/.claude/skills or a plugin\n- id: review\n  agent: code-reviewer          # a subagent from ~/.claude/agents or a plugin\n```\n\nA node can use either, or neither. With neither, the prompt is the whole\ninstruction, which is often enough.\n\nTo start a new workflow:\n\n```bash\nskillflow new workflows/my-thing.yaml -n fetch check publish\n```\n\nThat writes a commented skeleton with those three nodes wired in order, ready to\nedit.\n\n## The handoff\n\nA node runs inside a working directory that skillflow builds for it:\n\n```\nnodes/<id>/\n  CLAUDE.md          the brief: its task, and exactly what it owes downstream\n  HANDOFF.md         what every upstream node produced, in prose\n  inputs/\n    <node>/<artifact>/\n      summary.md     what this artifact is\n      data.json      structured payload, when there is one\n      files/         documents, when there are any\n  outputs/\n    manifest.json    written by the agent to declare what it produced\n  workspace/         scratch, discarded\n```\n\nAn agent declares its results by writing `outputs/manifest.json`:\n\n```json\n{\n  \"artifacts\": [\n    {\n      \"name\": \"accounts\",\n      \"kind\": \"records\",\n      \"summary\": \"Created 12 Accounts and 12 Deals. Three submissions were skipped as duplicates of existing accounts, listed in data.skipped.\",\n      \"data\": { \"created\": [\"001...\"], \"skipped\": [] }\n    }\n  ]\n}\n```\n\nThe contract is a file, not a protocol. Any skill you already have works as a\nnode as long as its prompt tells it to write the manifest, and skillflow puts\nthose instructions into `CLAUDE.md` for you.\n\nskillflow validates the manifest against what the node declared. A node that\nfinished talking but produced none of what it promised has failed, and failing\nloudly there is much cheaper than discovering it three nodes later.\n\n### Artifact kinds\n\n| kind | what it carries |\n| --- | --- |\n| `document` | files: decks, spreadsheets, PDFs, reports |\n| `records` | structured rows or ids |\n| `note` | prose that is itself the product |\n| `ref` | a pointer to something in an external system |\n| `changeset` | proposed mutations that have **not** been applied |\n\n`summary` is required on every artifact, including file ones. The next agent\nreads it before it reads anything else.\n\n## Approval gates\n\nThe reason a coding agent can be run unattended is that a branch is free to\nthrow away. An agent with write access to your CRM has no such thing. So a node\ncan be gated:\n\n```yaml\n- id: propose_stages\n  readonly: true\n  prompt: Work out which records need a stage change. Do not write anything.\n  outputs:\n    - name: stage_changes\n      kind: changeset\n\n- id: apply_stages\n  needs: [propose_stages]\n  resources: [crm:main]\n  approval:\n    when: before\n    prompt: Apply these changes. Nothing has been written yet.\n  prompt: Apply exactly the approved changeset. Nothing else.\n```\n\n`when: before` holds the node until a human approves. `when: after` lets the node\nrun but holds its **outputs**, so nothing downstream compounds them unreviewed.\n\nPrefer the propose-then-apply shape wherever the work can be split that way. It\nis the closest thing ops work has to a diff.\n\nA run does not block waiting for you. It does everything it can, writes the\napproval requests, and exits:\n\n```bash\nskillflow run workflows/crm-intake.yaml -i batch_date=2026-09-24\nskillflow review <run-id>          # read what is waiting, including the changeset\nskillflow approve <run-id> apply_stages\nskillflow resume <run-id>\n```\n\nThat is what lets a workflow run from cron at 6am and still have a human in it\nat 9am.\n\n## Connectors and permissions\n\nEvery claude.ai connector you are signed in to is visible to a headless run. That\nis what makes your existing skills work unattended, and it is also the danger:\nan unattended agent with your CRM, your inbox and Slack is one bad inference\naway from a customer email.\n\nSo skillflow does not bypass permissions the way coding-agent tools do (safe\nthere only because git can undo anything). It classifies every connector call\nand applies a policy per task or step:\n\n- **Reads** go through without asking.\n- **Writes** are governed by `writes:` on the task or step:\n  - `ask` (the default): the agent stops at the call, the exact payload appears\n    on the task with **Allow once**, **Allow every &lt;tool&gt; this run** and\n    **Deny**, and the agent continues when you decide. Its watchdogs stand down\n    while it waits, so a long lunch does not kill the run.\n  - `allow`: writes go through. Use it for a skill you trust, and pair it with an\n    approval gate on the step.\n  - `deny`: the agent is told to report what it would have changed instead.\n- **Connectors** on a task or step restrict which servers it may touch at all,\n  reads included. Empty means any connected one.\n\nA denied call tells the agent not to try another route and to report what it\nwould have changed, and in practice it does exactly that.\n\nWhat counts as a write is judged by tool name, deliberately broadly: anything not\nrecognisably a read is treated as a write. The cost of gating a read is one\nclick; the cost of waving through a write is a customer email.\n\n`skillflow` lists your connectors from `claude mcp list` and shows which need\nsigning in again. That is done from a terminal with `claude mcp` or from the\nclaude.ai connector settings, not from skillflow.\n\n## Resource locks\n\n```yaml\nresources: [crm:main, spreadsheet:intake]\n```\n\nTwo nodes holding the same string never run at the same time, across every run on\nthe machine. Locks are taken in a fixed order and rolled back as a group, so a\ncontended run loses cleanly instead of deadlocking. Mark read-only nodes with\n`readonly: true` so a reader can see which steps can never change anything.\n\n## Resume\n\nEvery run appends to `ledger.jsonl`. `skillflow resume` reads it, skips every node\nthat already completed, and reuses the artifacts they produced.\n\nThis matters more here than it does for coding agents. A killed coding run leaves\na dirty worktree. A killed intake run has already written twelve of thirty rows,\nand re-running it from the top is the worst thing the tool could do.\n\n## Commands\n\n| command | |\n| --- | --- |\n| `skillflow run <file>` | execute a workflow. `--dry-run` walks the graph without calling any model |\n| `skillflow resume <id>` | continue a paused or partly failed run |\n| `skillflow status [id]` | list runs, or show one in detail |\n| `skillflow review <id>` | print everything waiting on you, with the changeset |\n| `skillflow approve <id> <node>` | grant an approval |\n| `skillflow reject <id> <node>` | reject one; everything downstream stops |\n| `skillflow validate <file>` | check a workflow and print its stages |\n| `skillflow graph <file>` | print it as a mermaid diagram |\n| `skillflow logs <id> [node]` | replay the ledger. `--tools` includes tool calls |\n| `skillflow artifacts <id>` | list what a run produced |\n| `skillflow ui` | local web view with approve and reject buttons |\n| `skillflow list [skills\\|agents]` | what is installed and usable as a node |\n| `skillflow new <file>` | scaffold a workflow to edit |\n\n## Workflow reference\n\n```yaml\nname: my-workflow\ndescription: what it does\n\ninputs:\n  week_of:\n    description: shown in errors and docs\n    required: true\n  limit:\n    default: \"25\"\n\nconnectors:            # MCP servers; a node only gets the ones it names\n  salesforce: { ... }\n\ndefaults:              # per-node settings fall back to these\n  model: claude-opus-5\n  permissionMode: acceptEdits\n  maxTurns: 80\n  timeoutSec: 3600\n  idleTimeoutSec: 900\n\nconcurrency: 3\n\nnodes:\n  - id: example        # required, unique\n    name: Human name\n    skill: my-skill    # a skill from ~/.claude/skills or a plugin\n    agent: my-agent    # or run the node as one of your subagents\n    needs: [other]     # upstream nodes; their artifacts are handed over\n    readonly: true     # documents that this node cannot change anything\n    resources: []      # logical locks\n    connectors: []     # connectors it may use, by name; empty means any\n    writes: ask        # ask | allow | deny, for calls that change something\n    if: \"${{ inputs.limit }}\"   # falsy skips the node, and its dependents\n    retries: 0\n    approval:\n      when: after      # or: before\n      prompt: what you are being asked\n    prompt: |\n      The instruction. Supports templating.\n    outputs:\n      - name: thing\n        kind: records\n        required: true\n```\n\nTemplates resolve `${{ inputs.x }}`, `${{ run.id }}`, `${{ run.date }}`,\n`${{ env.VAR }}` and `${{ nodes.<id>.outputs.<name>.summary }}`. There is\ndeliberately no expression evaluation: a workflow file is executable\nconfiguration, and every extra capability is a new way for a shared workflow to\nsurprise the person running it.\n\n## Cost\n\nEvery node is a full Claude session, so cost scales with node count, not with how\nsmall each step looks. The three-node `hello-chain` above, which does almost\nnothing, runs about $2.20 end to end.\n\nTwo things keep that down:\n\n- **Fewer, larger nodes.** Split a pipeline where a handoff or a gate genuinely\n  belongs, not to make each step tidy. Every split adds a session.\n- **`maxTurns` per node.** The default of 60 is a ceiling, not a target. A node\n  that reads two inputs and writes one output rarely needs more than 15.\n\n`skillflow status <id>` breaks cost down per node, which is usually enough to see\nwhich one is worth tuning.\n\n## Scheduling\n\nskillflow has no scheduler of its own, on purpose. Use cron:\n\n```bash\n0 6 * * 4 cd /path/to/ops && skillflow run workflows/document-sweep.yaml -i period=$(date +\\%Y-\\%m-\\%d) -i recipient=you@example.com\n```\n\nThe run pauses at its gates and waits for you.\n\n## Included workflows\n\nThe three in `workflows/` are templates, drawn from pipelines that actually run.\nThey exist as worked examples of the three shapes the artifact model has to cover:\n\n- `crm-intake.yaml` — writes to a system of record, showing both gate patterns\n  side by side so you can see when to use which\n- `outbound-campaign.yaml` — work that leaves the building, gated before it does\n- `document-sweep.yaml` — document handoff between nodes\n\nThey are meant to be edited, not run as-is. Replace the `your-*-skill` names with\nyour own skills, fill in `connectors`, and rewrite the prompts for your systems.\nThe structure is the part worth keeping: which steps are `readonly`, where the\ngates sit, and what each node hands on.\n\n## Status\n\nEarly. The scheduler, handoffs, gates, locks, ledger, resume, discovery and the\ntask board are covered by tests (`npm test`, 51 of them). What is thinnest is\nbreadth of real-world use: it has been exercised against a handful of pipelines,\nnot hundreds.\n\nTwo things it does not do yet, both deliberate:\n\n- **No connector sign-in.** skillflow uses the connectors your Claude Code is\n  already signed in to and tells you which ones need re-authorising, but the\n  OAuth itself happens in Claude Code or on claude.ai, not here.\n- **A run waiting on a permission dies with the app.** The request stays on\n  disk, but the agent process does not survive a quit. Resume re-runs that step\n  from the start.\n- **No multi-machine support.** Everything runs on the machine you start it on.\n  The locks are local files, so two laptops will not see each other's.\n- **Single user.** The board has no login and no notion of who is who beyond the\n  username it records on your comments.\n\n## Prior art\n\nThe design owes a debt to [Multica](https://github.com/multica-ai/multica),\nwhich solves a neighbouring problem: agents as assignees on an issue tracker.\nSeveral ideas here were taken from reading it, in particular delivering the brief\nas a file in the working directory rather than as an inline system prompt, and\nkeeping the idle watchdog separate from the wall-clock one. No code was copied,\nand skillflow is a different thing: a dependency graph with typed handoffs rather\nthan a tracker.\n\n## License\n\nMIT\n"}