Files
lda-wf/docs/wf_cli.md
T

27 KiB

wf CLI

wf is the workflow platform command-line interface. It is a second front door beside MCP: useful for shell-driven authoring, local validation, file-based patches, and agent workflows that do better with commands than giant MCP schemas.

wf uses the same config/store stack as the workflow server:

wf --config wf.config.json <command>

If --config is omitted, wf.config.json in the current working directory is used. Legacy wf_mcp.config.json files are still supported when passed explicitly with --config.

--local still uses the selected --config file. For neutral workflow configs, it builds the configured server in the CLI process, including configured Python sources and other source providers. Local means "same-process workflow server", not "local-only source transports": configured MCP HTTP/stdio sources may still open external transports from inside the CLI process. The configured durable store is reused, but a running server's in-memory source sessions/runtime pools are not reused. Use --url when you want to force the CLI to talk to an already-running wf-rpc-server; --local and --url are mutually exclusive.

Remote Server

Start a local/static JSON-RPC workflow server:

wf-rpc-server --store-root .wf_store --host 127.0.0.1 --port 8765

Prefer neutral workflow config for new MCP-backed servers:

{
  "version": 1,
  "server": {
    "store": {"kind": "filesystem", "root": ".wf_store"},
    "transports": [{"kind": "rpc_http", "host": "127.0.0.1", "port": 8765}],
    "sources": [
      {
        "kind": "mcp",
        "id": "everything.default",
        "provider": "everything",
        "account": "default",
        "transport": {"kind": "stdio", "command": "uvx", "args": ["mcp-server-everything"]}
      }
    ]
  }
}

--mcp-config is still accepted for legacy broker config files. Convert a legacy broker config into the neutral config shape:

wf config migrate-mcp wf_mcp.config.json --output wf.json

Validate a neutral workflow config before starting a server:

wf config validate wf.json

validate checks JSON/model shape, resolves config-relative paths, and imports trusted static Python sources so missing modules or registries fail before server startup. MCP sources are shape-validated only; use wf status, wf source list, or wf deploy validate --live against a running server for live upstream checks.

For a complete Python-source flow from ops.py through deployment/run, see the Python source runbook.

The old store_root field maps to server.store: {"kind": "filesystem", "root": ...}; old connections[] map to server.sources[] entries with kind: "mcp".

server.store is currently the default root for all file-backed server state: workflow artifacts/deployments/runs, source registry entries, catalog cache, and local/dev auth records. Role-specific store overrides are now supported via server.stores.* (e.g. server.stores.workflow, server.stores.auth, server.stores.source_registry, server.stores.catalog_cache); missing roles continue to fall back to server.store. For filesystem configs, role-specific store overrides can split local/dev auth records and catalog cache from workflow records.

Start a JSON-RPC server backed by MCP broker config and MCP-capable sources:

wf-rpc-server --mcp-config wf_mcp.config.json --host 127.0.0.1 --port 8765

Then point wf at it:

wf --url http://127.0.0.1:8765/rpc cap list
wf --url http://127.0.0.1:8765/rpc admin registry list

For a bounded end-to-end product check, use the RPC CLI smoke runbook. It keeps list/trace output small and avoids dumping arbitrary MCP resource payloads.

--mcp-config owns the server's workflow stores, MCP connections, and source registry. --store-root is for the local/static server path and cannot be combined with --mcp-config.

admin registry shows desired persisted source entries. It is separate from workflow artifacts and deployments, so it can be empty even when the server has runtime sources and saved workflows.

After wf admin registry add/update/enable/disable/remove, call:

wf --url http://127.0.0.1:8765/rpc admin registry apply

Apply updates the running server's source graph from desired registry state. It is explicit in v1; registry mutations are not auto-applied.

Check the selected target:

wf status
wf --url http://127.0.0.1:8765/rpc status

status is read-only. It reports the selected target, capability/source availability, durable run counts/latest run, admin counts, auth record count, and desired registry count when the target exposes those surfaces. It does not return auth payload values, trace entries, or checkpoint state.

Schema discovery:

wf schema
wf schema draft
wf schema raw --verbose
wf schema raw --full

--full is an alias for --verbose; docs use --verbose as the canonical flag.

Diagnose A Source

Use wf source diagnose <source_id> to inspect source health before calling capabilities:

wf --config wf.config.json source diagnose gdrive.personal

The output reports transport kind, auth reference, whether the auth record exists, whether the auth scheme is compatible with the transport, catalog snapshot counts, and non-secret diagnostics. Secret payload values are never printed.

List resource and prompt names exposed by a source:

