Files
custom-claude-skills/skills/manual-workflow/SKILL.md
Paul O'Reilly a95197b162 add manual-workflow skill
Run a workflow template from a Claude Code session via the local control
plane. Handles topological wave execution, artifact passing, sentinel
resolution, and human review gates locally — no CP-side workflow
expansion required.

The pre-gathered "Available workflows" context uses python3 (not a
ls|sed pipe) so the Bash permission checker accepts it without needing
sed in allowed-tools — piped commands fail the check even when both
sides match an allowed pattern individually.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:33:36 +12:00

7.0 KiB

name, description, user_invocable, allowed-tools
name description user_invocable allowed-tools
manual-workflow Run a workflow template from a Claude Code session using the control plane for task execution. Handles artifact passing, sentinel resolution, and human review gates locally. Usage: /manual-workflow <template-name> [param=value ...] true Read, Glob, Grep, Bash(curl *), Bash(python3 *), Bash(sleep *), Bash(date *), Bash(cat *), Bash(ls *), Bash(kill *), Bash(kubectl port-forward *), AskUserQuestion, Write, Edit

/manual-workflow Skill

manual-workflow

Run a workflow template through the control plane, managing artifact passing and human review gates from this Claude session.

Arguments

$ARGUMENTS — Template name followed by optional key=value params.

Examples:

  • /manual-workflow spec-planning — interactive param prompting
  • /manual-workflow spec-planning project_id=agent-runtimes task_description="Design artifact passing"

Pre-gathered context

Available workflows

!python3 -c "import glob,os; [print(os.path.basename(p).removesuffix('.yaml')) for p in sorted(glob.glob(os.path.expanduser('~/dev/claude/projects/agent-runtimes/workflows/*.yaml')))]"

Control plane access

!curl -s --connect-timeout 2 http://localhost:8100/health 2>/dev/null || echo "CP_NOT_AVAILABLE"

Instructions

Step 0: Parse arguments and validate

Parse $ARGUMENTS to extract the template name (first word) and any key=value params.

If no template name provided, list available workflows and ask the user to pick one.

Step 1: Load and validate template

Read the workflow YAML from ~/dev/claude/projects/agent-runtimes/workflows/<name>.yaml. Parse it to extract: params, nodes, node depends_on, and model_override fields.

Display a summary:

Workflow: <name> v<version>
Description: <description>
Nodes: <count> (<node_ids>)
DAG: <visual showing phases>

Step 2: Resolve parameters

For each required param not provided via arguments, ask the user with AskUserQuestion. For each optional param, show the default and ask if they want to override.

For model_a / model_b: if not provided and the user doesn't override, set sensible defaults:

  • model_a = the harness context that runs via MiniMax (use minimax/v1 harness)
  • model_b = the default Claude model (use anthropic-cloud/v1 harness)

Or let the user specify model names and you'll map them to harnesses.

Step 3: Ensure CP access

Check if http://localhost:8100/health responds. If not:

kubectl port-forward -n agent-runtimes svc/controlplane 8100:8100 &

Wait for health check to pass.

Step 4: Execute in topological waves

Process nodes in dependency order. Nodes with no deps (or all deps satisfied) form a "wave" and run in parallel.

For each wave:

  1. Resolve sentinels in each node's prompt: replace <<ARTIFACT:node_id:key>> with the collected output from that node. Wrap injected content in [BEGIN ARTIFACT: node_id:key] / [END ARTIFACT: node_id:key] markers.

  2. Render Jinja2 — replace {{ param }} references with resolved param values. For Jinja2 conditionals ({% if %}, {% for %}), render them with the params dict. Use Python:

    from jinja2.sandbox import SandboxedEnvironment
    env = SandboxedEnvironment()
    rendered = env.from_string(prompt).render(**params)
    
  3. Determine harness for each node:

    • If model_override resolves to a value containing "model_a" and model_a is minimax → use minimax/v1
    • If model_override resolves to a value containing "model_b" or is empty → use anthropic-cloud/v1
    • The harness controls which provider's auth the agent gets
  4. Submit tasks to the CP via:

    curl -s -X POST http://localhost:8100/tasks \
      -H "Content-Type: application/json" \
      -d '{"name": "<node_name>", "project_id": "<project_id>", "prompt": "<rendered_prompt>", "harness": "<harness>", "runtime": {"cli": "claude", "model": "<model_if_applicable>", "timeout": 3600}}'
    
  5. Monitor tasks — poll every 30s:

    curl -s http://localhost:8100/tasks/<task_id>
    

    Display progress updates to the user: which nodes are running, which completed, duration.

  6. Collect outputs — when a task succeeds, extract the assistant's output from the task logs:

    # Parse NDJSON logs, find assistant message content
    for line in logs.split('\n'):
        entry = json.loads(line)
        if entry.get('type') == 'assistant':
            content = entry['message']['content']
            for block in content:
                if block['type'] == 'text':
                    result += block['text']
    

    Store the output as artifacts[node_id]['output'] for sentinel resolution in downstream nodes.

  7. Handle failures — if a task fails:

    • Show the error and logs to the user
    • Ask: "Retry this node, skip it, or abort the workflow?"
    • On retry: resubmit with the same prompt
    • On skip: mark the artifact as [NODE FAILED — no output available]
    • On abort: stop the workflow

Step 5: Human review gate

When the escalate node completes (or any node whose name contains "human" or "escalat"):

  1. Display the full escalation output to the user
  2. Ask for decisions on each "Decision needed: YES" item using AskUserQuestion
  3. Append the human decisions to the escalation artifact:
    ## Human Decisions (from manual review)
    
    ### Decision 1: [topic]
    **Choice:** [user's answer]
    **Rationale:** [user's explanation if provided]
    
    ### Decision 2: [topic]
    ...
    
  4. The updated artifact (original + human decisions) is then used for sentinel resolution in the synthesize node.

Step 6: Final output

When the synthesize node completes:

  1. Display a summary: total nodes, succeeded/failed, wall-clock time per phase
  2. Write the final spec output to a file in the project:
    • Ask the user where to save it (suggest spec/<name>.md)
    • Write the file
  3. Show the file path and suggest next steps: "Review the spec, then commit when ready."

Error handling

  • If the CP goes offline mid-workflow, pause and tell the user to fix it
  • If a node times out (>3600s), treat as failure
  • If ALL nodes in a wave fail, ask the user before continuing
  • Never leave orphan tasks running — cancel them on abort

Progress display

Keep the user informed with a compact status line after each check:

[Phase 2] spec_review_a: running (2m) | spec_review_b: running (1m45s) | security_review_a: succeeded (3m) | ...

Important notes

  • This skill runs the workflow LOCALLY from your Claude session — it does NOT use the CP's workflow expansion (Phase 2 artifact passing isn't implemented yet)
  • Artifacts are passed by embedding the full text in downstream prompts — this works but means large artifacts consume context
  • The harness mapping (model name → harness context) is specific to the agent-runtimes project's current provider setup
  • Monitor the agent-monitor in another terminal for richer progress: scripts/agent-monitor --filter "project=<project_id>" --filter "age<30m"