AGENT SKILL

workflow

Write an Ironflow WorkflowHandler - typed input schema, steps (shell, http, agent, approval, human input, signal, decision, sub-workflow, parallel), registration in handlers(). Loaded by the ironflow hub for the workflow verb.

License 许可
MIT
最近更新
2026-11-04
Stars
★ 2
Forks
1

01 前言 Front Matter

name
workflow
description
Write an Ironflow WorkflowHandler - typed input schema, steps (shell, http, agent, approval, human input, signal, decision, sub-workflow, parallel), registration in handlers(). Loaded by the ironflow hub for the workflow verb.
user-invocable
false

02 资源文件 5

03 SKILL.md 正文

Ironflow workflow

A workflow is a struct implementing WorkflowHandler. Control flow is plain Rust. Each ctx.* call is a persisted step with its own status, duration and cost.

1. Gather the shape

Ask only what the code cannot guess, in one AskUserQuestion call:

  • workflow name (kebab-case, unique) and one-line purpose
  • input fields (name, type, required or default)
  • the steps in order, and which of them run in parallel
  • whether a human approval gate sits somewhere, and whether an agent step needs a budget above 0.10 USD

Then locate the workflows crate: grep -rl "pub fn handlers" --include=*.rs .

2. Write the handler

One file per handler in the workflows crate, snake_case.rs. Template:

use ironflow_engine::config::{ApprovalConfig, ShellConfig, StepConfig};
use ironflow_engine::context::WorkflowContext;
use ironflow_engine::handler::{HandlerFuture, WorkflowHandler, input_schema_for};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::Value;

/// Input payload of the `deploy` workflow. The dashboard renders a form from it.
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeployInput {
    /// Git ref to deploy.
    pub git_ref: String,
    /// Target environment.
    #[serde(default = "default_env")]
    pub environment: String,
}

fn default_env() -> String {
    "staging".to_string()
}

/// Builds, tests in parallel, waits for a human, deploys.
pub struct Deploy;

impl WorkflowHandler for Deploy {
    fn name(&self) -> &str {
        "deploy"
    }

    fn description(&self) -> &str {
        "Build, run checks in parallel, wait for approval, deploy."
    }

    fn category(&self) -> Option<&str> {
        Some("ops")
    }

    fn input_schema(&self) -> Option<Value> {
        Some(input_schema_for::<DeployInput>())
    }

    fn execute<'a>(&'a self, ctx: &'a mut WorkflowContext) -> HandlerFuture<'a> {
        Box::pin(async move {
            let input: DeployInput = ctx.input().await?;

            ctx.shell(
                "build",
                ShellConfig::new("cargo build --release").env("GIT_REF", &input.git_ref),
            )
            .await?;

            // Fan out. `true` = fail fast on the first error.
            let checks = ctx
                .parallel(
                    vec![
                        ("test", StepConfig::Shell(ShellConfig::new("cargo test"))),
                        ("lint", StepConfig::Shell(ShellConfig::new("cargo clippy"))),
                    ],
                    true,
                )
                .await?;
            if !checks.iter().all(|c| c.output.is_success()) {
                return Ok(());
            }

            if ctx
                .when("production run", |i: &DeployInput| i.environment == "production")
                .await?
            {
                // The run suspends here. After approval the handler is replayed from
                // the top: completed steps come back from cache, this code runs again.
                ctx.approval(
                    "approve-production",
                    ApprovalConfig::new("Ship to production?").with_timeout_seconds(3600),
                )
                .await?;
            }

            ctx.shell(
                "deploy",
                ShellConfig::new("./deploy.sh \"$ENVIRONMENT\"").env("ENVIRONMENT", &input.environment),
            )
            .await?;

            Ok(())
        })
    }
}

Then add the source display, which needs the real file name:

    fn source_code(&self) -> Option<&str> {
        Some(include_str!("deploy.rs"))
    }

Step catalogue with every config builder: references/steps.md. Read it before writing an agent, http, signal (ctx.wait_for_signal), secret or artifact step. Sending a signal from a workflow (ctx.send_signal): references/signal-send.md. A workflow that runs on a cron schedule (a time of day, a timezone, no overlapping runs): references/schedule.md. Sub-workflows (ctx.workflow, ctx.workflow_with, children in parallel with ctx.workflows_with): references/sub-workflows.md.

3. Register

In the workflows crate lib.rs: mod deploy;, pub use deploy::{Deploy, DeployInput};, and one line in handlers():

pub fn handlers() -> Vec<Box<dyn WorkflowHandler>> {
    vec![Box::new(Hello), Box::new(Deploy)]
}

That list feeds both the server and the worker. Nothing else to touch.

4. Verify

cargo build -p workflows
cargo test -p workflows

Then offer, in one sentence, the workflow reviewer (/ironflow review) and the test skill (/ironflow test <name>). Do not run either without an answer.

