@agentled/cli 0.7.5 → 0.7.7

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 (41) hide show
  1. package/README.md +19 -1
  2. package/dist/commands/clients.d.ts +2 -0
  3. package/dist/commands/clients.js +408 -0
  4. package/dist/commands/clients.js.map +1 -0
  5. package/dist/commands/do.js +1 -1
  6. package/dist/commands/do.js.map +1 -1
  7. package/dist/commands/examples.d.ts +5 -5
  8. package/dist/commands/examples.js +8 -2
  9. package/dist/commands/examples.js.map +1 -1
  10. package/dist/commands/onboarding.js +43 -15
  11. package/dist/commands/onboarding.js.map +1 -1
  12. package/dist/index.js +2 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/utils/agentled-home-content.d.ts +1 -1
  15. package/dist/utils/agentled-home-content.js +6 -3
  16. package/dist/utils/agentled-home-content.js.map +1 -1
  17. package/dist/utils/browser-auth.d.ts +1 -0
  18. package/dist/utils/browser-auth.js +3 -3
  19. package/dist/utils/browser-auth.js.map +1 -1
  20. package/dist/utils/pipeline-lint.js +3 -1
  21. package/dist/utils/pipeline-lint.js.map +1 -1
  22. package/dist/utils/workspace-folder.d.ts +4 -0
  23. package/dist/utils/workspace-folder.js +76 -1
  24. package/dist/utils/workspace-folder.js.map +1 -1
  25. package/dist/utils/workspace-meta.d.ts +4 -3
  26. package/dist/utils/workspace-meta.js +27 -5
  27. package/dist/utils/workspace-meta.js.map +1 -1
  28. package/llms-full.txt +4 -2
  29. package/llms.txt +17 -0
  30. package/package.json +1 -1
  31. package/patterns/v1/00-design-principles.md +161 -0
  32. package/patterns/v1/12-event-driven-workflow-groups.md +21 -0
  33. package/patterns/v1/13-entity-pipeline-lifecycle.md +319 -0
  34. package/scaffolds/README.md +7 -1
  35. package/scaffolds/child-with-return.json +71 -0
  36. package/scaffolds/funnel-orchestrator.json +134 -0
  37. package/scaffolds/kg-process-update.json +88 -0
  38. package/scaffolds/orchestrator-kg-loop.json +96 -0
  39. package/scaffolds/source-from-platform.json +78 -0
  40. package/scaffolds/source-to-kg.json +88 -0
  41. package/skills/agentled/SKILL.md +24 -1
@@ -20,16 +20,22 @@ Every bundled scaffold:
20
20
  4. Uses domain-agnostic placeholder names (`candidate`, `metric_a`,
21
21
  `entity_id`) — not specific verticals.
22
22
 
23
+ > **Read [`patterns/v1/00-design-principles.md`](../patterns/v1/00-design-principles.md) first.** It defines the four pillars (idempotency, small scope, KG-list-as-connector, design-as-data) and the decision tree that tells you which scaffold to pick.
24
+
23
25
  ## Catalog
24
26
 
25
27
  | Slug | Pattern(s) | Shape |
26
28
  |------|-----------|-------|
27
29
  | `minimal` | — | trigger → milestone (smallest valid pipeline) |
30
+ | `ai-with-tools` | — | trigger → `aiActionWithTools` (web_search + workspace_memory) → milestone |
28
31
  | `email-polling-dedup` | 02 + 13 | schedule → fetch emails (label dedup) → loop process → add label |
29
32
  | `lead-scoring-kg` | 04 + 09 | trigger → kg.read-list → AI scoring loop → knowledgeSync → report |
30
33
  | `list-match-email` | 08 | trigger → kg.read-list → AI match top candidates → composed email (approval gate) → knowledgeSync |
