From a95197b162662e2e4469d190041275df1f160481 Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Sat, 11 Apr 2026 00:33:36 +1200 Subject: [PATCH] add manual-workflow skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- skills/manual-workflow/SKILL.md | 174 ++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 skills/manual-workflow/SKILL.md diff --git a/skills/manual-workflow/SKILL.md b/skills/manual-workflow/SKILL.md new file mode 100644 index 0000000..8fb8bf5 --- /dev/null +++ b/skills/manual-workflow/SKILL.md @@ -0,0 +1,174 @@ +--- +name: manual-workflow +description: > + 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 [param=value ...] +user_invocable: true +allowed-tools: 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/.yaml`. +Parse it to extract: `params`, `nodes`, node `depends_on`, and `model_override` fields. + +Display a summary: +``` +Workflow: v +Description: +Nodes: () +DAG: +``` + +### 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: +```bash +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 `<>` 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: + ```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: + ```bash + curl -s -X POST http://localhost:8100/tasks \ + -H "Content-Type: application/json" \ + -d '{"name": "", "project_id": "", "prompt": "", "harness": "", "runtime": {"cli": "claude", "model": "", "timeout": 3600}}' + ``` + +5. **Monitor tasks** — poll every 30s: + ```bash + curl -s http://localhost:8100/tasks/ + ``` + 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: + ```python + # 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/.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=" --filter "age<30m"`