wf --config wf.config.json source resources everything.default
wf --config wf.config.json source prompts everything.default --format json

These commands read source inventory only. They do not fetch resource content or render prompts, which can be large or stateful upstream operations.

Output Policy

JSON is the default for detail, mutation, and execution commands unless a command documents a safer human default. Some inventory commands default to line-oriented names/ids to avoid dumping large payloads.

List/discovery commands may support:

--format json      # complete machine-readable payload
--format ids       # one identifier per line
--format compact   # one concise line per item

Detail and mutation commands are JSON-only unless documented otherwise.

There is no table format in v1.

By default, expected operation failures are shown as compact CLI errors without Python tracebacks. Use the root --verbose flag when debugging internal failures:

wf --verbose --url http://127.0.0.1:8765/rpc source inspect missing.source

Lifecycle

The normal CLI workflow is:

  1. Inspect capabilities.
  2. Create a draft workspace from a capability.
  3. Inspect or patch the draft.
  4. Validate the draft.
  5. Save an artifact.
  6. Save a deployment with source bindings.
  7. Validate the deployment.
  8. Run the deployment.
  9. Read bounded trace detail only when debugging.

Capability Discovery

List capabilities:

wf cap list
wf cap list --source wf.std --format ids
wf cap list --query echo --format compact

--source filters by the exact source id shown by wf source list. For example, echo is usually an MCP tool such as everything.default.echo, not a wf.std builtin.

Inspect one capability:

wf cap inspect wf.std.concat

inspect returns the full contract, including wrapper_hints when available. Hints are scaffolding, not semantic guarantees.

Call one capability once before creating a draft:

wf cap call wf.std.constant --input '{"value": "hello"}'
wf --url http://127.0.0.1:8765/rpc cap call everything.default.echo --input '{"message": "hello"}'

Use --format compact for a bounded one-line summary that avoids dumping large MCP content-block envelopes:

wf cap call wf.std.constant --input '{"value": "hello"}' --format compact

Use --unwrap-text to extract exactly one MCP text content block. This implies text output and refuses images, resources, blobs, multiple content blocks, and non-MCP output:

wf cap call everything.default.echo --input '{"message": "hello"}' --unwrap-text

Use --max-output-chars N to bound compact/text terminal output. JSON output is never truncated.

cap call is an authoring/runtime smoke test. It uses the same local or remote target selection as the rest of the CLI and returns a normalized outcome, output, source id, and diagnostics. Use it to confirm payload shape and upstream source reachability before spending time on a draft workspace.

Be careful with raw MCP capabilities: some tools/resources can return large content-block envelopes, including base64 payloads. Prefer known-small smoke capabilities such as wf.std.constant unless you explicitly need to test a specific upstream source.

Draft Workspaces

Create a draft from a capability:

wf draft create concat_ws --capability wf.std.concat --name concat_ws

Capability-backed draft creation auto-binds required capability inputs only. Optional inputs are not wired by default. Bind one when the workflow should expose it; the focused helper projects the workflow input schema:

wf draft bind report_ws --revision 2 --step call --from input.path --to local.path

Use wf draft set-input with repeated --map flags when replacing several bindings for fields already declared in the workflow input or state schema. Add --merge only for a compatibility map-only edit to the existing list.

List and inspect drafts:

wf draft list --format compact
wf draft inspect concat_ws
wf draft inspect concat_ws --include-draft

Patch a draft with RFC 6902 JSON Patch:

wf draft patch concat_ws \
  --revision 1 \
  --input '[{"op":"replace","path":"/name","value":"concat_ws_v2"}]'

For larger structural edits, prefer a patch file:

wf draft patch concat_ws \
  --revision 1 \
  --input-file draft-patch.json

Focused draft edit commands cover common graph edits without writing RFC 6902 patches directly:

wf draft set-name concat_ws --revision 1 --name concat_ws_v2
wf draft set-route concat_ws --revision 2 --step call --outcome ok --to __end__
wf draft set-input concat_ws --revision 3 --step call --map input.items=items --map input.separator=separator
wf draft set-output concat_ws --revision 4 --step call --map value=state.value
wf draft set-workflow-output concat_ws --revision 5 \
  --map state.value=result --value format='"markdown"'
wf draft set-input concat_ws --revision 6 --step call --merge --map input.limit=limit
wf draft branch concat_ws --revision 7 --step call --route ok=__end__ --route error=tool_error
wf draft handle concat_ws --revision 8 --to fail --branch lookup:error --branch transform:error
wf draft compile concat_ws

