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
- 参考文档references/approval-replay.md4.5 KB
- 参考文档references/schedule.md1.8 KB
- 参考文档references/signal-send.md2.4 KB
- 参考文档references/steps.md39.5 KB
- 参考文档references/sub-workflows.md11.4 KB
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.10USD
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
ifworks, butctx.when("production run", |i: &DeployInput| i.environment == "production").await?andctx.when_dynamic("build succeeded", build.is_success())make it show up inironflow-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. Seereferences/steps.md. - No stringly-typed access. Read step outputs with
stdout(),stderr(),exit_code(),status(),body(),error()(the message of anallow_failurestep that failed), the input withctx.input::<T>(), decisions through a#[derive(DecisionAnswers)]struct, agent answers through.output::<T>(), and artifacts through the handlestep.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 implementTypedWorkflow;ctx.workflow(&Child, ChildInput { .. })returnsrun_id()as aUuid. - Step names are cache keys. Stable, unique within the run, no timestamps or random ids. In a loop, suffix with the loop index.
ctx.parallelenforces 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, actx.workflows_withwave), give each oneWorkflowOptions::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 aT:ctx.agentreturns theTitself. Do not split it into a tools step and a formatting step. On an HTTP provider,.tool_profile(BUG)(aToolProfileconstant 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 animpl AgentRolecalled withctx.agent("review", Reviewer.task(input)), never a function returningAgentStepConfig(references/steps.md). - Agent output enters a prompt only as untrusted.
out.text()is anAgentText: pass it withPrompt::new().text("..").untrusted(&out.text()), neverformat!it into the next prompt. A typed answer derivesAgentAnswerand declares its text fieldsAgentText(aStringfield fails to compile);.untrusted(&answer.field)in a prompt,.raw()for a log, an artifact, a comment or a shell argument.{:?}printsAgentText(<N bytes>), not the text (references/steps.md). - Budget. Claude Code's system cache alone costs about
0.04USD, somax_budget_usdbelow0.10fails. Put adefault_max_cost_usdon 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, orctx.operation_allow_failure()for an operation, lets the run continue with statusWarning;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 areEngineError::Serialization. The engine also validates the payload againstinput_schema()before creating a run through the API, so the handler rarely sees a bad payload.
04 同仓库更多 Skills
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.
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.
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.
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.
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".
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".