Rules that are not obvious

  • Branches are invisible to the planner unless you declare them. A plain if works, but ctx.when("production run", |i: &DeployInput| i.environment == "production").await? and ctx.when_dynamic("build succeeded", build.is_success()) make it show up in ironflow-cli run plan <name>, which lists the steps a run would create without executing anything. The first argument is a label, never parsed; the closure gets the typed input. See references/steps.md.
  • No stringly-typed access. Read step outputs with stdout(), stderr(), exit_code(), status(), body(), error() (the message of an allow_failure step that failed), the input with ctx.input::<T>(), decisions through a #[derive(DecisionAnswers)] struct, agent answers through .output::<T>(), and artifacts through the handle step.artifact("file")?. Never index the raw output JSON by key, and never copy a step name by hand where a handle exists. Sub-workflow children implement TypedWorkflow; ctx.workflow(&Child, ChildInput { .. }) returns run_id() as a Uuid.
  • Step names are cache keys. Stable, unique within the run, no timestamps or random ids. In a loop, suffix with the loop index. ctx.parallel enforces it for its branches: a duplicate name fails the run before any step starts. A child workflow's step is named after its handler: when the same child runs twice (a loop, a ctx.workflows_with wave), give each one WorkflowOptions::step_name.
  • Approval replays the handler. Anything that is not a ctx.* step runs again after approval. Keep side effects inside steps. Details and a safe pattern: references/approval-replay.md.
  • Agent steps: tools and structured output on one step. AgentStepConfig::new(prompt).allow_tool(Tool::Read).output::<T>() lets the agent read, then answer with a T: ctx.agent returns the T itself. Do not split it into a tools step and a formatting step. On an HTTP provider, .tool_profile(BUG) (a ToolProfile constant shared with the worker) picks a named tool profile registered on the worker (references/steps.md); a Claude CLI provider refuses it.
  • Agent steps: a reusable agent is an impl AgentRole. An agent reused across workflows (persona, tools, rights, output type) is an impl AgentRole called with ctx.agent("review", Reviewer.task(input)), never a function returning AgentStepConfig (references/steps.md).
  • Agent output enters a prompt only as untrusted. out.text() is an AgentText: pass it with Prompt::new().text("..").untrusted(&out.text()), never format! it into the next prompt. A typed answer derives AgentAnswer and declares its text fields AgentText (a String field fails to compile); .untrusted(&answer.field) in a prompt, .raw() for a log, an artifact, a comment or a shell argument. {:?} prints AgentText(<N bytes>), not the text (references/steps.md).
  • Budget. Claude Code's system cache alone costs about 0.04 USD, so max_budget_usd below 0.10 fails. Put a default_max_cost_usd on the handler when it contains an agent step.
  • User data goes through env, never into the command string. ShellConfig::new("echo \"$X\"").env("X", value).
  • A failing step fails the run. No hidden retries. allow_failure() on a step config, or ctx.operation_allow_failure() for an operation, lets the run continue with status Warning; retry_policy(RetryPolicy::...) opts into retries explicitly. On a shell step, exit_code_as_output() makes a non-zero exit a normal output (exit_code(), is_success()) instead of a failure.
  • ctx.input::<T>() errors are EngineError::Serialization. The engine also validates the payload against input_schema() before creating a run through the API, so the handler rarely sees a bad payload.

04 同仓库更多 Skills

ThomasTartrau
ThomasTartrau/ironflow

upgrade

Upgrade the Ironflow crates of a project and migrate its code, or audit existing code against the current Ironflow idioms. Reads the changelog between the installed and the target release, maps every change to the project's code, then proposes or applies the migrations. Loaded by the ironflow hub for the upgrade and audit verbs.

开发工程师
★ 2
ThomasTartrau
ThomasTartrau/ironflow

test

Write an end-to-end test for an Ironflow workflow handler - real Engine, InMemoryStore, RecordReplayProvider fixtures, assertions on run status and step names. Loaded by the ironflow hub for the test verb.

测试与质量Skill与智能体开发
★ 2
ThomasTartrau
ThomasTartrau/ironflow

setup

Scaffold a new Ironflow project - a Cargo workspace with a workflows lib, an API server binary, a worker binary, a hello workflow and its end-to-end test. Loaded by the ironflow hub for the setup verb.

Skill与智能体开发
★ 2
ThomasTartrau
ThomasTartrau/ironflow

operation

Write a custom Ironflow Operation - an integration (GitLab, Slack, any HTTP API) executed as a tracked step through ctx.operation(). Loaded by the ironflow hub for the operation verb.

★ 2
ThomasTartrau
ThomasTartrau/ironflow

ironflow

Entry point for building on the Ironflow workflow engine. Use for setting up an Ironflow project (server + worker), writing a WorkflowHandler, writing a custom Operation, testing a workflow end to end, reviewing a handler, upgrading the ironflow crates and migrating the code, or auditing existing code against the current Ironflow idioms. Trigger on "ironflow", "workflow handler", "ironflow setup", "ctx.shell", "ctx.agent", "approval gate", "upgrade ironflow", "bump ironflow", "créer un workflow ironflow", "mettre en place ironflow", "mettre à jour ironflow", "migrer ironflow".

测试与质量Skill与智能体开发
★ 2
ThomasTartrau
ThomasTartrau/ironflow

ops-crate

Generate a new ops/xxx crate for an external service integration. Identifies the right pattern (A=wrapper, B=HTTP direct, C=macro), scaffolds the full structure, and runs the quality checklist. Trigger on "ops crate", "new ops", "creer un crate ops", "add ops/xxx", "nouveau crate ops", "ironflow ops".

★ 2