@conductor-oss/conductor-skills 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE.txt +176 -0
- package/README.md +352 -0
- package/VERSION +1 -0
- package/bin/conductor-skills.js +135 -0
- package/commands/conductor-optimize.md +18 -0
- package/commands/conductor-scaffold-worker.md +19 -0
- package/commands/conductor-setup.md +15 -0
- package/commands/conductor.md +15 -0
- package/install.ps1 +677 -0
- package/install.sh +855 -0
- package/package.json +50 -0
- package/skills/conductor/SKILL.md +151 -0
- package/skills/conductor/examples/ai-agent-loop.md +119 -0
- package/skills/conductor/examples/ai-agent-mcp.md +129 -0
- package/skills/conductor/examples/create-and-run-workflow.md +50 -0
- package/skills/conductor/examples/do-while-loop.md +72 -0
- package/skills/conductor/examples/fork-join.md +52 -0
- package/skills/conductor/examples/llm-chat.md +61 -0
- package/skills/conductor/examples/llm-rag.md +115 -0
- package/skills/conductor/examples/monitor-and-retry.md +54 -0
- package/skills/conductor/examples/review-workflow.md +67 -0
- package/skills/conductor/examples/signal-wait-task.md +36 -0
- package/skills/conductor/examples/sub-workflow.md +52 -0
- package/skills/conductor/examples/workflows/ai-agent-loop.json +88 -0
- package/skills/conductor/examples/workflows/ai-agent-mcp.json +69 -0
- package/skills/conductor/examples/workflows/child-normalize.json +21 -0
- package/skills/conductor/examples/workflows/do-while-loop.json +35 -0
- package/skills/conductor/examples/workflows/fork-join.json +61 -0
- package/skills/conductor/examples/workflows/llm-chat.json +28 -0
- package/skills/conductor/examples/workflows/llm-rag.json +49 -0
- package/skills/conductor/examples/workflows/parent-pipeline.json +35 -0
- package/skills/conductor/examples/workflows/weather-notification.json +42 -0
- package/skills/conductor/references/api-reference.md +111 -0
- package/skills/conductor/references/cli-index.md +92 -0
- package/skills/conductor/references/fallback-cli.md +36 -0
- package/skills/conductor/references/optimization.md +148 -0
- package/skills/conductor/references/orkes.md +57 -0
- package/skills/conductor/references/schedules.md +81 -0
- package/skills/conductor/references/setup.md +126 -0
- package/skills/conductor/references/troubleshooting.md +35 -0
- package/skills/conductor/references/visualization.md +49 -0
- package/skills/conductor/references/workers.md +227 -0
- package/skills/conductor/references/workflow-definition.md +672 -0
- package/skills/conductor/scripts/conductor_api.py +396 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Example: Minimum LLM Workflow
|
|
2
|
+
|
|
3
|
+
The smallest useful AI workflow — one `LLM_CHAT_COMPLETE` task. Useful as a building block for prompts that don't need tools, retrieval, or loops.
|
|
4
|
+
|
|
5
|
+
> Conductor auto-enables LLM providers when their API key is set in the server's environment (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.). No separate provider registration needed in OSS.
|
|
6
|
+
|
|
7
|
+
## Workflow
|
|
8
|
+
|
|
9
|
+
See [workflows/llm-chat.json](workflows/llm-chat.json):
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"name": "summarize_text",
|
|
14
|
+
"tasks": [{
|
|
15
|
+
"name": "summarize",
|
|
16
|
+
"taskReferenceName": "summarize",
|
|
17
|
+
"type": "LLM_CHAT_COMPLETE",
|
|
18
|
+
"inputParameters": {
|
|
19
|
+
"llmProvider": "openai",
|
|
20
|
+
"model": "gpt-4o-mini",
|
|
21
|
+
"messages": [
|
|
22
|
+
{"role": "system", "message": "You summarize text in one sentence."},
|
|
23
|
+
{"role": "user", "message": "${workflow.input.text}"}
|
|
24
|
+
],
|
|
25
|
+
"temperature": 0.3,
|
|
26
|
+
"maxTokens": 200
|
|
27
|
+
}
|
|
28
|
+
}],
|
|
29
|
+
"outputParameters": {
|
|
30
|
+
"summary": "${summarize.output.result}",
|
|
31
|
+
"tokensUsed": "${summarize.output.tokenUsed}"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Run
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
conductor workflow create examples/workflows/llm-chat.json
|
|
40
|
+
conductor workflow start -w summarize_text -i '{"text": "Conductor is a workflow orchestration platform. It supports SIMPLE tasks, HTTP, SWITCH, FORK_JOIN, AI tasks, and more. Workflows are durable — they survive worker crashes by replaying from last completed task."}' --sync
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The `--sync` flag waits for completion and returns the workflow result inline. For long-running prompts (large models, long outputs), drop `--sync` and poll via `conductor workflow get-execution`.
|
|
44
|
+
|
|
45
|
+
## Output shape
|
|
46
|
+
|
|
47
|
+
`LLM_CHAT_COMPLETE` returns:
|
|
48
|
+
|
|
49
|
+
- `result` — the response text (string)
|
|
50
|
+
- `finishReason` — `STOP`, `LENGTH`, or `TOOL_CALLS`
|
|
51
|
+
- `tokenUsed`, `promptTokens`, `completionTokens` — token accounting
|
|
52
|
+
- `toolCalls` — present if the model invoked a tool (see [ai-agent-mcp.md](ai-agent-mcp.md))
|
|
53
|
+
|
|
54
|
+
Downstream tasks read `${summarize.output.result}` for the text and `${summarize.output.tokenUsed}` for cost tracking.
|
|
55
|
+
|
|
56
|
+
## Patterns
|
|
57
|
+
|
|
58
|
+
- **Provider per-task.** Mix providers in one workflow — `gpt-4o-mini` for cheap classification, `claude-opus-4-7` for the hard reasoning step. Each task picks its own `llmProvider` + `model`.
|
|
59
|
+
- **Temperature near zero** for deterministic / classification work; **0.7+** for generative / creative.
|
|
60
|
+
- **`maxTokens`** is a hard cap. If `finishReason == "LENGTH"`, the response was truncated — raise the cap.
|
|
61
|
+
- **Don't put secrets in the prompt.** API keys, tokens, PII — keep them in `${workflow.secrets.X}` (Orkes) or worker env. Workflow inputs are visible in the execution view.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Example: RAG — Retrieval-Augmented Generation
|
|
2
|
+
|
|
3
|
+
Classic RAG pattern: search a vector database for context, then ask an LLM to answer using only that context. Two tasks, one workflow.
|
|
4
|
+
|
|
5
|
+
> **⚠️ The single most important RAG rule:** the **embedding model** you use at **query time** in `LLM_SEARCH_INDEX` **MUST exactly match** the embedding model used at **index time** in `LLM_INDEX_TEXT`. Different models produce incompatible vector spaces — a mismatch returns nonsense matches without any error. State this explicitly when generating RAG workflows.
|
|
6
|
+
|
|
7
|
+
## Pipeline
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
LLM_SEARCH_INDEX (vector search) → LLM_CHAT_COMPLETE (answer with context)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The search task auto-embeds the user's question, queries the vector DB, and returns the top-k matching chunks. The chat task receives those chunks as system-prompt context and grounds its answer in them.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
1. **A vector database** registered with Conductor — Pinecone, Postgres pgvector, or MongoDB Atlas. The example uses `postgres-prod` (configured server-side; see your Conductor admin).
|
|
18
|
+
2. **Documents already indexed.** Use [`LLM_INDEX_TEXT`](../references/workflow-definition.md#llm_index_text) in a separate ingestion workflow to populate the index.
|
|
19
|
+
3. **An LLM provider** with its API key set (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.) — Conductor auto-enables providers when their API key is present.
|
|
20
|
+
|
|
21
|
+
## Workflow
|
|
22
|
+
|
|
23
|
+
See [workflows/llm-rag.json](workflows/llm-rag.json):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"name": "rag_qa",
|
|
28
|
+
"tasks": [
|
|
29
|
+
{
|
|
30
|
+
"name": "search_knowledge_base",
|
|
31
|
+
"taskReferenceName": "search",
|
|
32
|
+
"type": "LLM_SEARCH_INDEX",
|
|
33
|
+
"inputParameters": {
|
|
34
|
+
"vectorDB": "postgres-prod",
|
|
35
|
+
"namespace": "kb",
|
|
36
|
+
"index": "articles",
|
|
37
|
+
"embeddingModelProvider": "openai",
|
|
38
|
+
"embeddingModel": "text-embedding-3-small",
|
|
39
|
+
"query": "${workflow.input.question}",
|
|
40
|
+
"llmMaxResults": 3
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "generate_answer",
|
|
45
|
+
"taskReferenceName": "answer",
|
|
46
|
+
"type": "LLM_CHAT_COMPLETE",
|
|
47
|
+
"inputParameters": {
|
|
48
|
+
"llmProvider": "anthropic",
|
|
49
|
+
"model": "claude-sonnet-4-6",
|
|
50
|
+
"messages": [
|
|
51
|
+
{"role": "system", "message": "Answer using only the context below. If the answer isn't in the context, say \"I don't know.\"\n\nContext:\n${search.output.result}"},
|
|
52
|
+
{"role": "user", "message": "${workflow.input.question}"}
|
|
53
|
+
],
|
|
54
|
+
"temperature": 0.2,
|
|
55
|
+
"maxTokens": 500
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"outputParameters": {
|
|
60
|
+
"answer": "${answer.output.result}",
|
|
61
|
+
"sources": "${search.output.result}",
|
|
62
|
+
"tokensUsed": "${answer.output.tokenUsed}"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Run
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
conductor workflow create examples/workflows/llm-rag.json
|
|
71
|
+
conductor workflow start -w rag_qa -i '{"question": "How does Conductor handle worker failures?"}' --sync
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The `--sync` flag waits for both tasks (search + chat) to complete and returns the final answer plus the source chunks for citation.
|
|
75
|
+
|
|
76
|
+
## Output
|
|
77
|
+
|
|
78
|
+
- `answer` — the grounded response (string)
|
|
79
|
+
- `sources` — the retrieved chunks, with their metadata (use to render citations)
|
|
80
|
+
- `tokensUsed` — for cost tracking
|
|
81
|
+
|
|
82
|
+
## Variant: pre-computed embeddings
|
|
83
|
+
|
|
84
|
+
If you already have the query embedding (e.g., computed by an upstream worker), use `LLM_SEARCH_EMBEDDINGS` instead — same shape, but takes `embeddings` (a float array) instead of `query` (text). Saves one embedding call per request.
|
|
85
|
+
|
|
86
|
+
## Patterns
|
|
87
|
+
|
|
88
|
+
- **System prompt does the grounding.** "Answer only from the context below" is the difference between a real RAG system and a thin wrapper that pretends. State it explicitly. Tell the model what to do when the context doesn't cover the question — "say I don't know" beats hallucination.
|
|
89
|
+
- **Low temperature for QA.** `0.2` keeps answers grounded; higher temperatures invent facts.
|
|
90
|
+
- **`llmMaxResults` is the recall knob.** 3 chunks → tight answer, low cost. 10 chunks → broader recall, higher token spend, risk of off-topic context diluting the signal.
|
|
91
|
+
- **Different providers per task is fine.** Cheap small model for embedding (`text-embedding-3-small`), a strong reasoning model for the answer (`claude-sonnet-4-6`). Each task picks its own provider/model.
|
|
92
|
+
- **Return sources.** Always return `${search.output.result}` so the caller can cite or display sources. RAG without sources is just expensive Q&A.
|
|
93
|
+
|
|
94
|
+
## Ingesting documents (separate workflow)
|
|
95
|
+
|
|
96
|
+
For the search to find anything, an ingestion workflow needs to populate the index. Sketch:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"name": "ingest_doc",
|
|
101
|
+
"type": "LLM_INDEX_TEXT",
|
|
102
|
+
"inputParameters": {
|
|
103
|
+
"vectorDB": "postgres-prod",
|
|
104
|
+
"namespace": "kb",
|
|
105
|
+
"index": "articles",
|
|
106
|
+
"embeddingModelProvider": "openai",
|
|
107
|
+
"embeddingModel": "text-embedding-3-small",
|
|
108
|
+
"text": "${workflow.input.document}",
|
|
109
|
+
"docId": "${workflow.input.docId}",
|
|
110
|
+
"metadata": {"source": "${workflow.input.source}"}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Run this once per document. The embedding model **must** match the one used at query time in `rag_qa` — different models produce incompatible vector spaces.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Example: Monitor and Retry Failed Workflows
|
|
2
|
+
|
|
3
|
+
> "Show me failed workflows from today and retry the timeout failures."
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Find failed workflows in the time range
|
|
9
|
+
conductor workflow search -s FAILED --start-time-after "2024-01-15" -c 20
|
|
10
|
+
|
|
11
|
+
# 2. Inspect each one to identify the failed task and reason
|
|
12
|
+
conductor workflow get-execution {workflowId} -c
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Look at the failed task's `status` and `reasonForIncompletion`:
|
|
16
|
+
|
|
17
|
+
| Status | Action |
|
|
18
|
+
|--------|--------|
|
|
19
|
+
| `TIMED_OUT` | Retryable — `conductor workflow retry {id}` |
|
|
20
|
+
| `FAILED` (transient) | Retryable — `conductor workflow retry {id}` |
|
|
21
|
+
| `FAILED_WITH_TERMINAL_ERROR` | **Not** retryable. Surface root cause to the user before retrying. |
|
|
22
|
+
|
|
23
|
+
## Retry batch
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
conductor workflow retry {id1}
|
|
27
|
+
conductor workflow retry {id2}
|
|
28
|
+
conductor workflow retry {id3}
|
|
29
|
+
# verify
|
|
30
|
+
conductor workflow status {id1} # → RUNNING
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Reporting back
|
|
34
|
+
|
|
35
|
+
Group results in your reply to the user:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Found 4 failed workflows.
|
|
39
|
+
|
|
40
|
+
Retried (3):
|
|
41
|
+
- order_processing ...111 → RUNNING
|
|
42
|
+
- data_pipeline ...222 → RUNNING
|
|
43
|
+
- order_processing ...444 → RUNNING
|
|
44
|
+
|
|
45
|
+
Skipped — terminal failure (1):
|
|
46
|
+
- email_campaign ...333 "Invalid email template" — needs template fix
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Patterns demonstrated
|
|
50
|
+
|
|
51
|
+
- Time-range search with `--start-time-after` / `--start-time-before`.
|
|
52
|
+
- Distinguishing retryable from terminal failures.
|
|
53
|
+
- Batch retry with post-retry verification.
|
|
54
|
+
- Recommending root-cause fixes when timeouts persist (raise `responseTimeoutSeconds` on the task definition).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Example: Review and Optimize a Workflow
|
|
2
|
+
|
|
3
|
+
> "Review my `order_processing` workflow and tell me what to fix."
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Load the definition
|
|
9
|
+
conductor workflow get order_processing > /tmp/wf.json
|
|
10
|
+
|
|
11
|
+
# 2. For each SIMPLE task, load the task definition
|
|
12
|
+
conductor taskDef get charge_card
|
|
13
|
+
conductor taskDef get send_email
|
|
14
|
+
conductor taskDef get update_inventory
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Walk the checklist in [../references/optimization.md](../references/optimization.md). Group findings by severity.
|
|
18
|
+
|
|
19
|
+
## Sample report
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Workflow: order_processing v3 (12 tasks)
|
|
23
|
+
|
|
24
|
+
CRITICAL (2)
|
|
25
|
+
✗ B1 SIMPLE task `charge_card` (taskDef): responseTimeoutSeconds=0, timeoutSeconds=0
|
|
26
|
+
A hung payment worker hangs the workflow. Set responseTimeoutSeconds=30,
|
|
27
|
+
pollTimeoutSeconds=60, timeoutSeconds=300 on the task definition.
|
|
28
|
+
✗ D1 Workflow input `stripeApiKey` is a secret being passed in plaintext.
|
|
29
|
+
Move to ${workflow.secrets.STRIPE_KEY} or to the worker's environment.
|
|
30
|
+
|
|
31
|
+
WARN (3)
|
|
32
|
+
⚠ A1 description is empty
|
|
33
|
+
⚠ B2 No workflow timeoutSeconds — a stuck workflow can run forever.
|
|
34
|
+
Suggest 1800s with timeoutPolicy=TIME_OUT_WF.
|
|
35
|
+
⚠ B3 SIMPLE task `send_email` has retryCount=0. Email is transient by nature —
|
|
36
|
+
set retryCount=3, retryLogic=EXPONENTIAL_BACKOFF, retryDelaySeconds=30.
|
|
37
|
+
|
|
38
|
+
INFO
|
|
39
|
+
• A4 12 tasks — well within the 100-task limit
|
|
40
|
+
• B4 No failureWorkflow. This workflow mutates inventory and charges cards;
|
|
41
|
+
consider a failureWorkflow that releases the inventory hold and notifies ops.
|
|
42
|
+
|
|
43
|
+
Recommended Changes (priority order)
|
|
44
|
+
[ ] task_def_charge_card.json set responseTimeoutSeconds=30, pollTimeoutSeconds=60, timeoutSeconds=300
|
|
45
|
+
[ ] order_processing.json move stripeApiKey to ${workflow.secrets.STRIPE_KEY}
|
|
46
|
+
[ ] order_processing.json add description, timeoutSeconds=1800, timeoutPolicy=TIME_OUT_WF
|
|
47
|
+
[ ] task_def_send_email.json set retryCount=3, retryLogic=EXPONENTIAL_BACKOFF, retryDelaySeconds=30
|
|
48
|
+
[ ] order_processing.json (discussion) add failureWorkflow for inventory rollback + ops alert
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Offer to fix
|
|
52
|
+
|
|
53
|
+
> Want me to apply any of these? I can:
|
|
54
|
+
> - update the `charge_card` and `send_email` task definitions and re-register them
|
|
55
|
+
> - rewrite the workflow to add `timeoutSeconds`, `timeoutPolicy`, `description`
|
|
56
|
+
> - move `stripeApiKey` out of inputs (need to know where you want it: secrets or worker env)
|
|
57
|
+
>
|
|
58
|
+
> The `failureWorkflow` and the inventory-rollback design are bigger calls — I'd want to know your retry/refund policy before scaffolding that.
|
|
59
|
+
|
|
60
|
+
Don't apply silently. Each fix gets confirmed.
|
|
61
|
+
|
|
62
|
+
## What this example demonstrates
|
|
63
|
+
|
|
64
|
+
- Loading definition + each SIMPLE task's taskDef before reporting (timeouts and retry config live on the task def, not the workflow task).
|
|
65
|
+
- Grading findings (CRITICAL / WARN / INFO) so the user knows what's urgent.
|
|
66
|
+
- Offering targeted fixes, not a wholesale rewrite.
|
|
67
|
+
- Drawing the line at design decisions that need user input (failureWorkflow design, retry semantics).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Example: Signal a WAIT Task
|
|
2
|
+
|
|
3
|
+
> "Workflow `order-wf-789` is waiting for payment confirmation. Approve it."
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Find the blocking task
|
|
9
|
+
conductor workflow get-execution order-wf-789 -c
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Look for a task with `type: WAIT` and `status: IN_PROGRESS`. That's the one to signal. In this example: `wait_for_payment`.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 2. Signal it (use signal-sync to get the updated workflow back in one round-trip)
|
|
16
|
+
conductor task signal-sync \
|
|
17
|
+
--workflow-id order-wf-789 \
|
|
18
|
+
--task-ref wait_for_payment \
|
|
19
|
+
--status COMPLETED \
|
|
20
|
+
--output '{"paymentId": "pay-456", "amount": 149.99, "method": "credit_card"}'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## signal vs signal-sync
|
|
24
|
+
|
|
25
|
+
- `signal` — async, fire-and-forget. Returns immediately; workflow advances in the background.
|
|
26
|
+
- `signal-sync` — returns the updated workflow object in the same response. Use when you need to confirm the next task is now running, or to chain follow-up logic.
|
|
27
|
+
|
|
28
|
+
## Statuses you can signal
|
|
29
|
+
|
|
30
|
+
`COMPLETED`, `FAILED`, `FAILED_WITH_TERMINAL_ERROR`. Use `FAILED_WITH_TERMINAL_ERROR` to reject the WAIT permanently (no retry).
|
|
31
|
+
|
|
32
|
+
## Patterns demonstrated
|
|
33
|
+
|
|
34
|
+
- Reading execution state to find the blocking WAIT task.
|
|
35
|
+
- Passing structured `output` data with the signal — that data becomes `${wait_for_payment.output.x}` for downstream tasks.
|
|
36
|
+
- Sync signaling for human-in-the-loop confirmations.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Example: Composing Workflows with SUB_WORKFLOW
|
|
2
|
+
|
|
3
|
+
Run another registered workflow as a single task. The parent waits for the child to complete and reads its output. Useful for splitting large workflows into reusable units.
|
|
4
|
+
|
|
5
|
+
> If you want fire-and-forget instead — parent does not wait — use `START_WORKFLOW`.
|
|
6
|
+
|
|
7
|
+
## Pattern
|
|
8
|
+
|
|
9
|
+
Two registered workflows: `child_normalize` (reusable) and `parent_pipeline` (composes the child).
|
|
10
|
+
|
|
11
|
+
### Child — `child_normalize`
|
|
12
|
+
|
|
13
|
+
See [workflows/child-normalize.json](workflows/child-normalize.json). Takes a raw payload, returns a normalized object.
|
|
14
|
+
|
|
15
|
+
### Parent — `parent_pipeline`
|
|
16
|
+
|
|
17
|
+
See [workflows/parent-pipeline.json](workflows/parent-pipeline.json):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"name": "normalize",
|
|
22
|
+
"taskReferenceName": "normalize",
|
|
23
|
+
"type": "SUB_WORKFLOW",
|
|
24
|
+
"subWorkflowParam": { "name": "child_normalize", "version": 1 },
|
|
25
|
+
"inputParameters": {
|
|
26
|
+
"payload": "${workflow.input.raw}"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The child's `outputParameters` become the SUB_WORKFLOW task's output:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
${normalize.output.normalized}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Run
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Register both — child first
|
|
41
|
+
conductor workflow create examples/workflows/child-normalize.json
|
|
42
|
+
conductor workflow create examples/workflows/parent-pipeline.json
|
|
43
|
+
|
|
44
|
+
conductor workflow start -w parent_pipeline -i '{"raw": {"name": "ALICE", "email": "ALICE@EX.COM"}}'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- The child must be registered before the parent runs (not before the parent is created — Conductor doesn't validate references at definition time).
|
|
50
|
+
- Pin `version` in `subWorkflowParam` for stability. Omitting it picks the latest, which can break parents silently when the child is updated.
|
|
51
|
+
- Child failure surfaces as a SUB_WORKFLOW task failure on the parent.
|
|
52
|
+
- For dynamic-named children, set `subWorkflowParam.name` from input or use `START_WORKFLOW`.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autonomous_agent",
|
|
3
|
+
"description": "ReAct-pattern agent loop: think → act → observe until done",
|
|
4
|
+
"version": 1,
|
|
5
|
+
"schemaVersion": 2,
|
|
6
|
+
"inputParameters": ["task", "mcpServer", "tools"],
|
|
7
|
+
"tasks": [
|
|
8
|
+
{
|
|
9
|
+
"name": "agent_loop",
|
|
10
|
+
"taskReferenceName": "loop",
|
|
11
|
+
"type": "DO_WHILE",
|
|
12
|
+
"loopCondition": "if ($.loop['iteration'] < 10 && $.loop[$.loop['iteration']].think.output.result.done != true) { true; } else { false; }",
|
|
13
|
+
"loopOver": [
|
|
14
|
+
{
|
|
15
|
+
"name": "think",
|
|
16
|
+
"taskReferenceName": "think",
|
|
17
|
+
"type": "LLM_CHAT_COMPLETE",
|
|
18
|
+
"inputParameters": {
|
|
19
|
+
"llmProvider": "openai",
|
|
20
|
+
"model": "gpt-4o-mini",
|
|
21
|
+
"messages": [
|
|
22
|
+
{
|
|
23
|
+
"role": "system",
|
|
24
|
+
"message": "You are an agent. Tools: ${workflow.input.tools}. Previous results: ${loop.output}. Decide next step. Respond as JSON: { done: bool, method?: string, arguments?: object, answer?: string }."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"role": "user",
|
|
28
|
+
"message": "${workflow.input.task}"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"temperature": 0.1,
|
|
32
|
+
"maxTokens": 500
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "act",
|
|
37
|
+
"taskReferenceName": "act",
|
|
38
|
+
"type": "SWITCH",
|
|
39
|
+
"evaluatorType": "javascript",
|
|
40
|
+
"expression": "$.done ? 'finish' : 'call_tool'",
|
|
41
|
+
"inputParameters": {
|
|
42
|
+
"done": "${think.output.result.done}"
|
|
43
|
+
},
|
|
44
|
+
"decisionCases": {
|
|
45
|
+
"call_tool": [
|
|
46
|
+
{
|
|
47
|
+
"name": "execute",
|
|
48
|
+
"taskReferenceName": "execute",
|
|
49
|
+
"type": "CALL_MCP_TOOL",
|
|
50
|
+
"inputParameters": {
|
|
51
|
+
"mcpServer": "${workflow.input.mcpServer}",
|
|
52
|
+
"method": "${think.output.result.method}",
|
|
53
|
+
"arguments": "${think.output.result.arguments}"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"finish": [
|
|
58
|
+
{
|
|
59
|
+
"name": "done",
|
|
60
|
+
"taskReferenceName": "done",
|
|
61
|
+
"type": "NOOP"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"inputParameters": {
|
|
68
|
+
"loop": "${loop.output}"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "final_answer",
|
|
73
|
+
"taskReferenceName": "final",
|
|
74
|
+
"type": "INLINE",
|
|
75
|
+
"inputParameters": {
|
|
76
|
+
"evaluatorType": "graaljs",
|
|
77
|
+
"expression": "function e() { var i = $.loop_output['iteration']; return { answer: $.loop_output[i].think.output.result.answer }; } e();",
|
|
78
|
+
"loop_output": "${loop.output}"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"outputParameters": {
|
|
83
|
+
"iterations": "${loop.output.iteration}",
|
|
84
|
+
"answer": "${final.output.result.answer}"
|
|
85
|
+
},
|
|
86
|
+
"timeoutSeconds": 600,
|
|
87
|
+
"timeoutPolicy": "TIME_OUT_WF"
|
|
88
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my_first_agent",
|
|
3
|
+
"description": "AI agent that discovers MCP tools, plans, executes, and summarizes",
|
|
4
|
+
"version": 1,
|
|
5
|
+
"schemaVersion": 2,
|
|
6
|
+
"inputParameters": ["task"],
|
|
7
|
+
"tasks": [
|
|
8
|
+
{
|
|
9
|
+
"name": "discover_tools",
|
|
10
|
+
"taskReferenceName": "discover",
|
|
11
|
+
"type": "LIST_MCP_TOOLS",
|
|
12
|
+
"inputParameters": {
|
|
13
|
+
"mcpServer": "http://localhost:3001/mcp"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "plan_action",
|
|
18
|
+
"taskReferenceName": "plan",
|
|
19
|
+
"type": "LLM_CHAT_COMPLETE",
|
|
20
|
+
"inputParameters": {
|
|
21
|
+
"llmProvider": "openai",
|
|
22
|
+
"model": "gpt-4o-mini",
|
|
23
|
+
"messages": [
|
|
24
|
+
{
|
|
25
|
+
"role": "system",
|
|
26
|
+
"message": "You are an AI agent. Available tools: ${discover.output.tools}. Pick exactly one tool and respond as JSON with fields `method` and `arguments`."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"role": "user",
|
|
30
|
+
"message": "${workflow.input.task}"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"temperature": 0.1,
|
|
34
|
+
"maxTokens": 500
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "execute_tool",
|
|
39
|
+
"taskReferenceName": "execute",
|
|
40
|
+
"type": "CALL_MCP_TOOL",
|
|
41
|
+
"inputParameters": {
|
|
42
|
+
"mcpServer": "http://localhost:3001/mcp",
|
|
43
|
+
"method": "${plan.output.result.method}",
|
|
44
|
+
"arguments": "${plan.output.result.arguments}"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "summarize_result",
|
|
49
|
+
"taskReferenceName": "summarize",
|
|
50
|
+
"type": "LLM_CHAT_COMPLETE",
|
|
51
|
+
"inputParameters": {
|
|
52
|
+
"llmProvider": "openai",
|
|
53
|
+
"model": "gpt-4o-mini",
|
|
54
|
+
"messages": [
|
|
55
|
+
{
|
|
56
|
+
"role": "user",
|
|
57
|
+
"message": "The user asked: \"${workflow.input.task}\". Tool returned: ${execute.output.content}. Reply in one short paragraph."
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"maxTokens": 500
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"outputParameters": {
|
|
65
|
+
"plan": "${plan.output.result}",
|
|
66
|
+
"toolResult": "${execute.output.content}",
|
|
67
|
+
"summary": "${summarize.output.result}"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "child_normalize",
|
|
3
|
+
"description": "Normalize a user payload (lowercase email, trim name)",
|
|
4
|
+
"version": 1,
|
|
5
|
+
"schemaVersion": 2,
|
|
6
|
+
"inputParameters": ["payload"],
|
|
7
|
+
"tasks": [
|
|
8
|
+
{
|
|
9
|
+
"name": "transform",
|
|
10
|
+
"taskReferenceName": "transform",
|
|
11
|
+
"type": "JSON_JQ_TRANSFORM",
|
|
12
|
+
"inputParameters": {
|
|
13
|
+
"data": "${workflow.input.payload}",
|
|
14
|
+
"queryExpression": "{name: (.name | ascii_downcase | gsub(\"^\\\\s+|\\\\s+$\"; \"\")), email: (.email | ascii_downcase)}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"outputParameters": {
|
|
19
|
+
"normalized": "${transform.output.result}"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "paginated_fetch",
|
|
3
|
+
"description": "Fetch N pages from an endpoint via DO_WHILE",
|
|
4
|
+
"version": 1,
|
|
5
|
+
"schemaVersion": 2,
|
|
6
|
+
"inputParameters": ["endpoint", "count"],
|
|
7
|
+
"tasks": [
|
|
8
|
+
{
|
|
9
|
+
"name": "loop",
|
|
10
|
+
"taskReferenceName": "loop_ref",
|
|
11
|
+
"type": "DO_WHILE",
|
|
12
|
+
"loopCondition": "if ($.loop_ref['iteration'] < $.value) { true; } else { false; }",
|
|
13
|
+
"loopOver": [
|
|
14
|
+
{
|
|
15
|
+
"name": "do_work",
|
|
16
|
+
"taskReferenceName": "do_work",
|
|
17
|
+
"type": "HTTP",
|
|
18
|
+
"inputParameters": {
|
|
19
|
+
"http_request": {
|
|
20
|
+
"uri": "${workflow.input.endpoint}?page=${loop_ref.output.iteration}",
|
|
21
|
+
"method": "GET"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"inputParameters": {
|
|
27
|
+
"value": "${workflow.input.count}",
|
|
28
|
+
"loop_ref": "${loop_ref.output}"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"outputParameters": {
|
|
33
|
+
"iterations": "${loop_ref.output.iteration}"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "parallel_fetch",
|
|
3
|
+
"description": "Fetch inventory and pricing in parallel, then merge",
|
|
4
|
+
"version": 1,
|
|
5
|
+
"schemaVersion": 2,
|
|
6
|
+
"inputParameters": ["inventoryUrl", "pricingUrl"],
|
|
7
|
+
"tasks": [
|
|
8
|
+
{
|
|
9
|
+
"name": "fork",
|
|
10
|
+
"taskReferenceName": "fork",
|
|
11
|
+
"type": "FORK_JOIN",
|
|
12
|
+
"forkTasks": [
|
|
13
|
+
[
|
|
14
|
+
{
|
|
15
|
+
"name": "fetch_inventory",
|
|
16
|
+
"taskReferenceName": "inventory",
|
|
17
|
+
"type": "HTTP",
|
|
18
|
+
"inputParameters": {
|
|
19
|
+
"http_request": {
|
|
20
|
+
"uri": "${workflow.input.inventoryUrl}",
|
|
21
|
+
"method": "GET"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
{
|
|
28
|
+
"name": "fetch_pricing",
|
|
29
|
+
"taskReferenceName": "pricing",
|
|
30
|
+
"type": "HTTP",
|
|
31
|
+
"inputParameters": {
|
|
32
|
+
"http_request": {
|
|
33
|
+
"uri": "${workflow.input.pricingUrl}",
|
|
34
|
+
"method": "GET"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "join",
|
|
43
|
+
"taskReferenceName": "join",
|
|
44
|
+
"type": "JOIN",
|
|
45
|
+
"joinOn": ["inventory", "pricing"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "merge",
|
|
49
|
+
"taskReferenceName": "merge",
|
|
50
|
+
"type": "JSON_JQ_TRANSFORM",
|
|
51
|
+
"inputParameters": {
|
|
52
|
+
"inventory": "${inventory.output.response.body}",
|
|
53
|
+
"pricing": "${pricing.output.response.body}",
|
|
54
|
+
"queryExpression": "{items: .inventory.items, prices: .pricing.prices}"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"outputParameters": {
|
|
59
|
+
"merged": "${merge.output.result}"
|
|
60
|
+
}
|
|
61
|
+
}
|