31
34
  | `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 |
35
+ | `source-to-kg` | 12 + 15a | schedule → generate queries → search → extract → kg.upsert-rows(`userKey`, status:`new`) — idempotent sourcing half of a two-workflow group |
36
+ | `kg-process-update` | 12 + 15b | schedule → kg.read-list(status:`new`) → loop process → kg.update-rows(status:`processed`) — processing half of a two-workflow group |
37
+ | `child-with-return` | 05 + 12 | manual → enrich → score → `return` (internal:true, called via call-workflow) |
38
+ | `orchestrator-kg-loop` | 11 | manual → kg.read-list → loop call-workflow per row → wait loop_completion → kg.update-rows → summarize |
33
39
 
34
40
  ## Bring your own scaffolds
35
41
 
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "Child Workflow with Return",
3
+ "goal": "A reusable child workflow that takes input via call-workflow and returns a structured result. Terminal step is `return`, not `milestone`.",
4
+ "description": "Pattern 12 (child-workflow-contracts). Mark `context.executionInputConfig.internal: true` so the UI hides the Run button — this workflow is invoked only by orchestrators via `call-workflow`. The return contract is explicit: every field a caller can read is declared in `returnConfig.fields`.",
5
+ "status": "draft",
6
+ "context": {
7
+ "executionInputConfig": {
8
+ "title": "Score Entity (called via orchestrator)",
9
+ "description": "This is a child workflow. Run it from an orchestrator via call-workflow, not by hand.",
10
+ "runCTALabel": "Score",
11
+ "allowRegeneration": false,
12
+ "internal": true,
13
+ "fields": [
14
+ { "name": "entity_id", "label": "Entity ID", "type": "text", "required": true },
15
+ { "name": "entity_url", "label": "Entity URL", "type": "text", "required": true }
16
+ ]
17
+ }
18
+ },
19
+ "steps": [
20
+ {
21
+ "id": "start",
22
+ "type": "trigger",
23
+ "name": "Called by Orchestrator",
24
+ "pipelineStepStartConditions": { "trigger": { "type": "manual" } },
25
+ "next": { "stepId": "enrich" }
26
+ },
27
+ {
28
+ "id": "enrich",
29
+ "type": "aiAction",
30
+ "name": "Enrich Entity",
31
+ "pipelineStepPrompt": {
32
+ "template": "Replace with your real enrichment logic.\n\nEntity: {{input.entity_id}}\nURL: {{input.entity_url}}\n\nReturn the enriched fields the score step will consume.",
33
+ "responseStructure": {
34
+ "enriched": "object — fields the score step needs"
35
+ }
36
+ },
37
+ "creditCost": 6,
38
+ "next": { "stepId": "score" }
39
+ },
40
+ {
41
+ "id": "score",
42
+ "type": "aiAction",
43
+ "name": "Score Entity",
44
+ "pipelineStepPrompt": {
45
+ "template": "Score this entity against the rubric stored in KG text (`kg.read-text` with key `design.scoring-rubric` — load upstream if you need it). Return total_score (0-100), decision (qualify|nurture|reject), and reasoning.\n\nEntity: {{input.entity_id}}\nEnriched: {{steps.enrich.enriched}}",
46
+ "responseStructure": {
47
+ "total_score": "number (0-100)",
48
+ "decision": "string (qualify | nurture | reject)",
49
+ "reasoning": "string"
50
+ }
51
+ },
52
+ "creditCost": 8,
53
+ "next": { "stepId": "return-result" }
54
+ },
55
+ {
56
+ "id": "return-result",
57
+ "type": "return",
58
+ "name": "Return Score to Caller",
59
+ "returnConfig": {
60
+ "source": "executionContent",
61
+ "fields": [
62
+ { "name": "entity_id", "stepId": "start", "field": "entity_id" },
63
+ { "name": "total_score", "stepId": "score", "field": "total_score" },
64
+ { "name": "decision", "stepId": "score", "field": "decision" },
65
+ { "name": "reasoning", "stepId": "score", "field": "reasoning" },
66
+ { "name": "enriched", "stepId": "enrich", "field": "enriched" }
67
+ ]
68
+ }
69
+ }
70
+ ]
71
+ }
@@ -0,0 +1,134 @@
1
+ {
2
+ "_scaffold": "funnel-orchestrator",
3
+ "_description": "Scheduled KG funnel coordinator: read new rows, call child scoring workflow, read qualified rows, queue outreach workflow, compute KPIs, and finish with a digest.",
4
+ "name": "Scheduled Funnel Orchestrator",
5
+ "goal": "Run a recurring funnel pass across already-working child workflows and report what moved.",
6
+ "description": "Use after sourcing, scoring, contact discovery, and outreach workflows have each passed manual validation.",
7
+ "status": "draft",
8
+ "context": {
9
+ "executionInputConfig": {
10
+ "title": "Run Funnel",
11
+ "description": "Manual override for the scheduled orchestrator.",
12
+ "runCTALabel": "Run funnel",
13
+ "fields": [
14
+ { "name": "source_list_key", "label": "Source list key", "type": "text", "required": true, "defaultValue": "leads", "description": "Knowledge list key (e.g. \"leads\", \"startups\")." },
15
+ { "name": "new_status", "label": "New status", "type": "text", "required": true, "defaultValue": "new" },
16
+ { "name": "qualified_status", "label": "Qualified status", "type": "text", "required": true, "defaultValue": "qualified" },
17
+ { "name": "score_workflow_id", "label": "Scoring workflow ID", "type": "text", "required": true, "description": "Workflow ID for the child scoring workflow." },
18
+ { "name": "outreach_workflow_id", "label": "Outreach workflow ID", "type": "text", "required": true, "description": "Workflow ID for the child outreach workflow." }
19
+ ]
20
+ }
21
+ },
22
+ "steps": [
23
+ {
24
+ "id": "start",
25
+ "type": "trigger",
26
+ "name": "Scheduled funnel run",
27
+ "pipelineStepStartConditions": {
28
+ "trigger": {
29
+ "type": "schedule",
30
+ "description": "Run weekly.",
31
+ "config": { "frequency": "weekly", "dayOfWeek": "monday", "time": "09:00" }
32
+ }
33
+ },
34
+ "next": { "stepId": "read-new" }
35
+ },
36
+ {
37
+ "id": "read-new",
38
+ "type": "appAction",
39
+ "name": "Read new rows",
40
+ "app": { "id": "kg", "actionId": "kg.read-list", "source": "native" },
41
+ "stepInputData": {
42
+ "listKey": "{{input.source_list_key}}",
43
+ "status": "{{input.new_status || 'new'}}",
44
+ "limit": "25"
45
+ },
46
+ "next": { "stepId": "score-rows" }
47
+ },
48
+ {
49
+ "id": "score-rows",
50
+ "type": "appAction",
51
+ "name": "Run scoring workflow",
52
+ "app": { "id": "agentled", "actionId": "agentled.call-workflow", "source": "native" },
53
+ "stepInputData": {
54
+ "workflowId": "{{input.score_workflow_id}}",
55
+ "executionInputData": "{\"row\": {{newRow}}, \"listKey\": \"{{input.source_list_key}}\"}",
56
+ "timeoutMinutes": "20"
57
+ },
58
+ "loopConfig": {
59
+ "source": "executionContent",
60
+ "config": { "stepId": "read-new", "field": "listEntries" },
61
+ "ItemAlias": "newRow",
62
+ "loopCompletion": { "criteria": "all_items_processed" }
63
+ },
64
+ "creditCost": 0,
65
+ "next": { "stepId": "read-qualified" }
66
+ },
67
+ {
68
+ "id": "read-qualified",
69
+ "type": "appAction",
70
+ "name": "Read qualified rows",
71
+ "app": { "id": "kg", "actionId": "kg.read-list", "source": "native" },
72
+ "stepInputData": {
73
+ "listKey": "{{input.source_list_key}}",
74
+ "status": "{{input.qualified_status || 'qualified'}}",
75
+ "limit": "25"
76
+ },
77
+ "entryConditions": {
78
+ "onCriteriaFail": "wait",
79
+ "conditionText": "Wait for scoring child workflows to complete.",
80
+ "criteria": [
81
+ { "type": "loop_completion", "stepId": "score-rows", "operator": "==", "value": true }
82
+ ]
83
+ },
84
+ "next": { "stepId": "queue-outreach" }
85
+ },
86
+ {
87
+ "id": "queue-outreach",
88
+ "type": "appAction",
89
+ "name": "Queue outreach workflow",
90
+ "app": { "id": "agentled", "actionId": "agentled.start-workflow", "source": "native" },
91
+ "stepInputData": {
92
+ "workflowId": "{{input.outreach_workflow_id}}",
93
+ "executionInputData": "{\"row\": {{qualifiedRow}}, \"listKey\": \"{{input.source_list_key}}\"}"
94
+ },
95
+ "entryConditions": {
96
+ "onCriteriaFail": "skip",
97
+ "conditionText": "Skip outreach if no qualified rows are available.",
98
+ "criteria": [
99
+ { "variable": "{{steps.read-qualified.count}}", "operator": ">", "value": 0 }
100
+ ]
101
+ },
102
+ "loopConfig": {
103
+ "source": "executionContent",
104
+ "config": { "stepId": "read-qualified", "field": "listEntries" },
105
+ "ItemAlias": "qualifiedRow",
106
+ "loopCompletion": { "criteria": "all_items_processed" }
107
+ },
108
+ "creditCost": 0,
109
+ "next": { "stepId": "summarize-run" }
110
+ },
111
+ {
112
+ "id": "summarize-run",
113
+ "type": "aiAction",
114
+ "name": "Summarize run",
115
+ "pipelineStepPrompt": {
116
+ "template": "Create a concise internal funnel digest.\n\nNew rows read: {{steps.read-new.count}}\nQualified rows read: {{steps.read-qualified.count}}\nScoring results: {{steps.score-rows}}\nOutreach starts: {{steps.queue-outreach}}\n\nReturn JSON only.",
117
+ "responseStructure": {
118
+ "summary": "string",
119
+ "newCount": "number",
120
+ "qualifiedCount": "number",
121
+ "outreachQueuedCount": "number",
122
+ "recommendations": ["string"]
123
+ }
124
+ },
125
+ "creditCost": 3,
126
+ "next": { "stepId": "done" }
127
+ },
128
+ {
129
+ "id": "done",
130
+ "type": "milestone",
131
+ "name": "Funnel run complete"
132
+ }
133
+ ]
134
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "KG Process + Update (status state machine)",
3
+ "goal": "Read entities from a KG list filtered by status, process each in a loop, and flip status when done so the same row is never re-processed.",
4
+ "description": "Pattern 15b (processing half of the Source -> Process split) and pattern 12 (KG status as the API between workflows). Pair with `source-to-kg` as the producer. Loop processes one row at a time; after loop completion, `kg.update-rows` flips status to `processed` so the next run only sees what's still `new`.",
5
+ "status": "draft",
6
+ "context": {
7
+ "executionInputConfig": {
8
+ "title": "Process New Entities",
9
+ "description": "Process entities flagged as `new` in the shared KG list.",
10
+ "runCTALabel": "Process",
11
+ "allowRegeneration": false,
12
+ "fields": [
13
+ { "name": "list_key", "label": "KG list key", "type": "text", "required": true }
14
+ ]
15
+ }
16
+ },
17
+ "steps": [
18
+ {
19
+ "id": "schedule",
20
+ "type": "trigger",
21
+ "name": "Daily process",
22
+ "pipelineStepStartConditions": {
23
+ "trigger": {
24
+ "type": "schedule",
25
+ "description": "Run daily at 09:00 — picks up everything sourced overnight.",
26
+ "config": { "frequency": "daily", "time": "09:00" }
27
+ }
28
+ },
29
+ "next": { "stepId": "read-new" }
30
+ },
31
+ {
32
+ "id": "read-new",
33
+ "type": "appAction",
34
+ "name": "Read Rows with status: new",
35
+ "app": { "id": "kg", "actionId": "kg.read-list", "source": "native" },
36
+ "stepInputData": {
37
+ "listKey": "{{input.list_key}}",
38
+ "status": "new",
39
+ "limit": "100"
40
+ },
41
+ "next": { "stepId": "process" }
42
+ },
43
+ {
44
+ "id": "process",
45
+ "type": "aiAction",
46
+ "name": "Process Entity",
47
+ "loopConfig": {
48
+ "source": "executionContent",
49
+ "config": { "stepId": "read-new", "field": "listEntries" },
50
+ "ItemAlias": "row"
51
+ },
52
+ "pipelineStepPrompt": {
53
+ "template": "Process this entity. Replace this prompt with the real processing logic for your domain (enrich, score, classify, draft, etc.).\n\nEntity: {{row}}\n\nReturn the rowId verbatim plus any fields you want merged onto the KG row.",
54
+ "responseStructure": {
55
+ "rowId": "string — copy {{row.id}} verbatim so the update step can target this exact row",
56
+ "result": "object — fields to merge into the KG row (scores, decisions, derived data)"
57
+ }
58
+ },
59
+ "creditCost": 5,
60
+ "next": { "stepId": "mark-processed" }
61
+ },
62
+ {
63
+ "id": "mark-processed",
64
+ "type": "appAction",
65
+ "name": "Flip status -> processed",
66
+ "app": { "id": "kg", "actionId": "kg.update-rows", "source": "native" },
67
+ "entryConditions": {
68
+ "onCriteriaFail": "wait",
69
+ "conditionText": "Wait for the processing loop to finish before flipping status.",
70
+ "criteria": [
71
+ { "type": "loop_completion", "stepId": "process", "operator": "==", "value": true }
72
+ ]
73
+ },
74
+ "stepInputData": {
75
+ "listKey": "{{input.list_key}}",
76
+ "rowIds": "{{steps.process.rowIds}}",
77
+ "fieldUpdates": "{{steps.process.results}}",
78
+ "status": "processed"
79
+ },
80
+ "next": { "stepId": "done" }
81
+ },
82
+ {
83
+ "id": "done",
84
+ "type": "milestone",
85
+ "name": "Processing Done"
86
+ }
87
+ ]
88
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "Orchestrator: Loop call-workflow over KG",
3
+ "goal": "Read rows from a KG list, call a child workflow per row to score/process each, persist the results back to KG, then summarize.",
4
+ "description": "Pattern 11 (orchestrator) wired to a child built from the `child-with-return` scaffold. The KG list is the source of truth; the child encapsulates the per-row logic. Uses `entryConditions.type: \"loop_completion\"` with `onCriteriaFail: \"wait\"` so downstream steps block until every child finishes.",
5
+ "status": "draft",
6
+ "context": {
7
+ "executionInputConfig": {
8
+ "title": "Orchestrate Scoring",
9
+ "description": "Score every `new` row in the KG list by calling a child workflow per row.",
10
+ "runCTALabel": "Run",
11
+ "allowRegeneration": false,
12
+ "fields": [
13
+ { "name": "source_list_key", "label": "Source KG list key", "type": "text", "required": true },
14
+ { "name": "child_workflow_id", "label": "Child workflow id (the scoring workflow)", "type": "text", "required": true }
15
+ ]
16
+ }
17
+ },
18
+ "steps": [
19
+ {
20
+ "id": "start",
21
+ "type": "trigger",
22
+ "name": "Manual Start",
23
+ "pipelineStepStartConditions": { "trigger": { "type": "manual" } },
24
+ "next": { "stepId": "read-candidates" }
25
+ },
26
+ {
27
+ "id": "read-candidates",
28
+ "type": "appAction",
29
+ "name": "Read Candidates from KG",
30
+ "app": { "id": "kg", "actionId": "kg.read-list", "source": "native" },
31
+ "stepInputData": {
32
+ "listKey": "{{input.source_list_key}}",
33
+ "status": "new",
34
+ "limit": "50"
35
+ },
36
+ "next": { "stepId": "call-child" }
37
+ },
38
+ {
39
+ "id": "call-child",
40
+ "type": "appAction",
41
+ "name": "Score Row (child workflow)",
42
+ "loopConfig": {
43
+ "source": "executionContent",
44
+ "config": { "stepId": "read-candidates", "field": "listEntries" },
45
+ "ItemAlias": "row"
46
+ },
47
+ "app": { "id": "agentled", "actionId": "agentled.call-workflow", "source": "native" },
48
+ "stepInputData": {
49
+ "workflowId": "{{input.child_workflow_id}}",
50
+ "executionInputData": "{\"entity_id\": \"{{row.id}}\", \"entity_url\": \"{{row.url}}\"}",
51
+ "timeoutMinutes": "30"
52
+ },
53
+ "next": { "stepId": "persist-scores" }
54
+ },
55
+ {
56
+ "id": "persist-scores",
57
+ "type": "appAction",
58
+ "name": "Write Scores Back to KG",
59
+ "app": { "id": "kg", "actionId": "kg.update-rows", "source": "native" },
60
+ "entryConditions": {
61
+ "onCriteriaFail": "wait",
62
+ "conditionText": "Wait for every child call to complete before flipping status.",
63
+ "criteria": [
64
+ { "type": "loop_completion", "stepId": "call-child", "operator": "==", "value": true }
65
+ ]
66
+ },
67
+ "stepInputData": {
68
+ "listKey": "{{input.source_list_key}}",
69
+ "rowIds": "{{steps.call-child.rowIds}}",
70
+ "fieldUpdates": "{{steps.call-child.returnData}}",
71
+ "status": "scored"
72
+ },
73
+ "next": { "stepId": "summarize" }
74
+ },
75
+ {
76
+ "id": "summarize",
77
+ "type": "aiAction",
78
+ "name": "Summarize Run",
79
+ "pipelineStepPrompt": {
80
+ "template": "Summarize this orchestration run.\n\nRows processed: {{steps.read-candidates.count}}\nResults: {{steps.call-child.returnData}}\n\nReport totals, top-3 by score, and anything that looked anomalous.",
81
+ "responseStructure": {
82
+ "totals": "object — counts by decision bucket",
83
+ "top": "array — top 3 rows",
84
+ "notes": "string"
85
+ }
86
+ },
87
+ "creditCost": 5,
88
+ "next": { "stepId": "done" }
89
+ },
90
+ {
91
+ "id": "done",
92
+ "type": "milestone",
93
+ "name": "Orchestration Done"
94
+ }
95
+ ]
96
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "_scaffold": "source-from-platform",
3
+ "_description": "Source entities from a native app, normalize them, and upsert to KG with status:new. Pair with a downstream scoring workflow that reads status:new.",
4
+ "name": "Source from Platform",
5
+ "goal": "Source entities from a platform and write them to a Knowledge Graph list for downstream processing.",
6
+ "status": "draft",
7
+ "context": {
8
+ "executionInputConfig": {
9
+ "title": "Run Sourcing",
10
+ "description": "Source entities and add them to the Knowledge Graph.",
11
+ "runCTALabel": "Source",
12
+ "fields": [
13
+ { "name": "search_query", "label": "Search query", "type": "text", "required": true },
14
+ { "name": "target_list_key", "label": "Target list key", "type": "text", "required": true, "defaultValue": "leads", "description": "Knowledge list key (e.g. \"leads\", \"startups\")." }
15
+ ]
16
+ }
17
+ },
18
+ "steps": [
19
+ {
20
+ "id": "start",
21
+ "type": "trigger",
22
+ "name": "Scheduled sourcing",
23
+ "pipelineStepStartConditions": {
24
+ "trigger": {
25
+ "type": "schedule",
26
+ "description": "Run weekly.",
27
+ "config": { "frequency": "weekly", "dayOfWeek": "monday", "time": "08:00" }
28
+ }
29
+ },
30
+ "next": { "stepId": "source-entities" }
31
+ },
32
+ {
33
+ "id": "source-entities",
34
+ "type": "appAction",
35
+ "name": "Source entities",
36
+ "_comment": "Replace the app with the source action. Run: agentled apps for-source <platform>",
37
+ "app": { "id": "REPLACE_WITH_APP_ID", "actionId": "REPLACE_WITH_APP_ID.REPLACE_WITH_ACTION_ID", "source": "native" },
38
+ "stepInputData": {
39
+ "query": "{{input.search_query}}",
40
+ "limit": "25"
41
+ },
42
+ "creditCost": 2,
43
+ "next": { "stepId": "normalize-entities" }
44
+ },
45
+ {
46
+ "id": "normalize-entities",
47
+ "type": "aiAction",
48
+ "name": "Normalize entities",
49
+ "pipelineStepPrompt": {
50
+ "template": "Extract real entities from the source output.\n\nSource output:\n{{steps.source-entities}}\n\nRules:\n- Keep only identifiable entities.\n- Use a stable userKey, preferably domain, profile URL, email, or canonical source URL.\n- Set rowData.status to \"new\".\n- Include source, source_url, sourced_at, and enough evidence for a downstream scoring workflow.\n- Do not score, find contacts, or draft outreach here.\n\nReturn JSON only.",
51
+ "responseStructure": {
52
+ "rows": "array of { userKey: string, rowData: object }",
53
+ "count": "number"
54
+ }
55
+ },
56
+ "creditCost": 5,
57
+ "next": { "stepId": "save-entities" }
58
+ },
59
+ {
60
+ "id": "save-entities",
61
+ "type": "appAction",
62
+ "name": "Save entities",
63
+ "app": { "id": "kg", "actionId": "kg.upsert-rows", "source": "native" },
64
+ "stepInputData": {
65
+ "listKey": "{{input.target_list_key}}",
66
+ "rows": "{{steps.normalize-entities.rows}}",
67
+ "mergeStrategy": "merge",
68
+ "status": "new"
69
+ },
70
+ "next": { "stepId": "done" }
71
+ },
72
+ {
73
+ "id": "done",
74
+ "type": "milestone",
75
+ "name": "Sourcing complete"
76
+ }
77
+ ]
78
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "Source -> KG (idempotent sourcing)",
3
+ "goal": "Find entities and write them to a KG list with a stable userKey so re-running never produces duplicates.",
4
+ "description": "Pattern 15a (sourcing half of the Source -> Process split). Pair this scaffold with `kg-process-update` as the consumer. Uses `kg.upsert-rows` with `userKey` for O(1) dedup across runs and `status: \"new\"` so the processing workflow can filter cleanly.",
5
+ "status": "draft",
6
+ "context": {
7
+ "executionInputConfig": {
8
+ "title": "Source Entities",
9
+ "description": "Find entities and add them to the shared KG list.",
10
+ "runCTALabel": "Source",
11
+ "allowRegeneration": false,
12
+ "fields": [
13
+ { "name": "search_query", "label": "Search query / topic", "type": "text", "required": true },
14
+ { "name": "target_list_key", "label": "Target KG list key", "type": "text", "required": true }
15
+ ]
16
+ }
17
+ },
18
+ "steps": [
19
+ {
20
+ "id": "schedule",
21
+ "type": "trigger",
22
+ "name": "Weekly source",
23
+ "pipelineStepStartConditions": {
24
+ "trigger": {
25
+ "type": "schedule",
26
+ "description": "Run weekly on Monday morning.",
27
+ "config": { "frequency": "weekly", "dayOfWeek": "monday", "time": "08:00" }
28
+ }
29
+ },
30
+ "next": { "stepId": "generate-queries" }
31
+ },
32
+ {
33
+ "id": "generate-queries",
34
+ "type": "aiAction",
35
+ "name": "Generate Optimized Queries",
36
+ "pipelineStepPrompt": {
37
+ "template": "Generate 3 optimized search queries to find new entities matching: {{input.search_query}}\n\nUse boolean operators and filters appropriate to the target platform. Return a flat list of strings.",
38
+ "responseStructure": {
39
+ "queries": "array of strings"
40
+ }
41
+ },
42
+ "creditCost": 3,
43
+ "next": { "stepId": "search" }
44
+ },
45
+ {
46
+ "id": "search",
47
+ "type": "appAction",
48
+ "name": "Search (replace with target platform)",
49
+ "app": { "id": "REPLACE_WITH_APP_ID", "actionId": "REPLACE_WITH_APP_ID.REPLACE_WITH_ACTION_ID", "source": "native" },
50
+ "stepInputData": {
51
+ "queries": "{{steps.generate-queries.queries}}"
52
+ },
53
+ "creditCost": 2,
54
+ "next": { "stepId": "extract" }
55
+ },
56
+ {
57
+ "id": "extract",
58
+ "type": "aiAction",
59
+ "name": "Extract Entities",
60
+ "pipelineStepPrompt": {
61
+ "template": "Extract structured entity records from this raw source output.\n\nSource: {{steps.search.output}}\n\nEvery row MUST include a stable identifier (canonical URL, domain, or platform id) — this becomes the userKey for KG dedup. Drop rows you cannot uniquely identify.",
62
+ "responseStructure": {
63
+ "items": "array of { userKey: string (stable id e.g. canonical URL), rowData: { name, url, description, ...other fields } }"
64
+ }
65
+ },
66
+ "creditCost": 5,
67
+ "next": { "stepId": "save-to-kg" }
68
+ },
69
+ {
70
+ "id": "save-to-kg",
71
+ "type": "appAction",
72
+ "name": "Upsert to KG (status: new)",
73
+ "app": { "id": "kg", "actionId": "kg.upsert-rows", "source": "native" },
74
+ "stepInputData": {
75
+ "listKey": "{{input.target_list_key}}",
76
+ "rows": "{{steps.extract.items}}",
77
+ "mergeStrategy": "merge",
78
+ "status": "new"
79
+ },
80
+ "next": { "stepId": "done" }
81
+ },
82
+ {
83
+ "id": "done",
84
+ "type": "milestone",
85
+ "name": "Sourcing Done"
86
+ }
87
+ ]
88
+ }
@@ -42,6 +42,28 @@ After orientation, ask **at most three to five** targeted questions. Focus only
42
42
  - Every possible future integration question before the first useful slice is designed.