set-input maps graph source paths to node-local input fields: input.title=report.title means input.title -> local.report.title. Targets are rootless node-local paths: write --map input.title=report.title, not --map input.title=local.report.title. Existing single-field targets such as input.text=text remain valid. Repeating a graph source is allowed, so one source can populate multiple local targets without being collapsed into a map.

Canonical input replacement supports path bindings, literal JSON values, ordered binding files, and clearing the complete list:

wf draft inspect WS --include-draft |
  jq '.draft.steps.publish.input' > bindings.json

wf draft set-input WS --revision 4 --step publish \
  --map state.report.title=request.title \
  --map state.report.markdown=request.body \
  --value request.format='"markdown"'

wf draft set-input WS --revision 5 --step publish \
  --bindings-file bindings.json

wf draft set-input WS --revision 6 --step publish --clear

Replacement is the default. --bindings-file is the canonical lossless form when binding order, literals, or repeated source paths matter. --merge is a compatibility-only option for map-only --map edits; it cannot be combined with --value, --bindings-file, or --clear. Existing literal bindings are retained, but merge cannot add literals or preserve canonical ordering and repeated-source fan-out.

set-output maps node-local output fields to workflow state paths: text=state.text means local.text -> state.text.

set-workflow-output canonically replaces the complete ordered workflow-output binding list. Repeat --map for graph source paths (input.*, state.*, or context.*) and --value for literal JSON values. For example, state.value=result means state.value -> output.result, while --value format='"markdown"' writes a literal to output.format.

Nested input.* and state.* source paths project missing nested output_schema fields from their declared source schemas. Literal bindings and context.* paths do not infer a schema: their output targets must already be declared, and literals must validate against those declared targets.

Use --bindings-file to restore an exported mixed path/value list without changing its order, or --clear to replace the list with []. An empty workflow-output list restores the runtime's implicit same-name state fallback; it does not promise an always-empty public output. Use --merge --map only for the compatibility map adapter. It is intentionally lossy and cannot represent literals, canonical ordering, or repeated-source fan-out reliably.

wf draft inspect WS --include-draft |
  jq '.draft.output' > output-bindings.json

wf draft set-workflow-output WS --revision 5 \
  --bindings-file output-bindings.json

wf draft set-workflow-output WS --revision 6 --clear

Bind A Step Path

Use bind when a capability step input/output binding also needs workflow schema projection. The selected step must be capability-backed (use: ...) because the command derives the schema from that capability. Direction matters: use input.* or state.* to local.* for step inputs, and local.* to state.* or output.* for step outputs.

wf draft bind concat_ws --revision 9 --step call --from local.value --to state.value
wf draft bind concat_ws --revision 9 --step call --from input.text --to local.text
wf draft bind concat_ws --revision 9 --step call --from local.result --to output.result
wf draft bind report_ws --revision 2 --step render --from input.title --to local.report.title
wf draft set-input report_ws --revision 3 --step render --map input.title=report.title
wf draft validate concat_ws

bind names both endpoints, so nested local paths use the explicit local. root. set-input and draft add capability --input already imply the local side and therefore use rootless targets such as report.title.

If the workflow schema field already exists, bind reuses it and only updates the step binding. Use set-input --merge for pure input-map edits when no schema projection is needed.

When validation gives a repair_hint with an exact focused wf draft bind command, run it before falling back to JSON Patch.

Repair-hint examples:

# Declare an undeclared workflow input field and bind it to a step input
wf draft bind report_ws --revision 4 --step read --from input.path --to local.path
# Request a public workflow output through an explicit state projection
wf draft bind report_ws --revision 5 --step render --from local.markdown --to output.markdown
# Set workflow output independently, including a nested source projection
wf draft set-workflow-output report_ws --revision 6 --map state.report.markdown=markdown

The command combines two common edits:

  • It copies the selected capability local path schema into the workflow input, state, or output schema at the graph path.
  • It merges the matching step input or output binding.

Use set-route separately for outcome routing.

Add Typed Steps To A Draft

Use wf draft add to add one typed step to an existing draft:

capability  interrupt  foreach  end
when        choose     match    subgraph

The capability command is explicit: it does not guess missing maps. Explicit top-level --input input.x=x and --input state.x=x mappings project the corresponding workflow input/state schema fields from the capability input schema. When the capability declares multiple outcomes, provide exactly one --route OUTCOME=TARGET for each declared outcome. Missing or unknown outcomes are rejected before the draft is mutated. When add capability --route rejects an outcome, use the declared outcomes and repair text from the error. Remove unknown route entries and add one route for each missing declared outcome.

