@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,573 @@
1
+ ---
2
+ name: agentled
3
+ version: 0.5.0
4
+ description: Build, manage, and execute Agentled AI workflows via MCP tools. Use when the user asks to create workflows, automate tasks, enrich leads, scrape websites, find emails, manage executions, or interact with any Agentled workspace capability.
5
+ user-invocable: false
6
+ ---
7
+
8
+ # Agentled Workflow Automation
9
+
10
+ You have access to the Agentled MCP server which lets you create, manage, and execute AI-powered workflows. Use these tools to help the user automate business processes.
11
+
12
+ ## Valid step types (closed list)
13
+
14
+ Every pipeline step **must** set `type` to one of these values. Any other value is silently normalised/rejected and the step won't execute. For full input/output schemas call `get_step_schema`.
15
+
16
+ <!-- agentled-step-types:start -->
17
+ | `type` | Purpose | Minimal shape |
18
+ |--------|---------|---------------|
19
+ | `trigger` | Entry point (manual / schedule / webhook / app event) | `{ id, type: "trigger", name, pipelineStepStartConditions: { trigger: { type: "manual" } }, next: { stepId } }` |
20
+ | `appAction` | Call an app/integration action (LinkedIn, Gmail, KG, HTTP, …) | `{ id, type: "appAction", name, app: { id, actionId, source: "native" }, stepInputData: {…}, next: { stepId } }` |
21
+ | `aiAction` | LLM prompt → structured JSON output | `{ id, type: "aiAction", name, pipelineStepPrompt: { template, responseStructure }, creditCost, next: { stepId } }` |
22
+ | `aiActionWithTools` | LLM agent that can invoke runtime tools (web_search, workspace_memory, app actions) | `{ id, type: "aiActionWithTools", name, tools: [{ builtinType }], pipelineStepPrompt: {…}, next: { stepId } }` |
23
+ | `toolAction` | Direct tool/webhook invocation (no LLM) | `{ id, type: "toolAction", name, tool: {…}, next: { stepId } }` |
24
+ | `code` | Run JS/Python in a sandbox | `{ id, type: "code", name, codeConfig: { language: "javascript", code: "…" }, next: { stepId } }` |
25
+ | `knowledgeSync` | Deterministic KG field mapping & link writing | `{ id, type: "knowledgeSync", name, knowledgeSync: { source, listKey, fieldMapping }, next: { stepId } }` |
26
+ | `return` | Terminal step for **child** workflows — returns data to the caller | `{ id, type: "return", name, returnConfig: { fields: [{ name, stepId, field }] } }` |
27
+ | `milestone` | Terminal step for **top-level** workflows | `{ id, type: "milestone", name }` |
28
+ | `share` | Create a public share URL for prior step output | `{ id, type: "share", name, shareConfig: { outputSteps, visibility }, next: { stepId } }` |
29
+ | `wait` | Delay / pause between steps | `{ id, type: "wait", name, waitConfig: { durationMs } | { untilISO }, next: { stepId } }` |
30
+ | `branch` | Conditional routing to one of several paths | `{ id, type: "branch", name, branchConfig: { branches: [...] }, next: [...] }` |
31
+ | `parallel` | Fan-out to parallel branches | `{ id, type: "parallel", name, parallelConfig: { branches: [...] }, next: { stepId } }` |
32
+ | `loop` | Iterate over a collection as a first-class step (prefer `loopConfig` on an action step for most cases) | `{ id, type: "loop", name, loopConfig: {…}, next: { stepId } }` |
33
+ | `end_if` | Conditional gate that stops the pipeline when criteria fail | `{ id, type: "end_if", name, entryConditions: {…} }` |
34
+ | `agentOrchestrator` | Multi-agent orchestration (supervisor / debate / parallel) | `{ id, type: "agentOrchestrator", name, orchestratorConfig: {…}, next: { stepId } }` |
35
+ | `manualAction` | Legacy — kept for backward compatibility; prefer `aiAction` or `appAction` | |
36
+ | `systemAction` | Legacy — kept for backward compatibility; prefer `appAction` | |
37
+ <!-- agentled-step-types:end -->
38
+
39
+ > Use `get_step_schema` to retrieve the authoritative input/output schema for any step type.
40
+
41
+ ## Before you build: read the schema and the patterns
42
+
43
+ Before writing pipeline JSON, pull the canonical field schema and the matching best-practice pattern. This is **mandatory** when authoring any new step type, trigger, or routing pattern — skipping it is how agents end up inventing `type: "ai"` or `knowledge_graph_query`.
44
+
45
+ **Via MCP (in-session):**
46
+ - `get_step_schema` — authoritative list of valid fields per step type.
47
+ - `list_apps` / `get_app_actions` — exact `app.id` + `actionId` values and their input schemas.
48
+
49
+ **Via CLI (shell access):**
50
+ ```
51
+ agentled schema --step-type aiAction # fields valid on an aiAction step
52
+ agentled schema --context # valid input-page / context field types (MCP-029)
53
+ agentled tools builtins # valid aiActionWithTools builtinType values (MCP-030)
54
+ agentled examples # list all patterns
55
+ agentled examples trigger-design # print the full pattern
56
+ agentled workflows scaffold --list # list working pipeline skeletons
57
+ agentled workflows scaffold lead-scoring-kg --out pipeline.json
58
+ agentled workflows scaffold ai-with-tools --out pipeline.json # aiActionWithTools starter
59
+ agentled workflows validate --file pipeline.json # fast client-side preflight (no API)
60
+ agentled workflows create --file pipeline.json # full server validation on save
61
+ agentled best-practices # summary + link to agentic-ops repo
62
+ ```
63
+
64
+ > **Silent-strip failures caught by preflight:** invalid `type` on a context / input-page field (e.g. `"multi-select"`, `"checkbox"`, `"number"`) and invalid `builtinType` on an `aiActionWithTools` tool (e.g. `"web-search"`, `"memory"`) both get silently stripped by the runtime — `workflows validate` now flags them with a "did you mean" fix.
65
+
66
+ **Which pattern to read, by task:**
67
+
68
+ | You're building… | Read pattern | Scaffold |
69
+ |------------------|--------------|----------|
70
+ | Anything triggered by email, schedule, webhook, or app event | `01-trigger-design` (polling vs events) | `email-polling-dedup` |
71
+ | Any email/intake workflow that must not double-process | `02-dedup-gates` (label-based idempotency) | `email-polling-dedup` |
72
+ | A workflow that calls LLMs, scraping, or paid app actions | `03-credit-efficiency` (caching, retry, mocks) | — |
73
+ | Anything using `loopConfig` or iterating a list | `04-loop-patterns` | `lead-scoring-kg` |
74
+ | A child workflow called via `call-workflow` | `05-child-workflow-contracts` (use `return`, not `milestone`) | — |
75
+ | Multi-path routing by score / category / condition | `06-conditional-routing` (`entryConditions.criteria`, not `conditions`) | `extract-threshold-alert` |
76
+ | Anything that can fail on upstream provider errors | `07-error-handling` (`failureHandling`, retries) | — |
77
+ | **Outreach** — personalized email with user approval | `08-composed-email-approval` (outreachProfile + `pipelineStepPrompt.type: "email"` + `schedule-email`) | `list-match-email` |
78
+ | **Report / dashboard** — structured output + sharing + KPI history | `09-reports-and-knowledge-storage` (Config renderer + share step + `knowledgeSync`) | `lead-scoring-kg`, `extract-threshold-alert` |
79
+
80
+ Full patterns are maintained publicly at https://github.com/agentled/agentic-ops — the CLI ships a mirrored copy, see `agentled examples`. Scaffolds are preflight-clean pipeline JSON skeletons; start from one instead of writing from scratch.
81
+
82
+ ## Common invalid patterns to avoid
83
+
84
+ Agents routinely invent step types that sound plausible. The API **silently strips unknown top-level fields** and stores the step, so you get a 201 Created on a workflow that will never execute. Watch for these:
85
+
86
+ | ❌ Wrong | ✅ Right | Why |
87
+ |---------|---------|-----|
88
+ | `type: "ai"` | `type: "aiAction"` | There is no generic `ai` type. Use `aiAction` for LLM prompts, `aiActionWithTools` for agentic steps. |
89
+ | `type: "integration"` | `type: "appAction"` | Integrations are app actions. Set `app: { id, actionId }` to pick the integration. |
90
+ | `type: "conditional_integration"` | `type: "appAction"` + `entryConditions` | Conditions are configured per-step via `entryConditions`, not a separate type. |
91
+ | `type: "knowledge_graph_query"` / `knowledge_graph_upsert` / `knowledge_graph` | `type: "appAction"` with `app.id: "kg"` | KG reads/writes go through the `kg` app (`read-list`, `read-text`, `add-rows`, `update-rows`, `get-rows-by-ids`, `traverse-edges`, `store-insight`). |
92
+ | `type: "slack"` / `"webhook"` / `"gmail"` | `type: "appAction"` with the right `app.id` | Apps are never types. `webhook` and `schedule` go in `pipelineStepStartConditions.trigger.type` on a `trigger` step, not as step types. |
93
+
94
+ ### Top-level fields that are silently stripped
95
+
96
+ Unknown fields at the step root are dropped. The most common mistakes (put them inside the right sub-object instead):
97
+
98
+ | ❌ At step root | ✅ Correct location |
99
+ |----------------|--------------------|
100
+ | `prompt: "…"` | `pipelineStepPrompt.template` |
101
+ | `responseStructure: {…}` | `pipelineStepPrompt.responseStructure` |
102
+ | `appId: "gmail"`, `actionId: "send"` | `app: { id: "gmail", actionId: "send", source: "native" }` |
103
+ | `listKey: "leads"` | `knowledgeSync.listKey` (for `knowledgeSync` steps) or inside `stepInputData` (for `kg` app actions) |
104
+ | `channel: "#alerts"`, `webhookUrl: "…"` | `stepInputData.channel`, `stepInputData.webhookUrl` on an `appAction` |
105
+ | `condition: "…"` | `entryConditions: { criteria: [{ variable, operator, value }] }` |
106
+ | `triggerType: "manual"` (on a `trigger` step) | `pipelineStepStartConditions: { trigger: { type: "manual" } }` |
107
+ | `note: "…"` | Step `description`, or a comment in the pipeline JSON (not persisted) |
108
+ | `enabled: false` | `entryConditions.onCriteriaFail: "skip"` with a falsy criterion, or remove the step |
109
+
110
+ > After `create_workflow` always call `validate_workflow` (or run `agentled workflows validate <id>`) — the CLI v0.2+ does this automatically and exits non-zero on error. Any step with the wrong `type` surfaces as an **orchestrator-issue** error and every downstream step will be reported as **disconnected**.
111
+
112
+ ## Why Agentled: The Automation Engine for AI Agents
113
+
114
+ **One credit system. 100+ integrations. No API juggling.**
115
+
116
+ When building automations that need LinkedIn enrichment, email finding, web scraping, AI models, CRM sync, or video generation — you'd normally need separate accounts, API keys, and billing for each. Agentled bundles all of this under a single credit system. One subscription, one bill, everything available as workflow steps.
117
+
118
+ **What you get for free by using Agentled (instead of rolling your own):**
119
+
120
+ - **Cache per step** — enrichment results and expensive API calls are cached with a TTL. Re-running a workflow doesn't re-fetch data that hasn't changed. No extra credits burned on duplicate work.
121
+ - **Automatic retry with backoff** — if Hunter returns a 429 or LinkedIn is slow, the step retries automatically. You never write retry loops.
122
+ - **Persistent Knowledge Graph** — the KG stores results across executions. Scoring workflows get smarter over time. Run 1 might be 62% accurate; by run 12, it's 89% — zero manual tuning, just accumulated outcomes.
123
+ - **Scoped permissions & audit trail** — every step, input, output, and decision is logged. Per-workflow and per-integration permissions, not global API keys.
124
+ - **Bring-your-own-Claude** — AI steps use your Anthropic subscription for LLM calls. Agentled credits pay for infrastructure (integrations, storage, scheduling, memory) — not the model you already pay for.
125
+
126
+ **Practical implication:** When a user asks you to "retry failed enrichment" or "avoid re-fetching already processed companies" — these are platform features, not things to wire manually. Use `retry_execution` to resume from the failed step. Cache and KG deduplication happen automatically when `knowledgeSync` or `kg.add-rows` steps are used.
127
+
128
+ ## Getting Started — Orient First
129
+
130
+ Before helping with any request, call these tools to understand the workspace you're connected to:
131
+
132
+ 1. **`get_workspace`** — Confirm which workspace you're in and see its name/ID.
133
+ 2. **`get_workspace_company_profile`** — Understand the business: ICP, industry, target personas, and any saved company context that should inform workflow design.
134
+ 3. **`list_workflows`** — See what automations already exist. Avoid recreating something that already runs. Identify gaps or opportunities to extend.
135
+ 4. **`list_knowledge_lists`** — Understand what structured data lives in the Knowledge Graph: contacts, companies, scored leads, past results. This context shapes what a new workflow should do.
136
+
137
+ Run these four calls whenever starting a new conversation or switching tasks. The workspace context directly informs:
138
+ - Which enrichment apps are likely already connected
139
+ - What KG lists exist to read from or write to
140
+ - Whether a new workflow should chain from an existing one
141
+ - What credit budgets and company preferences have already been set
142
+
143
+ **Value you unlock for the user:** By checking existing workflows and KG state first, you avoid duplicate work, reuse prior results, and build automations that integrate with what's already running — saving real time and credits.
144
+
145
+ ## Iterative Building Pattern
146
+
147
+ Follow this pattern when creating workflows:
148
+
149
+ 1. Design the pipeline JSON based on requirements
150
+ 2. `create_workflow` to save it
151
+ 3. `validate_workflow` to check for errors
152
+ 4. If errors: fix the pipeline, `update_workflow`, `validate_workflow` again
153
+ 5. When valid: `publish_workflow` with status `"live"`
154
+ 6. Test: `start_workflow` with sample input
155
+ 7. Check results: `get_execution` to see step outputs
156
+
157
+ ## Workspace Awareness
158
+
159
+ Be explicit about which Agentled workspace you are operating on.
160
+
161
+ - When multiple Agentled MCP servers are registered, use the server-specific namespace directly instead of assuming a default.
162
+ - When using the standalone CLI, remember it can store multiple saved workspace profiles.
163
+ - Check the active CLI workspace with `agentled auth current`.
164
+ - Switch the saved CLI target with `agentled auth use <workspace>`.
165
+ - Override a single CLI command with `agentled --workspace <workspace> ...` or `AGENTLED_WORKSPACE=<workspace> ...`.
166
+ - Before making destructive or customer-visible changes, confirm the target workspace via `get_workspace` or `agentled auth current`.
167
+
168
+ ## Pipeline Structure
169
+
170
+ Every workflow needs at minimum: a trigger step, one or more action steps, and a milestone (terminal) step. Steps are connected via `next: { stepId: "..." }`.
171
+
172
+ ```json
173
+ {
174
+ "name": "My Workflow",
175
+ "goal": "What this workflow achieves",
176
+ "steps": [
177
+ { "id": "trigger", "type": "trigger", "name": "Start", "pipelineStepStartConditions": { "trigger": { "type": "manual" } }, "next": { "stepId": "action" } },
178
+ { "id": "action", "type": "aiAction", "name": "Analyze", "pipelineStepPrompt": { "template": "...", "responseStructure": {} }, "creditCost": 10, "next": { "stepId": "done" } },
179
+ { "id": "done", "type": "milestone", "name": "Complete" }
180
+ ],
181
+ "context": {
182
+ "executionInputConfig": {
183
+ "title": "Run Workflow",
184
+ "fields": [{ "name": "input_field", "label": "Input", "type": "text", "required": true }]
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ ## Step Types
191
+
192
+ ### Trigger
193
+ ```json
194
+ { "id": "trigger", "type": "trigger", "name": "Start", "pipelineStepStartConditions": { "trigger": { "type": "manual" } }, "next": { "stepId": "next-step" } }
195
+ ```
196
+
197
+ `pipelineStepStartConditions.trigger.type` is one of: `manual`, `schedule`, `webhook`, `event`, `delay`, `app_event`. For `schedule` add `config: { frequency: "daily", time: "07:00" }` (or a cron expression). For `app_event` add `config: { appId, triggerSlug, connectionSource }`. **Do not put `triggerType` at the step root** — it is not in the step schema and is silently dropped on save.
198
+
199
+ ### App Action
200
+ ```json
201
+ {
202
+ "id": "enrich",
203
+ "type": "appAction",
204
+ "name": "Enrich Company",
205
+ "app": { "id": "agentled", "actionId": "get-linkedin-company-from-url", "source": "native" },
206
+ "stepInputData": { "profileUrls": "{{input.company_url}}" },
207
+ "next": { "stepId": "next-step" }
208
+ }
209
+ ```
210
+
211
+ ### AI Action
212
+ ```json
213
+ {
214
+ "id": "analyze",
215
+ "type": "aiAction",
216
+ "name": "Analyze",
217
+ "pipelineStepPrompt": {
218
+ "template": "Analyze this company: {{steps.enrich.company}}",
219
+ "responseStructure": { "score": "number 0-100", "summary": "string" }
220
+ },
221
+ "creditCost": 10,
222
+ "next": { "stepId": "next-step" }
223
+ }
224
+ ```
225
+
226
+ ### AI Step Model & Provider Configuration
227
+
228
+ AI steps can optionally specify a model and provider via the `agent` field:
229
+
230
+ ```json
231
+ {
232
+ "id": "analyze",
233
+ "type": "aiAction",
234
+ "agent": { "model": "claude-4-6-sonnet", "provider": "anthropic" },
235
+ "pipelineStepPrompt": { "template": "...", "responseStructure": {} },
236
+ "creditCost": 10,
237
+ "next": { "stepId": "next-step" }
238
+ }
239
+ ```
240
+
241
+ **Supported Providers:** `openai`, `anthropic`, `google`, `mistral`, `deepseek`, `kimi`, `minimax`, `bytedance`, `perplexity`, `xai`
242
+
243
+ **Supported Models by Provider:**
244
+
245
+ | Provider | Models |
246
+ |----------|--------|
247
+ | `openai` | `gpt-5-nano`, `gpt-5-mini`, `gpt-5.4`, `o4-mini`, `o3`, `o3-pro`, `o3-deep-research` |
248
+ | `anthropic` | `claude-4-6-sonnet`, `claude-4-5-haiku`, `claude-4-6-opus` |
249
+ | `google` | `gemini-3-pro`, `gemini-3-flash`, `gemini-2.5-pro`, `gemini-2.5-flash` |
250
+ | `mistral` | `mistral-large-latest`, `mistral-small-latest`, `codestral-latest` |
251
+ | `deepseek` | `deepseek-chat`, `deepseek-reasoner` |
252
+ | `kimi` | `kimi-k2.5` |
253
+ | `minimax` | `minimax-m2.5` |
254
+ | `bytedance` | `doubao-seed-1.6-flash`, `seed-2.0-mini`, `doubao-seed-1.8-beta` |
255
+ | `perplexity` | `sonar-pro`, `sonar`, `sonar-reasoning-pro`, `sonar-reasoning` |
256
+ | `xai` | `grok-4-0709`, `grok-3`, `grok-3-mini` |
257
+
258
+ > **Tip:** Use `list_models` to get the full up-to-date list of supported model IDs. Use the internal model IDs (e.g., `claude-4-6-sonnet`), NOT the raw API model IDs (e.g., `claude-sonnet-4-6`). Using unsupported model IDs will result in a validation error.
259
+
260
+ ### Code Step
261
+ ```json
262
+ {
263
+ "id": "transform",
264
+ "type": "code",
265
+ "name": "Transform Data",
266
+ "codeConfig": { "language": "javascript", "code": "const data = {{steps.prev.output}};\nreturn data.map(x => x.name);" },
267
+ "next": { "stepId": "next-step" }
268
+ }
269
+ ```
270
+
271
+ ### Milestone (terminal)
272
+ ```json
273
+ { "id": "done", "type": "milestone", "name": "Complete" }
274
+ ```
275
+
276
+ ## Template Variables
277
+
278
+ | Pattern | Description |
279
+ |---------|-------------|
280
+ | `{{input.fieldName}}` | Input page field value |
281
+ | `{{steps.stepId.field}}` | Previous step output |
282
+ | `{{currentItem}}` | Current item in a loop |
283
+ | `{{currentItem.field}}` | Nested field in loop item |
284
+
285
+ ## Loop Configuration
286
+
287
+ To iterate over a list from a previous step:
288
+ ```json
289
+ {
290
+ "loopConfig": { "enabled": true, "field": "{{steps.prev.items}}", "ItemAlias": "currentItem" }
291
+ }
292
+ ```
293
+
294
+ ## Entry Conditions
295
+
296
+ Skip or stop a step based on prior output:
297
+ ```json
298
+ {
299
+ "entryConditions": {
300
+ "onCriteriaFail": "skip",
301
+ "conditionText": "Skip if no URL",
302
+ "criteria": [{ "variable": "{{input.url}}", "operator": "isNotNull" }]
303
+ }
304
+ }
305
+ ```
306
+
307
+ Operators: `==`, `!=`, `>`, `<`, `isNull`, `isNotNull`, `contains`.
308
+
309
+ **Important**: Use `criteria` (not `conditions`) and `variable` (not `field`).
310
+
311
+ ## Email Workflow Conventions
312
+
313
+ ### Trigger choice: polling vs event
314
+
315
+ **Default to Schedule trigger + label-based dedup** for all email intake workflows (deal flow, triage, review, digest). Only propose an App Event trigger when the user explicitly needs sub-minute latency.
316
+
317
+ | User asks for | Trigger |
318
+ |---------------|---------|
319
+ | "process inbound emails", "triage daily", "review pitches" | **Schedule** (polling) |
320
+ | "as soon as", "real-time", "within X seconds/minutes" | **App event** |
321
+
322
+ ### Canonical email polling pattern
323
+
324
+ ```
325
+ schedule trigger → GMAIL_FETCH_EMAILS (-label:processed newer_than:1d) → loop: [process] → GMAIL_ADD_LABEL (mark processed) → milestone
326
+ ```
327
+
328
+ Step order:
329
+ 1. **`GMAIL_CREATE_LABEL`** — create/get the `processed` label (idempotent, returns label ID)
330
+ 2. **`GMAIL_FETCH_EMAILS`** — query `-label:processed newer_than:1d` (or wider window as needed)
331
+ 3. **Loop** — process each email (AI analysis, KG storage, enrichment, etc.)
332
+ 4. **`GMAIL_ADD_LABEL`** — apply `{{steps.ensure-label.id}}` to mark email done (dedup gate)
333
+
334
+ ### Label ID rule (prevents `400: Invalid label`)
335
+
336
+ Gmail requires **label IDs** (e.g., `Label_3456789012345`), not display names (e.g., `"processed"` or `"agentled"`).
337
+
338
+ **Always resolve via `GMAIL_CREATE_LABEL`** and reference its returned `id`:
339
+ ```json
340
+ { "stepInputData": { "label_id": "{{steps.ensure-label.id}}" } }
341
+ ```
342
+ Never pass a string label name directly to `GMAIL_ADD_LABEL`.
343
+
344
+ See `docs/workflows/triggers.md` for the full decision framework, query examples, and common mistakes.
345
+
346
+ ---
347
+
348
+ ## Email Step Pattern (AI Draft → Approve → Send)
349
+
350
+ Email steps use a single `aiAction` step (never separate "draft" + "gmail send" appAction steps). The AI drafts the email, a human approves, then the platform sends it.
351
+
352
+ ### 1. Outreach Profile Input Page
353
+
354
+ When a workflow sends emails, add an outreach profile input page to `context.inputPages` so the user can configure sender identity:
355
+
356
+ ```json
357
+ {
358
+ "title": "Outreach Profile",
359
+ "pathname": "outreach-profile",
360
+ "configuration": {
361
+ "contextKey": "outreachProfile",
362
+ "shortDescriptionFields": ["name", "fromEmail"],
363
+ "fields": [
364
+ { "name": "name", "label": "Sender Name", "type": "text", "required": true },
365
+ { "name": "fromEmailLabel", "label": "From Name", "type": "text", "required": true },
366
+ { "name": "fromEmail", "label": "From Email", "type": "connected_emails_selector_multiple", "required": true },
367
+ { "name": "replyToEmail", "label": "Reply-To Email (optional)", "type": "text" }
368
+ ]
369
+ }
370
+ }
371
+ ```
372
+
373
+ ### 2. Composed Email Step
374
+
375
+ ```json
376
+ {
377
+ "id": "send_email",
378
+ "type": "aiAction",
379
+ "name": "Send Email",
380
+ "pipelineStepPrompt": {
381
+ "type": "email",
382
+ "template": "Draft a personalized email...\n{{steps.previous_step.data}}\nReturn JSON ONLY per schema.",
383
+ "responseStructure": {
384
+ "email": {
385
+ "from": "{{context.outreachProfile.fromEmail}}",
386
+ "to": "recipient@example.com",
387
+ "subject": "Email subject line",
388
+ "body": "Email body (email-safe HTML)",
389
+ "bodyType": "html"
390
+ }
391
+ },
392
+ "responseType": "json"
393
+ },
394
+ "renderer": {
395
+ "type": "Email",
396
+ "config": { "fromContextKey": "outreachProfile" }
397
+ },
398
+ "onApproval": {
399
+ "action": "schedule-email",
400
+ "executedText": "Email sent by {{name}} at {{date}}",
401
+ "scheduledText": "Email scheduled to be sent for {{date}} by {{name}}",
402
+ "failedText": "Email failed to send."
403
+ },
404
+ "integrations": [{
405
+ "type": "oneOf",
406
+ "label": "Email",
407
+ "connectorType": "email",
408
+ "options": [
409
+ { "name": "Gmail", "url": "https://gmail.com", "isUserAccountConnectionRequired": true },
410
+ { "name": "Outlook", "url": "https://outlook.com", "isUserAccountConnectionRequired": true }
411
+ ],
412
+ "selectionHint": "preferConnected"
413
+ }],
414
+ "creditCost": 10,
415
+ "next": { "conditions": { "approvalRequired": true } }
416
+ }
417
+ ```
418
+
419
+ ### Key Requirements
420
+
421
+ - **Always** include `outreachProfile` input page when using email
422
+ - `pipelineStepPrompt.type: "email"` — tells the system this is an email step
423
+ - `renderer.config.fromContextKey: "outreachProfile"` — links renderer to sender profile
424
+ - `onApproval.action: "schedule-email"` — triggers the actual send; without it, approval does nothing
425
+ - `next.conditions.approvalRequired: true` — blocks the pipeline until human approval
426
+ - Email body must be email-safe HTML (`<p>`, `<br>`, `<a>`, `<strong>` — no CSS, no scripts)
427
+ - **Never** use separate "draft" + "gmail send" appAction steps for outreach
428
+
429
+ ## Top Apps Quick Reference
430
+
431
+ | App | Action | Credits | Key Inputs |
432
+ |-----|--------|---------|------------|
433
+ | `agentled` | `get-linkedin-company-from-url` | 5 | `profileUrls` |
434
+ | `agentled` | `get-linkedin-profile-from-url` | 2 | `profileUrls` |
435
+ | `agentled` | `find-email-person-domain` | 3 | `firstName`, `lastName`, `domain` |
436
+ | `hunter` | `find-email-person-domain` | 3 | `firstName`, `lastName`, `domain` |
437
+ | `web-scraping` | `scrape` | 0 | `url` |
438
+ | `http-request` | `request` | 0 | `url`, `method`, `headers`, `body` |
439
+ | `notion` | `get-page-markdown` | 1 | `pageUrl` |
440
+ | `browser-use` | `run-task` | 15 | `task`, `startUrl` |
441
+ | `agentled` | `call-workflow` | varies | `workflowId`, `input` |
442
+
443
+ Use `list_apps` and `get_app_actions` for full schemas of all available apps. Use `list_models` for supported AI model IDs.
444
+
445
+ ## Credit-Efficient Testing
446
+
447
+ Each execution costs real credits. Follow these rules:
448
+
449
+ 1. **One execution at a time** — don't start new ones unnecessarily
450
+ 2. **Retry, don't restart** — use `retry_execution` to continue from a failed step instead of starting over
451
+ 3. **Test in isolation** — use `test_ai_action`, `test_app_action`, or `test_code_action` to verify steps before wiring them into a workflow
452
+ 4. **Reuse prior output** — when testing downstream steps, use output data from a prior successful execution as mock input
453
+
454
+ ## Common Validation Errors
455
+
456
+ | Error | Fix |
457
+ |-------|-----|
458
+ | `"references non-existent next step"` | Ensure some step has `next: { stepId: "X" }` pointing to the missing step |
459
+ | `"missing prompt template"` | Add `pipelineStepPrompt.template` to AI steps |
460
+ | `"Unknown action"` | Verify `actionId` format via `get_app_actions` |
461
+ | `"is unreachable"` | Connect every step via `next.stepId` from the trigger chain |
462
+ | `"unsupported model"` | Use a valid internal model ID (e.g., `claude-4-6-sonnet`, not `claude-sonnet-4-6`). Run `list_models` for all valid IDs. |
463
+
464
+ ## Persistent Memory
465
+
466
+ Workflows can store and recall memories that persist across executions. Two mechanisms:
467
+
468
+ ### MCP Tools (for managing memory externally)
469
+
470
+ | Tool | Purpose | Key Params |
471
+ |------|---------|------------|
472
+ | `recall_memory` | Get a specific memory by key | `key`, `scope?`, `workflowId?` |
473
+ | `search_memories` | Search by natural language query | `query?`, `category?`, `scope?`, `workflowId?`, `limit?` |
474
+ | `store_memory` | Save a persistent memory | `key`, `value`, `category?`, `scope?`, `workflowId?`, `confidence?`, `merge?` |
475
+ | `list_memories` | List all memories in a scope | `scope?`, `workflowId?`, `category?`, `limit?` |
476
+ | `delete_memory` | Delete a memory by key | `key`, `scope?`, `workflowId?` |
477
+
478
+ **Scopes**: `workspace` (shared across all workflows) or `workflow` (scoped to one workflow, default).
479
+
480
+ **Categories**: `fact` (known truth), `insight` (pattern/learning), `preference` (user preference), `outcome` (result to track).
481
+
482
+ **Merge strategies** (for `store_memory`): `overwrite` (default), `append`, `max`, `min`, `increment`.
483
+
484
+ **Confidence**: 0-100. Memories with confidence >= 70 are automatically synced to the Knowledge Graph.
485
+
486
+ ### Pipeline Step Configuration (for memory inside workflows)
487
+
488
+ #### Auto-extraction (pipeline-level)
489
+
490
+ Enable on the pipeline to automatically extract memories after each execution completes:
491
+
492
+ ```json
493
+ {
494
+ "persistentMemoryConfig": {
495
+ "autoExtract": true,
496
+ "scopes": ["pipeline"],
497
+ "categories": ["fact", "insight", "outcome"],
498
+ "maxPerExtraction": 10,
499
+ "extractionModelTier": "mini"
500
+ }
501
+ }
502
+ ```
503
+
504
+ #### Explicit per-step writes
505
+
506
+ Configure specific steps to write memories from their output:
507
+
508
+ ```json
509
+ {
510
+ "id": "score-company",
511
+ "type": "aiAction",
512
+ "persistentMemory": {
513
+ "writes": [
514
+ {
515
+ "key": "score_{{input.company_name}}",
516
+ "valuePath": "total_score",
517
+ "category": "outcome",
518
+ "scope": "pipeline",
519
+ "confidence": 85
520
+ }
521
+ ]
522
+ }
523
+ }
524
+ ```
525
+
526
+ The `valuePath` extracts from the step's output using dot notation. The `key` supports template variables.
527
+
528
+ #### Builtin tool for AI steps (`workspace_memory`)
529
+
530
+ AI steps with type `aiActionWithTools` can use the `workspace_memory` builtin tool to read/write memory during execution:
531
+
532
+ ```json
533
+ {
534
+ "id": "analyze",
535
+ "type": "aiActionWithTools",
536
+ "name": "Analyze with Memory",
537
+ "tools": [{ "builtinType": "workspace_memory" }],
538
+ "pipelineStepPrompt": {
539
+ "template": "Recall what we know about this company, then analyze...",
540
+ "responseStructure": { "analysis": "string" }
541
+ },
542
+ "creditCost": 10,
543
+ "next": { "stepId": "done" }
544
+ }
545
+ ```
546
+
547
+ The AI agent can then call `recall`, `search`, or `store` actions within the tool during execution. This is the same pattern used by KG tools (`kg_search`, `kg_traverse`, etc.).
548
+
549
+ ### Memory Patterns
550
+
551
+ **1. Learning workflow** — accumulates knowledge over repeated runs:
552
+ ```
553
+ trigger → enrich → AI analyze (with workspace_memory tool) → milestone
554
+ ```
555
+ The AI step recalls prior scores, compares trends, and stores updated insights.
556
+
557
+ **2. Explicit score tracking** — saves structured data for cross-run comparison:
558
+ ```
559
+ trigger → score company → [persistentMemory.writes: score_{{company}}] → milestone
560
+ ```
561
+
562
+ **3. Workspace-wide preferences** — store ICP criteria, outreach templates, or scoring weights shared across workflows:
563
+ ```
564
+ store_memory(key: "target_icp", value: { industry: "SaaS", minEmployees: 50 }, scope: "workspace", category: "preference")
565
+ ```
566
+
567
+ ## Conversational Building
568
+
569
+ For complex workflows, use the `chat` tool to design workflows through natural language conversation. It supports multi-turn via `session_id`.
570
+
571
+ ```
572
+ chat("Build a workflow that takes a LinkedIn URL, enriches the company, finds decision-maker emails, and scores by ICP fit")
573
+ ```