@agentled/cli 0.1.6 → 0.5.0

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.
Files changed (53) hide show
  1. package/README.md +136 -0
  2. package/dist/builtin-tools-catalog.d.ts +37 -0
  3. package/dist/builtin-tools-catalog.js +96 -0
  4. package/dist/builtin-tools-catalog.js.map +1 -0
  5. package/dist/commands/auth.js +30 -0
  6. package/dist/commands/auth.js.map +1 -1
  7. package/dist/commands/examples.d.ts +15 -0
  8. package/dist/commands/examples.js +100 -0
  9. package/dist/commands/examples.js.map +1 -0
  10. package/dist/commands/scaffold.d.ts +14 -0
  11. package/dist/commands/scaffold.js +103 -0
  12. package/dist/commands/scaffold.js.map +1 -0
  13. package/dist/commands/schema.d.ts +10 -0
  14. package/dist/commands/schema.js +107 -0
  15. package/dist/commands/schema.js.map +1 -0
  16. package/dist/commands/skills.d.ts +9 -0
  17. package/dist/commands/skills.js +94 -0
  18. package/dist/commands/skills.js.map +1 -0
  19. package/dist/commands/tools.d.ts +10 -0
  20. package/dist/commands/tools.js +53 -0
  21. package/dist/commands/tools.js.map +1 -0
  22. package/dist/commands/workflows.js +227 -9
  23. package/dist/commands/workflows.js.map +1 -1
  24. package/dist/context-schema.d.ts +37 -0
  25. package/dist/context-schema.js +108 -0
  26. package/dist/context-schema.js.map +1 -0
  27. package/dist/index.js +8 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/utils/preflight.d.ts +25 -0
  30. package/dist/utils/preflight.js +293 -0
  31. package/dist/utils/preflight.js.map +1 -0
  32. package/dist/utils/skills.d.ts +49 -0
  33. package/dist/utils/skills.js +214 -0
  34. package/dist/utils/skills.js.map +1 -0
  35. package/package.json +4 -1
  36. package/patterns/v1/00-why-agentic-ops.md +107 -0
  37. package/patterns/v1/01-trigger-design.md +107 -0
  38. package/patterns/v1/02-dedup-gates.md +135 -0
  39. package/patterns/v1/03-credit-efficiency.md +130 -0
  40. package/patterns/v1/04-loop-patterns.md +147 -0
  41. package/patterns/v1/05-child-workflow-contracts.md +151 -0
  42. package/patterns/v1/06-conditional-routing.md +151 -0
  43. package/patterns/v1/07-error-handling.md +157 -0
  44. package/patterns/v1/08-composed-email-approval.md +130 -0
  45. package/patterns/v1/09-reports-and-knowledge-storage.md +166 -0
  46. package/scaffolds/README.md +62 -0
  47. package/scaffolds/ai-with-tools.json +49 -0
  48. package/scaffolds/email-polling-dedup.json +71 -0
  49. package/scaffolds/extract-threshold-alert.json +131 -0
  50. package/scaffolds/lead-scoring-kg.json +84 -0
  51. package/scaffolds/list-match-email.json +131 -0
  52. package/scaffolds/minimal.json +20 -0
  53. package/skills/agentled/SKILL.md +573 -0