wf draft add capability report_ws \
  --revision 3 \
  --step publish \
  --capability local.report.publish \
  --description "Publish report" \
  --retry 2 \
  --timeout-seconds 30 \
  --from-step extract \
  --from-outcome ok \
  --route ok=__end__ \
  --input state.report.title=request.title \
  --value request.format='"markdown"'

wf draft update capability report_ws \
  --revision 4 \
  --step publish \
  --clear-description \
  --retry 0 \
  --clear-timeout

On update, an omitted field is preserved. A matching --clear-* flag removes that metadata override; --retry 0 is a real value, not omission. Supplying --input and/or --value replaces the step's complete ordered canonical input list. --clear-input replaces it with []. Use --bindings-file for exact path/value interleaving exported from draft inspect --include-draft.

Capability updates deliberately preserve use, routes, and outputs. Change routes with set-route/branch, change output bindings with set-output or bind, and change the capability itself by removing and adding the step.

Interrupts preserve explicit request and resume contracts:

wf draft add interrupt report_ws \
  --revision 4 \
  --step review \
  --kind issue_review \
  --request-schema-file request.schema.json \
  --resume-schema-file resume.schema.json \
  --outcome submitted \
  --outcome cancelled \
  --from-step draft_issues \
  --from-outcome ok \
  --route submitted=create_issues \
  --route cancelled=revision_requested

Decision steps embed their targets and therefore do not accept --route:

wf draft add when report_ws \
  --revision 5 \
  --step decide \
  --condition-file has-report.json \
  --then publish \
  --otherwise revise

choose reads an ordered clause array from --clauses-file; match reads an ordered scalar case array from --cases-file. subgraph accepts either --workflow-name or an immutable --artifact-id plus --artifact-version, along with explicit boundary schemas and bindings.

Repeat --input and --bind-output once per mapping. Do not put multiple mappings after a single flag; --bind-output title=state.title summary=state.summary is parsed as an unexpected extra argument.

Run wf draft validate report_ws after adding the step. If validation returns a repair_hint, prefer the focused helper in that hint before JSON Patch.

Draft commands may return status: invalid after persisting an edit. That is normal for intermediate authoring. Repair diagnostics, run wf draft validate, then save/compile only after the workspace is valid.

Branch And Handle Existing Steps

Use wf draft branch to update routes for an existing step in one revision:

wf draft branch concat_ws --revision 6 --step call --route ok=__end__ --route error=tool_error

Use wf draft handle to route multiple source step outcomes to a common target:

wf draft handle concat_ws --revision 7 --to fail --branch lookup:error --branch transform:error

Remove Draft Elements

Use remove commands to back out one bad route, step, or binding without writing JSON Patch:

wf draft remove-route report_ws --revision 8 --step extract --outcome ok
wf draft remove-step report_ws --revision 9 --step render
wf draft remove-binding report_ws --revision 10 --step render --input title
wf draft remove-binding report_ws --revision 11 --step render --output markdown

Removal may leave the workspace status: invalid. That is normal for intermediate authoring. Run wf draft validate, then repair routes or bindings before saving or compiling.

Compile A Draft Workspace

Use wf draft compile to print the compiled raw plan without mutating or saving the draft:

wf draft compile concat_ws

On success, stdout is the raw plan JSON itself, not a compiled_plan envelope. The API/RPC/MCP operation also returns required capability metadata. On invalid draft status, the CLI prints the structured diagnostic envelope to stderr and exits nonzero.

Validate:

wf draft validate concat_ws

Draft validation diagnostics may include repair_hint commands. Treat these as the next focused command to try, not as proof that the draft is fixed. Re-run wf draft validate <workspace_id> after applying a hint.

Delete a draft workspace:

wf draft delete concat_ws --confirm

Draft deletion removes only the draft workspace. It does not delete artifacts, deployments, or runs.

Save as an artifact:

wf draft save concat_ws \
  --artifact concat_ws \
  --version 1 \
  --title "Concat Workflow" \
  --outcome ok

Use --kind wrapper when saving a callable wrapper artifact:

wf draft save concat_ws \
  --artifact concat_wrapper \
  --version 1 \
  --title "Concat Wrapper" \
  --kind wrapper \
  --outcome ok

Artifacts

List and inspect artifacts:

wf artifact list --format ids
wf artifact list --kind wrapper --format compact
wf artifact inspect concat_ws 1

Artifacts are immutable saved workflow definitions. List output is compact by design; use inspect for full details.

Create an artifact directly from a raw JSON/YAML workflow plan file:

wf artifact create-from-plan workflow.plan.json \
  --artifact concat_ws \
  --version 1 \
  --title "Concat Workflow" \
  --outcome ok \
  --binding local.ops=local.ops

