@agentled/cli 0.7.7 → 0.7.8

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.
@@ -0,0 +1,40 @@
1
+ # Top Apps, Credit-Efficient Testing & Validation Errors
2
+
3
+ > Loaded on demand from the Agentled skill. `list_apps` / `get_app_actions`
4
+ > (MCP) and `agentled apps` (CLI) are the **authoritative** sources for app
5
+ > schemas — this file is a fast reference plus the testing/error playbook.
6
+
7
+ ## Top Apps Quick Reference
8
+
9
+ | App | Action | Credits | Key Inputs |
10
+ |-----|--------|---------|------------|
11
+ | `agentled` | `get-linkedin-company-from-url` | 5 | `profileUrls` |
12
+ | `agentled` | `get-linkedin-profile-from-url` | 2 | `profileUrls` |
13
+ | `agentled` | `find-email-person-domain` | 3 | `firstName`, `lastName`, `domain` |
14
+ | `hunter` | `find-email-person-domain` | 3 | `firstName`, `lastName`, `domain` |
15
+ | `web-scraping` | `scrape` | 0 | `url` |
16
+ | `http-request` | `request` | 0 | `url`, `method`, `headers`, `body` |
17
+ | `notion` | `get-page-markdown` | 1 | `pageUrl` |
18
+ | `browser-use` | `run-task` | 15 | `task`, `startUrl` |
19
+ | `agentled` | `call-workflow` | varies | `workflowId`, `input` |
20
+
21
+ Use `list_apps` and `get_app_actions` for full schemas of all available apps. Use `list_models` for supported AI model IDs.
22
+
23
+ ## Credit-Efficient Testing
24
+
25
+ Each execution costs real credits. Follow these rules:
26
+
27
+ 1. **One execution at a time** — don't start new ones unnecessarily
28
+ 2. **Retry, don't restart** — use `retry_execution` to continue from a failed step instead of starting over
29
+ 3. **Test in isolation** — use `test_ai_action`, `test_app_action`, or `test_code_action` to verify steps before wiring them into a workflow
30
+ 4. **Reuse prior output** — when testing downstream steps, use output data from a prior successful execution as mock input
31
+
32
+ ## Common Validation Errors
33
+
34
+ | Error | Fix |
35
+ |-------|-----|
36
+ | `"references non-existent next step"` | Ensure some step has `next: { stepId: "X" }` pointing to the missing step |
37
+ | `"missing prompt template"` | Add `pipelineStepPrompt.template` to AI steps |
38
+ | `"Unknown action"` | Verify `actionId` format via `get_app_actions` |
39
+ | `"is unreachable"` | Connect every step via `next.stepId` from the trigger chain |
40
+ | `"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. |
@@ -0,0 +1,109 @@
1
+ # Editing Existing Workflows — Merge Model
2
+
3
+ > Loaded on demand from the Agentled skill. Read this before editing any
4
+ > dictionary-shaped step field (`stepInputData.fieldUpdates`,
5
+ > `pipelineStepPrompt.responseStructure`, `knowledgeSync.fieldMapping`) or any
6
+ > `context.*` / `metadata.*` workflow-level field.
7
+
8
+ For live workflows, prefer per-step tools over bulk updates:
9
+
10
+ - `update_step(workflowId, stepId, updates)` — change one step (prompt, inputs, next, etc.)
11
+ - `add_step(workflowId, step, insertAfter?)` — insert a new step
12
+ - `remove_step(workflowId, stepId)` — delete a step and re-wire neighbors
13
+ - After edits: `validate_workflow` → `publish_workflow` (or `promote_draft` for live workflows)
14
+
15
+ ## Merge model
16
+
17
+ `update_step` accepts three explicit operations on the same call. At least one must be non-empty.
18
+
19
+ - **`updates`** — partial step patch, **deep-merged ONE LEVEL deep**. Top-level scalars are replaced; nested objects (e.g. `pipelineStepPrompt`, `stepInputData`) get their direct keys merged with the stored value's keys. Keys nested two levels deep are overwritten as a unit, not merged.
20
+ - **`replace: string[]`** — dot-paths whose values from `updates` are assigned **wholesale**, skipping the deep-merge. Use this for **dictionary-shaped fields where keys are user data** (not config) — patching one inner key with `updates` alone silently wipes the others.
21
+ - **`unset: string[]`** — dot-paths to delete. Each path must currently exist on the step (validated against the original — you cannot unset something you just created in the same call).
22
+
23
+ **Read-before-write for dictionary fields.** Before editing `stepInputData.fieldUpdates`, `pipelineStepPrompt.responseStructure`, `knowledgeSync.fieldMapping`, or any field where keys are user data: call `get_step({ workflowId, stepId })`, modify locally, send the full new object back via `replace[]`. Cheap (~1KB) and avoids the "patched one key, silently wiped the others" trap.
24
+
25
+ **Diff in the response.** Every `update_step` returns `diff: { addedPaths, changedPaths, removedPaths }` and `warnings[]`. If the merge silently removed ≥6 fields without an explicit `unset`, a warning fires. Read it.
26
+
27
+ ## What to use where
28
+
29
+ | Path / field | API | How to edit | Notes |
30
+ |---|---|---|---|
31
+ | `name`, `goal`, `description`, `pipelineStepPrompt.template`, `creditCost` | `update_step` | `updates` | Plain scalar; safe to send alone. |
32
+ | `next`, `loopConfig`, `entryConditions` (full block) | `update_step` | `updates` | Direct nested config; sending the new value wholesale is fine — these are config, not user-data dictionaries. |
33
+ | `tools`, `integrations` | `update_step` | `updates` | Arrays are replaced wholesale by design. To append, fetch with `get_step`, splice locally, send the full new array. |
34
+ | `stepInputData.fieldUpdates` (kg.update-rows / kg.upsert-rows) | `update_step` | `get_step` → `updates` (full dict) + `replace: ["stepInputData.fieldUpdates"]` | Keys are user data; default one-level merge replaces this dict and can drop sibling mappings. |
35
+ | `pipelineStepPrompt.responseStructure` | `update_step` | `get_step` → `updates` (full struct) + `replace: ["pipelineStepPrompt.responseStructure"]` | Output-shape dictionary; treat as user data. |
36
+ | `knowledgeSync.fieldMapping` | `update_step` | `get_step` → `updates` (full mapping) + `replace: ["knowledgeSync.fieldMapping"]` | Source→target dict; same trap as `fieldUpdates`. |
37
+ | `renderer.config` (when preserving sibling keys matters) | `update_step` | `updates` (full `renderer.config`) + `replace: ["renderer.config"]` | ⚠ `replace: ["renderer.config.layout"]` does NOT protect `renderer.config`'s siblings — the one-level deep-merge runs first on `updates.renderer`. Replace at the parent level. |
38
+ | `entryConditions.criteria` (when preserving the rest of `entryConditions`) | `update_step` | `updates: { entryConditions: {...full block...} }` | Send the full `entryConditions` block; one-level merge already does the right thing for direct children. |
39
+ | Removing a step input or stale field | `update_step` | `unset: ["stepInputData.oldKey"]` | Cleanest way to remove. Path must exist on the original. |
40
+ | `context.inputPages`, `context.outputPages`, `context.executionInputConfig` | `update_workflow_context` | Three explicit verbs (`updates` / `replace` / `unset`) on workflow-relative paths, OR legacy `{ contextKey, value }` for wholesale per-key replacement | **Workflow-level, not step-level.** `update_step` cannot reach `context.*`. See workflow-level merge model below. |
41
+ | `metadata` | `update_workflow_context` | Same three verbs on `metadata.*` paths; one-level shallow-merge under `updates.metadata` | Workflow-level. Metadata edits bypass the draft snapshot (write direct to Pipeline row even on live workflows). |
42
+
43
+ **Type changes.** `step.type` is technically mutable but stale type-specific fields (`pipelineStepPrompt`, `app`, `tools`, `orchestratorConfig`) persist unless you `unset` them. For clean conversions, prefer `remove_step` + `add_step`. If editing in place, list the incompatible fields under `unset`.
44
+
45
+ **Live workflows.** Edits are routed to a draft snapshot. Response includes `editingDraft: true`. Inspect via `get_draft`, ship via `promote_draft`, throw away via `discard_draft`. For high-stakes edits, `create_snapshot` first as a manual checkpoint.
46
+
47
+ **Draft staleness.** When a draft already exists, every `update_step` / `get_step` response carries a `draft` summary:
48
+
49
+ ```jsonc
50
+ "draft": {
51
+ "exists": true,
52
+ "draftCreatedAt": "...",
53
+ "liveUpdatedAt": "...",
54
+ "stale": true, // live advanced past draft.createdAt
55
+ "modifiedStepIds": ["step-x"], // step IDs whose JSON differs between draft and live
56
+ "modifiedFields": ["steps"] // top-level config keys that differ
57
+ }
58
+ ```
59
+
60
+ If `draft.stale === true`, the live workflow has been touched (template upgrade, UI edit, deploy) since the draft was forked. Promoting the draft will land its values for fields you didn't touch in this session — those values may be older than current live. Recovery: `discard_draft` and re-apply your edit, or call `get_draft` to inspect what's pending. The `update_step` response also pushes a staleness string into `warnings[]`.
61
+
62
+ **Never** send a full `steps[]` array via `update_workflow`. Use `update_step`, `add_step`, `remove_step` instead.
63
+
64
+ ## Workflow-level merge model (`update_workflow_context`)
65
+
66
+ `update_workflow_context` is the workflow-level analog of `update_step` — same three verbs, but on **workflow-relative paths** (`context.executionInputConfig.fields`, `metadata.tags`, …). Step-relative paths like `pipelineStepPrompt.template` are rejected here, and conversely `update_step` rejects `context.*` / `metadata.*` paths. The boundary is hard.
67
+
68
+ Allowed path prefixes for `replace[]` / `unset[]`:
69
+
70
+ - `context.inputPages`
71
+ - `context.outputPages`
72
+ - `context.executionInputConfig`
73
+ - `metadata`
74
+
75
+ Out-of-scope paths (e.g. `steps`, `name`) → `PATH_OUT_OF_SCOPE` (400). Other typed errors mirror `update_step`: `EMPTY_PAYLOAD`, `INVALID_PATH`, `REPLACE_VALUE_MISSING`, `UNSET_PATH_NOT_FOUND`. The response includes `diff: { addedPaths, changedPaths, removedPaths }` and `warnings[]` (≥6 silent removals fires the warning).
76
+
77
+ Recipes:
78
+
79
+ ```jsonc
80
+ // Flip executionInputConfig.internal — same merge-order trap as update_step.
81
+ // Fetch (get_workflow), merge locally, replace at the parent level:
82
+ {
83
+ "updates": { "context": { "executionInputConfig": {...full merged executionInputConfig with internal: true...} } },
84
+ "replace": ["context.executionInputConfig"]
85
+ }
86
+
87
+ // Replace inputPages wholesale:
88
+ {
89
+ "updates": { "context": { "inputPages": [...new pages...] } },
90
+ "replace": ["context.inputPages"]
91
+ }
92
+
93
+ // Add a metadata tag (one-level shallow-merge — siblings preserved):
94
+ { "updates": { "metadata": { "tags": ["beta"] } } }
95
+
96
+ // Delete an obsolete metadata key:
97
+ { "unset": ["metadata.legacyFlag"] }
98
+ ```
99
+
100
+ Live workflows: context edits route to the draft snapshot. Metadata is NOT part of the snapshot config — it always writes direct to the Pipeline row, immediately and on the live workflow.
101
+
102
+ ⚠ **Mixed metadata + context in one call** is the sharp edge: metadata is applied immediately while context becomes pending in the draft. `discard_draft` reverts the pending context changes but **does NOT revert metadata** — metadata never went to the draft. If you need a single atomic checkpoint covering both, `create_snapshot` first or split the call into two.
103
+
104
+ A legacy `{ contextKey, value }` body shape is still accepted as a **compatibility shape** for one-shot wholesale replacement of a single root context key. It can't edit metadata, doesn't return `diff` / `warnings`, and is not the recommended path for new code — prefer the three-verb shape above.
105
+
106
+ ## Post-authoring
107
+
108
+ 1. Test: `start_workflow` with sample input
109
+ 2. Check results: `get_execution` to see step outputs
@@ -0,0 +1,122 @@
1
+ # Email Workflows — Triggers, Dedup & the Composed Email Step
2
+
3
+ > Loaded on demand from the Agentled skill. Read this for any workflow that
4
+ > reads or sends email. Pairs with the `08-composed-email-approval` pattern
5
+ > (`agentled examples 08-composed-email-approval`).
6
+
7
+ ## Trigger choice: polling vs event
8
+
9
+ **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.
10
+
11
+ | User asks for | Trigger |
12
+ |---------------|---------|
13
+ | "process inbound emails", "triage daily", "review pitches" | **Schedule** (polling) |
14
+ | "as soon as", "real-time", "within X seconds/minutes" | **App event** |
15
+
16
+ ## Canonical email polling pattern
17
+
18
+ ```
19
+ schedule trigger → GMAIL_FETCH_EMAILS (-label:processed newer_than:1d) → loop: [process] → GMAIL_ADD_LABEL (mark processed) → milestone
20
+ ```
21
+
22
+ Step order:
23
+ 1. **`GMAIL_CREATE_LABEL`** — create/get the `processed` label (idempotent, returns label ID)
24
+ 2. **`GMAIL_FETCH_EMAILS`** — query `-label:processed newer_than:1d` (or wider window as needed)
25
+ 3. **Loop** — process each email (AI analysis, KG storage, enrichment, etc.)
26
+ 4. **`GMAIL_ADD_LABEL`** — apply `{{steps.ensure-label.id}}` to mark email done (dedup gate)
27
+
28
+ ## Label ID rule (prevents `400: Invalid label`)
29
+
30
+ Gmail requires **label IDs** (e.g., `Label_3456789012345`), not display names (e.g., `"processed"` or `"agentled"`).
31
+
32
+ **Always resolve via `GMAIL_CREATE_LABEL`** and reference its returned `id`:
33
+ ```json
34
+ { "stepInputData": { "label_id": "{{steps.ensure-label.id}}" } }
35
+ ```
36
+ Never pass a string label name directly to `GMAIL_ADD_LABEL`.
37
+
38
+ See `docs/workflows/triggers.md` for the full decision framework, query examples, and common mistakes.
39
+
40
+ ---
41
+
42
+ ## Email Step Pattern (AI Draft → Approve → Send)
43
+
44
+ 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.
45
+
46
+ ### 1. Outreach Profile Input Page
47
+
48
+ When a workflow sends emails, add an outreach profile input page to `context.inputPages` so the user can configure sender identity:
49
+
50
+ ```json
51
+ {
52
+ "title": "Outreach Profile",
53
+ "pathname": "outreach-profile",
54
+ "configuration": {
55
+ "contextKey": "outreachProfile",
56
+ "shortDescriptionFields": ["name", "fromEmail"],
57
+ "fields": [
58
+ { "name": "name", "label": "Sender Name", "type": "text", "required": true },
59
+ { "name": "fromEmailLabel", "label": "From Name", "type": "text", "required": true },
60
+ { "name": "fromEmail", "label": "From Email", "type": "connected_emails_selector_multiple", "required": true },
61
+ { "name": "replyToEmail", "label": "Reply-To Email (optional)", "type": "text" }
62
+ ]
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### 2. Composed Email Step
68
+
69
+ ```json
70
+ {
71
+ "id": "send_email",
72
+ "type": "aiAction",
73
+ "name": "Send Email",
74
+ "pipelineStepPrompt": {
75
+ "type": "email",
76
+ "template": "Draft a personalized email...\n{{steps.previous_step.data}}\nReturn JSON ONLY per schema.",
77
+ "responseStructure": {
78
+ "email": {
79
+ "from": "{{context.outreachProfile.fromEmail}}",
80
+ "to": "recipient@example.com",
81
+ "subject": "Email subject line",
82
+ "body": "Email body (email-safe HTML)",
83
+ "bodyType": "html"
84
+ }
85
+ },
86
+ "responseType": "json"
87
+ },
88
+ "renderer": {
89
+ "type": "Email",
90
+ "config": { "fromContextKey": "outreachProfile" }
91
+ },
92
+ "onApproval": {
93
+ "action": "schedule-email",
94
+ "executedText": "Email sent by {{name}} at {{date}}",
95
+ "scheduledText": "Email scheduled to be sent for {{date}} by {{name}}",
96
+ "failedText": "Email failed to send."
97
+ },
98
+ "integrations": [{
99
+ "type": "oneOf",
100
+ "label": "Email",
101
+ "connectorType": "email",
102
+ "options": [
103
+ { "name": "Gmail", "url": "https://gmail.com", "isUserAccountConnectionRequired": true },
104
+ { "name": "Outlook", "url": "https://outlook.com", "isUserAccountConnectionRequired": true }
105
+ ],
106
+ "selectionHint": "preferConnected"
107
+ }],
108
+ "creditCost": 10,
109
+ "next": { "conditions": { "approvalRequired": true } }
110
+ }
111
+ ```
112
+
113
+ ### Key Requirements
114
+
115
+ - **Always** include `outreachProfile` input page when using email
116
+ - `pipelineStepPrompt.type: "email"` — tells the system this is an email step
117
+ - `renderer.config.fromContextKey: "outreachProfile"` — links renderer to sender profile
118
+ - `onApproval.action: "schedule-email"` — triggers the actual send; without it, approval does nothing
119
+ - `next.conditions.approvalRequired: true` — blocks the pipeline until human approval
120
+ - Email body must be email-safe HTML (`<p>`, `<br>`, `<a>`, `<strong>` — no CSS, no scripts)
121
+ - **Never** use separate "draft" + "gmail send" appAction steps for outreach
122
+ - Set an output page `displayConfig.executionNameTemplate` that names the entity and the outreach state. For single-entity outreach, use `{{entityName}} - Email Drafted` before approval and `{{entityName}} - Email Sent` or `{{entityName}} - Contacted` after the post-send status step, e.g. `Joe Dan - VC Firm Name - Email Drafted`; add `{{today}}` only when a short date helps. For batch runs, summarize counts instead, e.g. `26 May - 4 sourced - 3 contacted`.
@@ -0,0 +1,106 @@
1
+ # Entity Pipeline Pattern (Source → KG → Process)
2
+
3
+ > Loaded on demand from the Agentled skill. Read this whenever the user wants
4
+ > to find leads, source companies, collect contacts, or discover entities to
5
+ > act on later. Pairs with `13-entity-pipeline-lifecycle`
6
+ > (`agentled examples 13-entity-pipeline-lifecycle`).
7
+
8
+ When a user asks about **finding leads, sourcing companies, collecting contacts, or discovering any entities they want to act on later**, propose this two-workflow architecture instead of a single monolithic workflow.
9
+
10
+ ## Why
11
+
12
+ Sourcing and processing have different cadences and costs. Decoupling them lets you:
13
+ - Source from many places (LinkedIn, web scrape, Crunchbase, email, webhooks) into one canonical list
14
+ - Process (enrich, score, outreach) only new entities, on a schedule, without re-processing already-handled ones
15
+ - Retry or re-run either phase independently without touching the other
16
+
17
+ ## Workflow 1 — Sourcing (runs on trigger or schedule)
18
+
19
+ Finds entities from one or more sources and writes them into a shared KG list with `status: "new"`. Uses `kg.upsert-rows` with a caller-supplied `userKey` on each row so re-runs dedup (O(1), no table scan).
20
+
21
+ ```json
22
+ // Step: write sourced entities to KG
23
+ {
24
+ "id": "save-to-kg",
25
+ "type": "appAction",
26
+ "name": "Save to KG",
27
+ "app": { "id": "kg", "actionId": "kg.upsert-rows", "source": "native" },
28
+ "stepInputData": {
29
+ "listKey": "sourced-startups",
30
+ // rows must be [{ userKey, rowData }, ...] — userKey is the dedup contract.
31
+ // Use any stable caller-defined id: URL, domain, LinkedIn URL, etc.
32
+ "rows": "{{steps.extract.items}}",
33
+ "mergeStrategy": "merge", // preserve fields added downstream (scores, notes)
34
+ "status": "new"
35
+ },
36
+ "next": { "stepId": "done" }
37
+ }
38
+ ```
39
+
40
+ **Key rules:**
41
+ - Always include a `status` on sourcing writes (default `"new"`) so the processing workflow can filter on it
42
+ - Give every row a stable `userKey` (URL, domain, LinkedIn URL — any caller-defined id that won't change). Same `userKey` in the same list = same DynamoDB row, forever
43
+ - Use `mergeStrategy: "merge"` so downstream-added fields (scores, status updates) survive re-upserts from the source
44
+ - Multiple sourcing workflows can write to the same `listKey` — they converge into one canonical list
45
+ - Use `kg.add-rows` only for one-shot, never-re-run writes where duplicates are acceptable
46
+
47
+ ## Workflow 2 — Processing (runs on schedule, e.g. weekly)
48
+
49
+ Reads only `status: "new"` rows, processes them (enrich, score, outreach, etc.), then updates status to `"processed"` (or `"scored"`, `"outreached"`, etc.) so they're never picked up again.
50
+
51
+ ```json
52
+ // Step 1: read new entities
53
+ {
54
+ "id": "read-new",
55
+ "type": "appAction",
56
+ "name": "Read New Entities",
57
+ "app": { "id": "kg", "actionId": "kg.read-list", "source": "native" },
58
+ "stepInputData": {
59
+ "listKey": "sourced-startups",
60
+ "filters": "{\"status\": \"new\"}",
61
+ "limit": "50"
62
+ },
63
+ "next": { "stepId": "process-loop" }
64
+ }
65
+
66
+ // Step 2: loop → enrich / score / outreach each entity
67
+ // ... your enrichment and scoring steps here, using {{currentItem.url}} etc. ...
68
+
69
+ // Step N: mark as processed
70
+ {
71
+ "id": "mark-processed",
72
+ "type": "appAction",
73
+ "name": "Mark Processed",
74
+ "app": { "id": "kg", "actionId": "kg.update-rows", "source": "native" },
75
+ "stepInputData": {
76
+ "listKey": "sourced-startups",
77
+ "rowIds": "{{steps.process-loop.processedIds}}",
78
+ "fieldUpdates": "{\"status\": \"processed\"}"
79
+ },
80
+ "next": { "stepId": "done" }
81
+ }
82
+ ```
83
+
84
+ ## Status values (suggested convention)
85
+
86
+ | Value | Meaning |
87
+ |-------|---------|
88
+ | `new` | Sourced, not yet processed |
89
+ | `scored` | Enriched and scored, not yet outreached |
90
+ | `outreached` | Outreach sent |
91
+ | `rejected` | Filtered out during scoring |
92
+ | `processed` | Generic "done" for non-outreach pipelines |
93
+
94
+ Use whatever values make sense for the use case — the pattern is the same.
95
+
96
+ ## When to propose this pattern
97
+
98
+ Suggest it whenever the user says any of:
99
+ - "find leads / companies / contacts"
100
+ - "source startups / investors / candidates"
101
+ - "collect entities from multiple sources"
102
+ - "build a list I can act on later"
103
+ - "score / enrich / outreach to a list"
104
+ - "run this once a week on new items"
105
+
106
+ The default answer is **two workflows**: one that sources into KG, one that processes from KG. A single do-everything workflow is only appropriate when the user has a fixed one-shot input and no recurring need.
@@ -0,0 +1,43 @@
1
+ # Outreach Scale, Segmentation & Discovery Questions
2
+
3
+ > Loaded on demand from the Agentled skill. Read this for any outbound, sales,
4
+ > investor, founder, or lead-generation workflow — classify the motion before
5
+ > designing.
6
+
7
+ ## What to ask — and what not to ask
8
+
9
+ After orienting on the workspace, ask **at most three to five** targeted questions. Focus only on what the workspace inspection cannot answer.
10
+
11
+ **Good questions:**
12
+ - "What is the primary goal: source more paying leads, run each event smoothly, or report pipeline health?"
13
+ - "Where is the bottleneck today: finding leads, getting replies, converting to paid, or operational follow-up?"
14
+ - "What is the system of record for payments/bookings right now: Stripe webhook, a booking page, a manual form, or a KG list?"
15
+ - "Which actions must be approval-gated: customer emails, status changes, or all customer-facing messages?"
16
+ - "What success metric should this optimize: conversions per week, leads contacted, confirmations, fill rate, or operator time saved?"
17
+
18
+ **Do not ask:**
19
+ - Step type names, app action names, schema syntax, or implementation details — discover these from the workspace and CLI.
20
+ - Every possible future integration question before the first useful slice is designed.
21
+ - Anything answerable by running `agentled workspace inspect`.
22
+
23
+ ## Outreach scale and segmentation
24
+
25
+ For outbound, sales, investor, founder, or lead-generation workflows, classify the motion before designing the workflow. See `docs/OUTREACH_SCALE_GUIDANCE.md`.
26
+
27
+ **Core diagnostic:**
28
+ - PCPL = prospects contacted / positive replies over the last 30 days. PCPL at or below 250 is a scalable winner; above 250 means diagnose deliverability, then copy, then offer-audience match.
29
+ - Daily volume = last 30 days sent / 22 working days.
30
+ - TAM = total reachable prospects in the CRM, KG list, enrichment tool, or target account universe.
31
+ - Reply ops = whether positive replies are handled quickly and followed up persistently.
32
+
33
+ **Tier guidance:**
34
+ - Tier 1 (~100/day, a few thousand prospects): enrich every account, route by strongest signal, use high-touch multi-channel outreach. This is the default for most small Agentled Ops / AngelHive-style motions.
35
+ - Tier 2 (~1,000/day, 50K-100K prospects): split into top 200-500 home-run accounts with Tier 1 treatment plus segmented automation for the long tail.
36
+ - Tier 3 (~10K/day, hundreds of thousands): test angle x offer x segment, generate script variations, keep tests running in parallel, and track PCPL per combination.
37
+ - Tier 4 (50K-100K/day, millions): require provider segmentation, live monitoring, sender reserve capacity, hourly testing cadence, and reply-handling SLAs before increasing volume.
38
+
39
+ **Workflow implications:**
40
+ - Small TAM problems are usually segmentation and signal-enrichment problems, not copy-only problems.
41
+ - Store `outreachAngle`, `offer`, `segment`, `campaignVariant`, `channelUsed`, `sender`, `contactedAt`, `replyStatus`, `meetingStatus`, and `positiveReplyAt` when the KG schema allows it.
42
+ - Configure business metrics for prospects contacted, positive replies, PCPL, meetings/bookings/paid confirmations, bounce/failed-send count, and segment/angle performance. These are `analyticsConfig` business metrics, not ROI assumptions.
43
+ - Literal PCPL should use `type: "ratio"` with `ratioMode: "raw"`. Use the default percentage ratio behavior for rates such as `positive_replies / prospects_contacted`.
@@ -0,0 +1,106 @@
1
+ # Persistent Memory
2
+
3
+ > Loaded on demand from the Agentled skill. Workflows can store and recall
4
+ > memories that persist across executions. Memory is opt-in — existing
5
+ > workflows are unaffected.
6
+
7
+ Two mechanisms: MCP tools (manage memory externally) and pipeline-step configuration (memory inside workflows).
8
+
9
+ ## MCP Tools (for managing memory externally)
10
+
11
+ | Tool | Purpose | Key Params |
12
+ |------|---------|------------|
13
+ | `recall_memory` | Get a specific memory by key | `key`, `scope?`, `workflowId?` |
14
+ | `search_memories` | Search by natural language query | `query?`, `category?`, `scope?`, `workflowId?`, `limit?` |
15
+ | `store_memory` | Save a persistent memory | `key`, `value`, `category?`, `scope?`, `workflowId?`, `confidence?`, `merge?` |
16
+ | `list_memories` | List all memories in a scope | `scope?`, `workflowId?`, `category?`, `limit?` |
17
+ | `delete_memory` | Delete a memory by key | `key`, `scope?`, `workflowId?` |
18
+
19
+ **Scopes**: `workspace` (shared across all workflows) or `workflow` (scoped to one workflow, default).
20
+
21
+ **Categories**: `fact` (known truth), `insight` (pattern/learning), `preference` (user preference), `outcome` (result to track).
22
+
23
+ **Merge strategies** (for `store_memory`): `overwrite` (default), `append`, `max`, `min`, `increment`.
24
+
25
+ **Confidence**: 0-100. Memories with confidence >= 70 are automatically synced to the Knowledge Graph.
26
+
27
+ ## Pipeline Step Configuration (for memory inside workflows)
28
+
29
+ ### Auto-extraction (pipeline-level)
30
+
31
+ Enable on the pipeline to automatically extract memories after each execution completes:
32
+
33
+ ```json
34
+ {
35
+ "persistentMemoryConfig": {
36
+ "autoExtract": true,
37
+ "scopes": ["pipeline"],
38
+ "categories": ["fact", "insight", "outcome"],
39
+ "maxPerExtraction": 10,
40
+ "extractionModelTier": "mini"
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### Explicit per-step writes
46
+
47
+ Configure specific steps to write memories from their output:
48
+
49
+ ```json
50
+ {
51
+ "id": "score-company",
52
+ "type": "aiAction",
53
+ "persistentMemory": {
54
+ "writes": [
55
+ {
56
+ "key": "score_{{input.company_name}}",
57
+ "valuePath": "total_score",
58
+ "category": "outcome",
59
+ "scope": "pipeline",
60
+ "confidence": 85
61
+ }
62
+ ]
63
+ }
64
+ }
65
+ ```
66
+
67
+ The `valuePath` extracts from the step's output using dot notation. The `key` supports template variables.
68
+
69
+ ### Builtin tool for AI steps (`workspace_memory`)
70
+
71
+ AI steps with type `aiActionWithTools` can use the `workspace_memory` builtin tool to read/write memory during execution:
72
+
73
+ ```json
74
+ {
75
+ "id": "analyze",
76
+ "type": "aiActionWithTools",
77
+ "name": "Analyze with Memory",
78
+ "tools": [{ "builtinType": "workspace_memory" }],
79
+ "pipelineStepPrompt": {
80
+ "template": "Recall what we know about this company, then analyze...",
81
+ "responseStructure": { "analysis": "string" }
82
+ },
83
+ "creditCost": 10,
84
+ "next": { "stepId": "done" }
85
+ }
86
+ ```
87
+
88
+ 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.).
89
+
90
+ ## Memory Patterns
91
+
92
+ **1. Learning workflow** — accumulates knowledge over repeated runs:
93
+ ```
94
+ trigger → enrich → AI analyze (with workspace_memory tool) → milestone
95
+ ```
96
+ The AI step recalls prior scores, compares trends, and stores updated insights.
97
+
98
+ **2. Explicit score tracking** — saves structured data for cross-run comparison:
99
+ ```
100
+ trigger → score company → [persistentMemory.writes: score_{{company}}] → milestone
101
+ ```
102
+
103
+ **3. Workspace-wide preferences** — store ICP criteria, outreach templates, or scoring weights shared across workflows:
104
+ ```
105
+ store_memory(key: "target_icp", value: { industry: "SaaS", minEmployees: 50 }, scope: "workspace", category: "preference")
106
+ ```