@@ -0,0 +1,157 @@
1
+ # 07 — Error handling: skip vs stop vs wait
2
+
3
+ **Problem**: Developers use the same error behavior (`stop` or `skip`) everywhere, causing workflows to either halt on recoverable errors or continue silently with missing data.
4
+
5
+ **Why it fails silently**: `skip` silently passes empty/null data to downstream steps, which then produce wrong outputs with no error. `stop` halts workflows on optional steps that should have been bypassed. Neither produces a visible failure at the wrong step — the failure surfaces later, in a confusing place.
6
+
7
+ ---
8
+
9
+ ## The three behaviors
10
+
11
+ Every entry condition has an `onCriteriaFail` setting that determines what happens when the condition isn't met:
12
+
13
+ | Value | What it does | When to use |
14
+ |---|---|---|
15
+ | `skip` | Skip this step, pass `null`/empty to downstream steps, continue execution | Optional step — workflow is valid without it |
16
+ | `stop` | Halt the entire execution immediately | Hard prerequisite — nothing downstream is meaningful without this step |
17
+ | `wait` | Block this step until criteria become true | Async dependency — waiting for a loop or parallel group to finish |
18
+
19
+ ---
20
+
21
+ ## Anti-pattern: stop everywhere
22
+
23
+ ```yaml
24
+ # Wrong: using stop for an optional enrichment step
25
+ - id: enrich-linkedin
26
+ type: app-action
27
+ entryConditions:
28
+ onCriteriaFail: "stop" # ← wrong: this is optional
29
+ criteria:
30
+ - variable: "{{input.linkedinUrl}}"
31
+ operator: "isNotNull"
32
+ # If linkedinUrl is missing: entire workflow halts.
33
+ # But the workflow could run fine without LinkedIn data.
34
+ ```
35
+
36
+ ```yaml
37
+ # Correct: optional enrichment step uses skip
38
+ - id: enrich-linkedin
39
+ type: app-action
40
+ entryConditions:
41
+ onCriteriaFail: "skip" # ← correct: skip if no URL
42
+ conditionText: "Skip LinkedIn enrichment if no URL provided"
43
+ criteria:
44
+ - variable: "{{input.linkedinUrl}}"
45
+ operator: "isNotNull"
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Anti-pattern: skip for hard prerequisites
51
+
52
+ ```yaml
53
+ # Wrong: using skip for required input validation
54
+ - id: validate-required-fields
55
+ type: code
56
+ entryConditions:
57
+ onCriteriaFail: "skip" # ← wrong: nothing downstream works without this
58
+ criteria:
59
+ - variable: "{{input.companyDomain}}"
60
+ operator: "isNotNull"
61
+ # If domain is missing: validation is skipped.
62
+ # Next step tries to enrich with null domain.
63
+ # Enrichment fails with a confusing error 3 steps later.
64
+ ```
65
+
66
+ ```yaml
67
+ # Correct: hard prerequisite uses stop
68
+ - id: validate-required-fields
69
+ type: code
70
+ entryConditions:
71
+ onCriteriaFail: "stop" # ← correct: stop early with a clear failure
72
+ conditionText: "Company domain is required"
73
+ criteria:
74
+ - variable: "{{input.companyDomain}}"
75
+ operator: "isNotNull"
76
+ ```
77
+
78
+ Fail fast. A clear stop at the prerequisite is better than a confusing error 5 steps later.
79
+
80
+ ---
81
+
82
+ ## `wait`: blocking on async completion
83
+
84
+ `wait` is for a specific use case: blocking a step until an async process finishes. The two common cases are loop completion and parallel group completion.
85
+
86
+ ```yaml
87
+ # Wait for a loop to finish before consuming its output
88
+ - id: aggregate-results
89
+ type: ai-action
90
+ entryConditions:
91
+ onCriteriaFail: "wait"
92
+ conditionText: "Wait for all enrichment loop iterations to complete"
93
+ criteria:
94
+ - type: loop_completion
95
+ stepId: enrich-loop
96
+ operator: "=="
97
+ value: true
98
+ prompt: "Summarize these enriched companies: {{steps.enrich-loop.outputs}}"
99
+ ```
100
+
101
+ `wait` is not a general-purpose retry mechanism. It's specifically for dependency ordering in async workflows.
102
+
103
+ ---
104
+
105
+ ## Decision guide
106
+
107
+ Ask these questions in order:
108
+
109
+ **1. Is this step required for the workflow to produce a valid output?**
110
+ - Yes → `stop`
111
+ - No → continue to question 2
112
+
113
+ **2. Is this step waiting for another step/loop/group to finish?**
114
+ - Yes → `wait`
115
+ - No → `skip`
116
+
117
+ Examples:
118
+
119
+ | Step | Required? | Async wait? | Use |
120
+ |---|---|---|---|
121
+ | "Validate required domain field" | Yes | No | `stop` |
122
+ | "Enrich LinkedIn (optional)" | No | No | `skip` |
123
+ | "Send Slack alert (if webhook configured)" | No | No | `skip` |
124
+ | "Aggregate loop results" | Yes | Yes (loop) | `wait` |
125
+ | "Score company (ICP match required)" | Yes | No | `stop` |
126
+ | "Add CRM note (nice to have)" | No | No | `skip` |
127
+
128
+ ---
129
+
130
+ ## Handling downstream null data after skip
131
+
132
+ When a step is skipped, its outputs are `null` or empty. Downstream steps that reference those outputs must handle nulls gracefully:
133
+
134
+ ```yaml
135
+ # Pattern: null-safe reference in AI prompt
136
+ prompt: |
137
+ Company: {{steps.enrich.company.name | default: "Unknown"}}
138
+ LinkedIn headline: {{steps.enrich-linkedin.headline | default: "Not available"}}
139
+ Score this company based on what's available.
140
+ ```
141
+
142
+ Or use a code step to normalize before passing to downstream AI steps:
143
+
144
+ ```javascript
145
+ // Code step: normalize potentially-null enrichment data
146
+ return {
147
+ name: input.enrichData?.name ?? input.rawInput.companyName,
148
+ industry: input.enrichData?.industry ?? "Unknown",
149
+ employees: input.enrichData?.employees ?? null,
150
+ };
151
+ ```
152
+
153
+ ---
154
+
155
+ ## One-line rule
156
+
157
+ > Use `skip` for optional steps, `stop` for hard prerequisites, and `wait` for async dependencies — and always handle `null` outputs from skipped steps in downstream steps.
@@ -0,0 +1,130 @@
1
+ # 08 — Composed email with approval gate: outreach that ships safely
2
+
3
+ **Problem**: Agents build outreach by chaining a "draft email" AI step with a separate "gmail send" app action. This skips the user-review step, sends unreviewed drafts, and can't display the email in the pending-approval UI.
4
+
5
+ **Why it fails silently**: A raw draft-then-send pipeline has no concept of "email" as a first-class artifact. The orchestrator's approval UI won't render a preview, the `schedule-email` action can't fire, and there's no audit trail of what was sent to whom. The workflow looks correct at authoring time and may even work in testing — until a real recipient gets an unreviewed draft.
6
+
7
+ ---
8
+
9
+ ## The composed-email shape
10
+
11
+ An email step is a specialized `aiAction` with four coupled pieces:
12
+
13
+ 1. **Prompt type**: `pipelineStepPrompt.type: "email"` — tells the orchestrator this is an email step (not a generic AI step).
14
+ 2. **Renderer**: `renderer: { type: "Email", config: { fromContextKey: "outreachProfile" } }` — tells the UI to render the result as an email preview, pulling sender info from a context input page.
15
+ 3. **Integrations declaration**: declares that the workflow needs a connected email account (Gmail or Outlook).
16
+ 4. **Approval + send action**: `onApproval: { action: "schedule-email" }` + `next.conditions.approvalRequired: true` — the step waits for user approval, then the `schedule-email` action actually sends the email.
17
+
18
+ All four must be present. Omit any one and the step silently degrades: no preview, no approval gate, or no send.
19
+
20
+ ## Required input page: outreachProfile
21
+
22
+ Sender identity lives on a workflow-level `inputPages` entry with `contextKey: "outreachProfile"`. Without this page, the `{{context.outreachProfile.fromEmail}}` template resolves to empty and the email has no sender.
23
+
24
+ ```json
25
+ {
26
+ "title": "Outreach Profile",
27
+ "pathname": "outreach-profile",
28
+ "configuration": {
29
+ "contextKey": "outreachProfile",
30
+ "shortDescriptionFields": ["name", "fromEmail"],
31
+ "fields": [
32
+ { "name": "name", "label": "Sender name", "type": "text", "required": true },
33
+ { "name": "fromEmailLabel", "label": "From name", "type": "text", "required": true },
34
+ { "name": "fromEmail", "label": "From email", "type": "connected_emails_selector_multiple", "required": true },
35
+ { "name": "replyToEmail", "label": "Reply-to (optional)", "type": "text" }
36
+ ]
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Full composed-email step (copy-paste)
42
+
43
+ ```json
44
+ {
45
+ "id": "send-outreach-email",
46
+ "type": "aiAction",
47
+ "name": "Send Outreach Email",
48
+ "pipelineStepPrompt": {
49
+ "type": "email",
50
+ "template": "Draft a personalized email to {{steps.enrich.contact.fullName}} at {{steps.enrich.company.name}} introducing our offering. Keep it warm, concise, and specific to their role.",
51
+ "responseStructure": {
52
+ "email": {
53
+ "from": "{{context.outreachProfile.fromEmail}}",
54
+ "to": "{{steps.enrich.contact.email}}",
55
+ "subject": "",
56
+ "body": "",
57
+ "bodyType": "html"
58
+ }
59
+ }
60
+ },
61
+ "renderer": {
62
+ "type": "Email",
63
+ "config": { "fromContextKey": "outreachProfile" }
64
+ },
65
+ "integrations": [
66
+ {
67
+ "type": "oneOf",
68
+ "label": "Email",
69
+ "connectorType": "email",
70
+ "options": [
71
+ { "name": "Gmail", "url": "https://gmail.com", "isUserAccountConnectionRequired": true },
72
+ { "name": "Outlook", "url": "https://outlook.com", "isUserAccountConnectionRequired": true }
73
+ ],
74
+ "selectionHint": "preferConnected"
75
+ }
76
+ ],
77
+ "onApproval": {
78
+ "executedText": "Email sent by {{name}} at {{date}}",
79
+ "failedText": "Email failed to send.",
80
+ "action": "schedule-email"
81
+ },
82
+ "creditCost": 5,
83
+ "next": { "stepId": "done", "conditions": { "approvalRequired": true } }
84
+ }
85
+ ```
86
+
87
+ ## Anti-patterns
88
+
89
+ **Draft + Gmail send (no approval):**
90
+
91
+ ```
92
+ ❌ draft-email (aiAction) → gmail.send-email (appAction) → done
93
+ ```
94
+
95
+ No preview, no user approval, and the drafted body is written as a plain string instead of the structured `email` object — Gmail's send-email action expects specific fields (`to`, `subject`, `html`) that the draft prompt has no way to produce reliably.
96
+
97
+ **Missing `pipelineStepPrompt.type: "email"`:**
98
+
99
+ ```yaml
100
+ pipelineStepPrompt:
101
+ template: "..."
102
+ responseStructure:
103
+ email: { from, to, subject, body, bodyType }
104
+ # ❌ No `type: "email"` — the renderer falls back to a generic JSON view,
105
+ # and the orchestrator treats this as a regular aiAction (no schedule-email).
106
+ ```
107
+
108
+ **Email body as plain text when `bodyType: "html"`:**
109
+
110
+ Email clients render HTML bodies literally if the wrapping element is missing. Always generate email-safe HTML (`<p>`, `<br>`, `<a>`, `<strong>` — no CSS blocks, no `<script>`). Don't use markdown — the approval UI won't convert it.
111
+
112
+ **No `outreachProfile` input page:**
113
+
114
+ `{{context.outreachProfile.fromEmail}}` resolves to an empty string. The email has no sender. It may still send (from the default connected account) but you lose per-workflow sender control.
115
+
116
+ ## Checklist
117
+
118
+ Before wiring a composed-email step into a workflow:
119
+
120
+ - [ ] `context.inputPages[]` contains the `outreachProfile` page (pattern above)
121
+ - [ ] `pipelineStepPrompt.type: "email"` is set
122
+ - [ ] `renderer: { type: "Email", config: { fromContextKey: "outreachProfile" } }` is present
123
+ - [ ] `integrations[]` declares the email connector (Gmail / Outlook)
124
+ - [ ] `onApproval.action: "schedule-email"` is set
125
+ - [ ] `next.conditions.approvalRequired: true` on the outgoing edge
126
+ - [ ] Recipient and subject are derived from prior step output, not hardcoded
127
+
128
+ ## When to use a generic approval gate instead
129
+
130
+ For non-email approvals (sending a Slack post, firing a webhook), use a plain `aiAction` + `next.conditions.approvalRequired: true` without the email renderer. The approval gate is a general-purpose feature; the email shape is only required when the approved artifact is an email that needs to be sent through the user's connected mailbox.
@@ -0,0 +1,166 @@
1
+ # 09 — Reports, sharing, and knowledge-graph storage: closing the loop
2
+
3
+ **Problem**: Agents produce analysis as free-form prose in AI step output, then either never display it or drop it into a raw JSON view. Results aren't shareable with stakeholders, aren't comparable across runs, and don't feed back into the Knowledge Graph for trend analysis or future scoring calibration.
4
+
5
+ **Why it fails silently**: Without a `renderer`, an AI step's structured output is unreadable in the workspace UI. Without a `share` step, the output has no URL that can be sent to a stakeholder. Without a `knowledgeSync` step, each run's data is discarded — the second run can't learn from the first, and KPIs have no history to trend against.
6
+
7
+ ---
8
+
9
+ ## The three-part closing loop
10
+
11
+ ```
12
+ ... upstream steps → AI report step (with Config renderer)
13
+ → [optional] share step (public URL)
14
+ → [optional] composed email step (delivery)
15
+ → knowledgeSync (persist to KG for trending)
16
+ → milestone
17
+ ```
18
+
19
+ The three pieces are independent and optional, but they compound. A report with a renderer is readable. A report with a renderer + share step is forwardable. A report with all three + knowledgeSync is how KPI dashboards actually get built.
20
+
21
+ ## 1. Report AI step with Config renderer
22
+
23
+ The Config renderer turns structured AI output into a KPI dashboard view. Key blocks: `kpiRow`, `markdown`, `table`, `signalList`.
24
+
25
+ ```json
26
+ {
27
+ "id": "generate-report",
28
+ "type": "aiAction",
29
+ "name": "Generate Report",
30
+ "pipelineStepPrompt": {
31
+ "template": "Analyze the data and produce a structured report.\n\nData: {{steps.evaluate.items}}",
32
+ "responseStructure": {
33
+ "summary": "string — executive summary",
34
+ "kpis": "object { total: number, qualified: number, avgScore: number }",
35
+ "items": "array of { name, score, decision }",
36
+ "insights": "array of strings"
37
+ }
38
+ },
39
+ "renderer": {
40
+ "type": "Config",
41
+ "config": {
42
+ "layout": {
43
+ "title": "Run Report",
44
+ "blocks": [
45
+ { "blockType": "kpiRow", "kpis": [
46
+ { "label": "Total", "valuePath": "kpis.total", "icon": "Hash" },
47
+ { "label": "Qualified", "valuePath": "kpis.qualified", "icon": "CheckCircle" },
48
+ { "label": "Avg Score", "valuePath": "kpis.avgScore", "format": "score" }
49
+ ]},
50
+ { "blockType": "markdown", "contentPath": "summary" },
51
+ { "blockType": "table", "arrayPath": "items", "searchable": true, "columns": [
52
+ { "header": "Name", "field": "name" },
53
+ { "header": "Score", "field": "score", "display": "score", "sortable": true },
54
+ { "header": "Decision", "field": "decision", "display": "badge" }
55
+ ]},
56
+ { "blockType": "signalList", "title": "Insights", "arrayPath": "insights", "variant": "signal" }
57
+ ]
58
+ }
59
+ }
60
+ },
61
+ "creditCost": 10,
62
+ "next": { "stepId": "share-report" }
63
+ }
64
+ ```
65
+
66
+ **Rule**: the `responseStructure` keys must match the renderer's `valuePath`/`arrayPath`/`contentPath` references. Drift between the two silently produces empty KPI tiles and blank tables.
67
+
68
+ ## 2. Share step for a public URL
69
+
70
+ ```json
71
+ {
72
+ "id": "share-report",
73
+ "type": "share",
74
+ "name": "Create Public Report Link",
75
+ "shareConfig": {
76
+ "outputSteps": ["generate-report"],
77
+ "expiresInDays": 30,
78
+ "visibility": "public"
79
+ },
80
+ "next": { "stepId": "send-report-email" }
81
+ }
82
+ ```
83
+
84
+ Outputs `{ shareId, shareUrl, expiresAt }`. Downstream steps use `{{steps.share-report.shareUrl}}` to reference the public URL (e.g., in an email body).
85
+
86
+ **When to add a share step**:
87
+
88
+ - Anyone outside the workspace needs to see the report.
89
+ - The report should remain accessible after the execution's detail page expires.
90
+ - The workflow delivers the report via email and the body should link to the full report.
91
+
92
+ **When to skip it**: internal-only dashboards where viewers already have workspace access.
93
+
94
+ ## 3. Knowledge-graph storage for trending
95
+
96
+ Without this, each run's data vanishes. With it, you can:
97
+
98
+ - Compare "today's MRR" against last month's.
99
+ - Calibrate future AI scoring on past outcomes (see `kg.retrieve-scoring-memory`).
100
+ - Build a timeline view of any metric.
101
+
102
+ ```json
103
+ {
104
+ "id": "store-history",
105
+ "type": "knowledgeSync",
106
+ "name": "Store to KPI History",
107
+ "knowledgeSync": {
108
+ "source": { "stepId": "generate-report" },
109
+ "listKey": "kpi_history",
110
+ "fieldMapping": {
111
+ "mrr": "mrr",
112
+ "burn": "burn",
113
+ "runway_months": "runway_months",
114
+ "executedAt": "executedAt"
115
+ }
116
+ },
117
+ "next": { "stepId": "done" }
118
+ }
119
+ ```
120
+
121
+ If you're inside a loop (scoring many items per run), set `source.resultsPath: "items"` so each loop iteration produces one KG row.
122
+
123
+ ## Anti-patterns
124
+
125
+ **AI step with no renderer:**
126
+
127
+ ```yaml
128
+ type: aiAction
129
+ responseStructure:
130
+ summary: string
131
+ kpis: { total, avgScore }
132
+ # ❌ No renderer — the workspace UI shows raw JSON. Nobody reads it.
133
+ ```
134
+
135
+ **Share step without `outputSteps`:**
136
+
137
+ ```yaml
138
+ type: share
139
+ shareConfig:
140
+ visibility: public
141
+ # ❌ Missing outputSteps — share URL renders nothing
142
+ ```
143
+
144
+ **`knowledgeSync` without a clear schema:**
145
+
146
+ If `listKey` doesn't already exist, the KG creates an implicit schema from the first write. Subsequent writes with different field shapes fail to index. For trending, pre-create the list with a typed schema.
147
+
148
+ **Reusing `fieldMapping` values with source-side renames:**
149
+
150
+ `fieldMapping` keys are source field names (from the prior step's output), values are target field names (in the KG). Flipping them silently stores the wrong data.
151
+
152
+ ```yaml
153
+ # ✅ Correct: source → target
154
+ fieldMapping:
155
+ mrr: monthly_revenue # steps.extract.mrr → row.monthly_revenue
156
+ burn: monthly_expenses
157
+ ```
158
+
159
+ ## Checklist for any "report" workflow
160
+
161
+ - [ ] `responseStructure` keys match every renderer `valuePath` / `arrayPath` / `contentPath`
162
+ - [ ] Renderer `blocks` cover at least one KPI + one tabular view
163
+ - [ ] Share step exists if report is forwardable to non-workspace users
164
+ - [ ] If delivered via email, the email template embeds `{{steps.share.shareUrl}}`
165
+ - [ ] `knowledgeSync` persists to a list with a typed schema (not implicit)
166
+ - [ ] `executedAt` (or a similar timestamp) is stored so rows can be trended
@@ -0,0 +1,62 @@
1
+ # CLI scaffolds
2
+
3
+ Each `*.json` file in this directory is a **preflight-clean pipeline skeleton**
4
+ keyed to one of the patterns in `agentic-ops/patterns/v1/` (canonical) or
5
+ `packages/cli/patterns/v1/` (byte-identical mirror).
6
+
7
+ Scaffolds are **pattern shapes, not domain templates.** The goal is to give
8
+ an agent (or human) a known-good starting point for a structural shape —
9
+ loop over a KG list, composed email with approval, conditional alert on
10
+ threshold, etc. — not to ship templates for every possible business use case.
11
+
12
+ ## Contract
13
+
14
+ Every bundled scaffold:
15
+
16
+ 1. Passes `agentled workflows validate --file <scaffold>` with zero errors
17
+ and zero warnings.
18
+ 2. Has a clear `name` and `goal` field that describes the pattern shape.
19
+ 3. References the matching agentic-ops pattern number(s) in its `description`.
20
+ 4. Uses domain-agnostic placeholder names (`candidate`, `metric_a`,
21
+ `entity_id`) — not specific verticals.
22
+
23
+ ## Catalog
24
+
25
+ | Slug | Pattern(s) | Shape |
26
+ |------|-----------|-------|
27
+ | `minimal` | — | trigger → milestone (smallest valid pipeline) |
28
+ | `email-polling-dedup` | 02 + 13 | schedule → fetch emails (label dedup) → loop process → add label |
29
+ | `lead-scoring-kg` | 04 + 09 | trigger → kg.read-list → AI scoring loop → knowledgeSync → report |
30
+ | `list-match-email` | 08 | trigger → kg.read-list → AI match top candidates → composed email (approval gate) → knowledgeSync |
31
+ | `extract-threshold-alert` | 06 + 09 | trigger → AI extract → threshold check (code) → external update → conditional Slack alert → knowledgeSync |
32
+ | `ai-with-tools` | — | trigger → `aiActionWithTools` (web_search + workspace_memory) → milestone |
33
+
34
+ ## Bring your own scaffolds
35
+
36
+ The bundled set is deliberately small. Workspaces, teams, or customers who
37
+ have recurring workflow shapes should maintain their own scaffold library
38
+ outside this CLI release cycle:
39
+
40
+ 1. Drop JSON files in `~/.agentled/scaffolds/` or set
41
+ `AGENTLED_SCAFFOLDS_DIR=/path/to/your/scaffolds`.
42
+ 2. Run `agentled workflows scaffold --list` — local scaffolds appear with
43
+ a `[local]` tag next to their name.
44
+ 3. Local slugs **shadow bundled ones** with the same name, so a team can
45
+ override `list-match-email.json` with a locally-tuned version without
46
+ forking the CLI.
47
+
48
+ Any JSON in those directories must also pass `workflows validate --file`.
49
+ The CLI doesn't gate this at load time — but a scaffold that fails preflight
50
+ will waste an operator's time, which is exactly what the scaffold set is
51
+ meant to prevent.
52
+
53
+ ## Editing the bundled set
54
+
55
+ The bundled scaffolds are meant to stay small and pattern-focused. If you
56
+ want to propose a new one:
57
+
58
+ - It must map to an existing agentic-ops pattern (or come with a new pattern).
59
+ - It must be domain-agnostic — name fields `candidate` / `metric_a` /
60
+ `entity_id`, not `mentor` / `mrr` / `companyId`.
61
+ - The `description` must state the pattern number it demonstrates.
62
+ - Commit both the scaffold and a preflight test verifying it passes.
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "AI with Runtime Tools",
3
+ "goal": "AI step that can call web_search + workspace_memory at runtime (aiActionWithTools shape).",
4
+ "description": "Starting shape for an AI step that needs to pull fresh web context and recall/store workspace memory. Replace the prompt and response structure for your task.",
5
+ "status": "draft",
6
+ "context": {
7
+ "executionInputConfig": {
8
+ "title": "Research + Recall",
9
+ "description": "Provide a topic or entity to research.",
10
+ "runCTALabel": "Run",
11
+ "fields": [
12
+ { "name": "topic", "label": "Topic or entity", "type": "text", "required": true }
13
+ ]
14
+ }
15
+ },
16
+ "steps": [
17
+ {
18
+ "id": "start",
19
+ "type": "trigger",
20
+ "name": "Manual Start",
21
+ "pipelineStepStartConditions": { "trigger": { "type": "manual" } },
22
+ "next": { "stepId": "analyze" }
23
+ },
24
+ {
25
+ "id": "analyze",
26
+ "type": "aiActionWithTools",
27
+ "name": "Analyze with Tools",
28
+ "tools": [
29
+ { "type": "builtin", "name": "web_search", "builtinType": "web_search" },
30
+ { "type": "builtin", "name": "workspace_memory", "builtinType": "workspace_memory" }
31
+ ],
32
+ "pipelineStepPrompt": {
33
+ "template": "Research the topic: {{input.topic}}.\n\nUse `web_search` for fresh external context. Use `workspace_memory` to recall what we already know about {{input.topic}} (call action \"search\" with a relevant query) and to `store` any durable fact you learn (category=fact, confidence 70-100).\n\nReturn a concise structured summary.",
34
+ "responseStructure": {
35
+ "summary": "string — 3-5 sentences synthesising research + prior memory",
36
+ "sources": "array of strings — URLs cited from web_search",
37
+ "stored_memories": "array of strings — keys of new memories written (if any)"
38
+ }
39
+ },
40
+ "creditCost": 10,
41
+ "next": { "stepId": "done" }
42
+ },
43
+ {
44
+ "id": "done",
45
+ "type": "milestone",
46
+ "name": "Done"
47
+ }
48
+ ]
49
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "Email Polling + Dedup",
3
+ "goal": "Poll Gmail for new emails, process each, and mark them with a label so they're never reprocessed.",
4
+ "description": "Pattern 02 (dedup-gates). Default intake pattern — prefer this over event triggers unless you need sub-minute latency.",
5
+ "status": "draft",
6
+ "steps": [
7
+ {
8
+ "id": "schedule",
9
+ "type": "trigger",
10
+ "name": "Daily poll",
11
+ "pipelineStepStartConditions": {
12
+ "trigger": {
13
+ "type": "schedule",
14
+ "config": { "frequency": "daily", "time": "07:00" }
15
+ }
16
+ },
17
+ "next": { "stepId": "ensure-label" }
18
+ },
19
+ {
20
+ "id": "ensure-label",
21
+ "type": "appAction",
22
+ "name": "Ensure Processed Label",
23
+ "app": { "id": "gmail", "actionId": "GMAIL_CREATE_LABEL", "source": "composio" },
24
+ "stepInputData": { "name": "processed" },
25
+ "next": { "stepId": "fetch-emails" }
26
+ },
27
+ {
28
+ "id": "fetch-emails",
29
+ "type": "appAction",
30
+ "name": "Fetch New Emails",
31
+ "app": { "id": "gmail", "actionId": "GMAIL_FETCH_EMAILS", "source": "composio" },
32
+ "stepInputData": {
33
+ "query": "-label:processed newer_than:1d",
34
+ "max_results": "50"
35
+ },
36
+ "next": { "stepId": "process" }
37
+ },
38
+ {
39
+ "id": "process",
40
+ "type": "aiAction",
41
+ "name": "Process Email",
42
+ "loopConfig": {
43
+ "source": "executionContent",
44
+ "config": { "stepId": "fetch-emails", "field": "messages" },
45
+ "ItemAlias": "emailItem"
46
+ },
47
+ "pipelineStepPrompt": {
48
+ "template": "Process this email:\n\nFrom: {{emailItem.from}}\nSubject: {{emailItem.subject}}\n\n{{emailItem.body}}",
49
+ "responseStructure": { "summary": "string", "action": "string" }
50
+ },
51
+ "creditCost": 8,
52
+ "next": { "stepId": "mark-processed" }
53
+ },
54
+ {
55
+ "id": "mark-processed",
56
+ "type": "appAction",
57
+ "name": "Mark as Processed",
58
+ "app": { "id": "gmail", "actionId": "GMAIL_ADD_LABEL", "source": "composio" },
59
+ "stepInputData": {
60
+ "message_id": "{{emailItem.id}}",
61
+ "label_id": "{{steps.ensure-label.id}}"
62
+ },
63
+ "next": { "stepId": "done" }
64
+ },
65
+ {
66
+ "id": "done",
67
+ "type": "milestone",
68
+ "name": "Done"
69
+ }
70
+ ]
71
+ }