@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.
- package/README.md +136 -0
- package/dist/builtin-tools-catalog.d.ts +37 -0
- package/dist/builtin-tools-catalog.js +96 -0
- package/dist/builtin-tools-catalog.js.map +1 -0
- package/dist/commands/auth.js +30 -0
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/examples.d.ts +15 -0
- package/dist/commands/examples.js +100 -0
- package/dist/commands/examples.js.map +1 -0
- package/dist/commands/scaffold.d.ts +14 -0
- package/dist/commands/scaffold.js +103 -0
- package/dist/commands/scaffold.js.map +1 -0
- package/dist/commands/schema.d.ts +10 -0
- package/dist/commands/schema.js +107 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.js +94 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/tools.d.ts +10 -0
- package/dist/commands/tools.js +53 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/workflows.js +227 -9
- package/dist/commands/workflows.js.map +1 -1
- package/dist/context-schema.d.ts +37 -0
- package/dist/context-schema.js +108 -0
- package/dist/context-schema.js.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/preflight.d.ts +25 -0
- package/dist/utils/preflight.js +293 -0
- package/dist/utils/preflight.js.map +1 -0
- package/dist/utils/skills.d.ts +49 -0
- package/dist/utils/skills.js +214 -0
- package/dist/utils/skills.js.map +1 -0
- package/package.json +4 -1
- package/patterns/v1/00-why-agentic-ops.md +107 -0
- package/patterns/v1/01-trigger-design.md +107 -0
- package/patterns/v1/02-dedup-gates.md +135 -0
- package/patterns/v1/03-credit-efficiency.md +130 -0
- package/patterns/v1/04-loop-patterns.md +147 -0
- package/patterns/v1/05-child-workflow-contracts.md +151 -0
- package/patterns/v1/06-conditional-routing.md +151 -0
- package/patterns/v1/07-error-handling.md +157 -0
- package/patterns/v1/08-composed-email-approval.md +130 -0
- package/patterns/v1/09-reports-and-knowledge-storage.md +166 -0
- package/scaffolds/README.md +62 -0
- package/scaffolds/ai-with-tools.json +49 -0
- package/scaffolds/email-polling-dedup.json +71 -0
- package/scaffolds/extract-threshold-alert.json +131 -0
- package/scaffolds/lead-scoring-kg.json +84 -0
- package/scaffolds/list-match-email.json +131 -0
- package/scaffolds/minimal.json +20 -0
- 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
|
+
}
|