43
43
  - Anything answerable by running `agentled workspace inspect`.
44
44
 
45
+ ## Outreach scale and segmentation
46
+
47
+ For outbound, sales, investor, founder, or lead-generation workflows, classify the motion before designing the workflow. See `docs/OUTREACH_SCALE_GUIDANCE.md`.
48
+
49
+ **Core diagnostic:**
50
+ - 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.
51
+ - Daily volume = last 30 days sent / 22 working days.
52
+ - TAM = total reachable prospects in the CRM, KG list, enrichment tool, or target account universe.
53
+ - Reply ops = whether positive replies are handled quickly and followed up persistently.
54
+
55
+ **Tier guidance:**
56
+ - 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.
57
+ - 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.
58
+ - 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.
59
+ - Tier 4 (50K-100K/day, millions): require provider segmentation, live monitoring, sender reserve capacity, hourly testing cadence, and reply-handling SLAs before increasing volume.
60
+
61
+ **Workflow implications:**
62
+ - Small TAM problems are usually segmentation and signal-enrichment problems, not copy-only problems.
63
+ - Store `outreachAngle`, `offer`, `segment`, `campaignVariant`, `channelUsed`, `sender`, `contactedAt`, `replyStatus`, `meetingStatus`, and `positiveReplyAt` when the KG schema allows it.
64
+ - 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.
65
+ - Literal PCPL should use `type: "ratio"` with `ratioMode: "raw"`. Use the default percentage ratio behavior for rates such as `positive_replies / prospects_contacted`.
66
+
45
67
  ## Valid step types (closed list)