artifact create-from-plan expects the raw workflow plan shape (nodes, edges, node). It does not accept draft workspace shape (steps, routes, use).

Prefer draft workspaces for iterative authoring. Use create-from-plan when a compiler, fixture, or advanced client already has a complete raw workflow plan. Artifact save responses include required_logical_sources and may include suggested_bindings. Copy suggested bindings into wf deploy save --binding when they are present; otherwise choose the concrete source/account explicitly.

Delete an unreferenced artifact version:

wf artifact delete smoke_artifact_20260609 1 --confirm

The command refuses to delete artifact versions still referenced by deployments. Delete referencing deployments first with wf deploy delete <deployment_id>.

Deployments

Save a deployment from flags:

wf deploy save concat_ws.default \
  --artifact concat_ws \
  --version 1

wf deploy create is accepted as an alias for wf deploy save; docs use save as the canonical verb because deployments are mutable records.

Save a deployment from JSON:

wf deploy save --input-file deployment.json

List, inspect, validate, and delete:

wf deploy list --format compact
wf deploy inspect concat_ws.default
wf deploy validate concat_ws.default
wf deploy validate concat_ws.default --live
wf deploy delete concat_ws.default

--live performs opt-in upstream liveness checks. Static validation can pass even when a live external source is temporarily unreachable.

Runs And Traces

Start a deployment:

wf run start concat_ws.default \
  --input '{"items":["red","blue"],"separator":" + "}'

List durable stopped runs:

wf run list --limit 20
wf run list --status interrupted
wf --url http://127.0.0.1:8765/rpc run list --status failed

wf run list returns compact stopped-run summaries from the target store. It does not include trace entries or checkpoint state. Use wf run inspect <run_id> for one run summary and wf run trace <run_id> --from 0 --limit 25 for bounded debug detail.

Inspect a run without trace detail:

wf run inspect run_123

Poll a run until it stops:

wf run watch run_123 --interval 1
wf run watch run_123 --trace --trace-limit 25

Read a bounded trace slice:

wf run trace run_123 --from 0 --limit 25

Trace output can be large. Always request a bounded range.

Interrupt Resume Schemas

Interrupted runs may include interrupt.request_schema and interrupt.resume_schema in wf run inspect output. The request schema describes the payload shown to the operator. The resume schema describes the payload accepted by wf run resume --payload or --payload-file.

Resume payload validation happens before workflow state mutation. If validation fails, inspect the schema and retry with a payload that matches the declared shape.

Explain

Explain stable diagnostic/error codes:

wf explain source_missing
wf explain deployment_unrunnable --format markdown
wf explain --input-file validation-output.json
wf explain --list --format compact

wf explain is exact-match and docs-backed. It is not fuzzy search and does not generate prose.

Draft authoring diagnostics commonly include:

  • invalid_source_path
  • invalid_destination_path
  • unknown_edge_destination
  • unknown_outcome
  • patch_invalid
  • revision_conflict

Common Diagnostics

Local/dev auth records

Auth payload values are write-only. list, inspect, save, and delete responses show ids, schemes, metadata, and payload keys only.

wf admin auth save drive.work --scheme bearer --payload-file drive-auth.json
wf admin auth list
wf admin auth inspect drive.work
wf admin auth delete drive.work --confirm

Use source auth_ref values to point sources at these records. Do not commit payload files containing real secrets.

Source Provider Setup

For MCP HTTP, MCP stdio, Python source, auth_ref, and OAuth setup examples, see the Source Provider Guide.

Google Drive MCP is documented there as manual smoke coverage only; do not use it as a regression fixture.

source_missing

A required logical source is not available or not bound.

Check:

wf deploy inspect <deployment_id>
wf cap list
wf deploy validate <deployment_id> --live

binding_missing

A deployment is missing a required logical-to-concrete source binding.

Check artifact requirements, then save the deployment with all required bindings:

wf deploy save <deployment_id> \
  --artifact <artifact_id> \
  --version <version> \
  --binding <logical>=<concrete>

capability_missing

The bound source does not expose a required capability.

Check:

wf cap list --source <source_id>
wf deploy inspect <deployment_id>

schema_changed

A saved dependency schema no longer matches the live capability. Inspect the live capability, patch the draft or wrapper, and save a new artifact version.

deployment_unrunnable

The deployment failed validation and should not be run yet.

Check:

wf deploy validate <deployment_id>
wf explain --input-file validation-output.json

Known Limits

  • The CLI reuses wf_mcp service/config/store wiring in v1.
  • Config loading registers stores and connections, but not arbitrary in-memory test NodeSpec functions.
  • wf does not replace MCP resources/prompts or interactive MCP clients.