46
68
 
47
69
  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`.
@@ -111,6 +133,7 @@ agentled best-practices # summary + link to agentic-
111
133
  | **Outreach** — personalized email with user approval | `08-composed-email-approval` (outreachProfile + `pipelineStepPrompt.type: "email"` + `schedule-email`) | `list-match-email` |
112
134
  | **Report / dashboard** — structured output + sharing + KPI history | `09-reports-and-knowledge-storage` (Config renderer + share step + `knowledgeSync`) | `lead-scoring-kg`, `extract-threshold-alert` |
113
135
  | **Multi-workflow system** — workflows sharing KG state and status transitions | `12-event-driven-workflow-groups` (group manifest, state machines, build order) | — |
136
+ | **Entity pipeline group** — sourcing → score/qualify → find contact → outreach → scheduled orchestrator | `13-entity-pipeline-lifecycle` | `source-from-platform`, `lead-scoring-kg`, `list-match-email`, `funnel-orchestrator` |
114
137
 
115
138
  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.
116
139
 
@@ -420,7 +443,7 @@ agentled_<slug>/
420
443
  - Decisions ("we chose theme X for next edition", "we picked OpenVC + Crunchbase as initial sources") need a paper trail with reasoning. KG entries should carry the *outcome*, not the deliberation.
421
444
  - Workflow drafts often go through 3-5 iterations before push — keep them local until ready.
422
445
 
423
- **Sync rule:** at the end of any meaningful working session — and explicitly when the user says "save", "wrap up", "we're done for now" — summarize what's *durable and reusable* (final decisions, current themes, active sources, workflow purpose statements) and write it to the workspace KG via `upsert_knowledge_text` (for narrative docs) or `upsert_knowledge_rows` (for structured data). Use stable, predictable keys: `decisions.current-theme`, `groups.<slug>.spec`, `workflows.<id>.purpose`. Append a one-line entry to `worklog.md` recording what was synced and when.
446
+ **Sync rule:** at the end of any meaningful working session — and explicitly when the user says "save", "wrap up", "we're done for now" — summarize what's *durable and reusable* (final decisions, current themes, active sources, workflow purpose statements) and write it to the workspace KG via `upsert_knowledge_text` (for narrative docs) or `upsert_knowledge_rows` (for structured data). Use stable, predictable keys: `decisions.current-theme`, `groups.<slug>.spec`, `workflows.<id>.purpose`. For any group of workflows that forms a client-facing workspace pipeline, also create or update a client-readable KG text guide such as `<workspace-or-pipeline>.design`, `groups.<slug>.guide`, or `pipelines.<slug>.design`. Put the client explanation first (what it does, how it works, where data comes from, how approvals/sync/reporting work), then put workflow IDs, caveats, and debug details under "Operator Notes." Append a one-line entry to `worklog.md` recording what was synced and when.
424
447
 
425
448
  **Don't sync:** raw debug bundles, full execution timelines, abandoned drafts, transcripts. Those stay local. Sync only what a future session — or another agent — will actually need to pick up the work.
426
449