@ema.co/mcp-toolkit 2026.2.5 → 2026.2.19

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.

Potentially problematic release.


This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.

Files changed (67) hide show
  1. package/.context/public/guides/ema-user-guide.md +12 -16
  2. package/.context/public/guides/mcp-tools-guide.md +203 -334
  3. package/LICENSE +29 -21
  4. package/README.md +58 -35
  5. package/dist/mcp/domain/loop-detection.js +97 -0
  6. package/dist/mcp/domain/proto-constraints.js +284 -0
  7. package/dist/mcp/domain/structural-rules.js +12 -5
  8. package/dist/mcp/domain/validation-rules.js +107 -20
  9. package/dist/mcp/domain/workflow-graph-optimizer.js +235 -0
  10. package/dist/mcp/domain/workflow-graph-transforms.js +808 -0
  11. package/dist/mcp/domain/workflow-graph.js +374 -0
  12. package/dist/mcp/domain/workflow-optimizer.js +10 -4
  13. package/dist/mcp/guidance.js +54 -31
  14. package/dist/mcp/handlers/feedback/index.js +139 -0
  15. package/dist/mcp/handlers/feedback/store.js +262 -0
  16. package/dist/mcp/handlers/persona/index.js +237 -8
  17. package/dist/mcp/handlers/persona/schema.js +27 -0
  18. package/dist/mcp/handlers/reference/index.js +6 -4
  19. package/dist/mcp/handlers/workflow/index.js +25 -28
  20. package/dist/mcp/handlers/workflow/optimize.js +73 -33
  21. package/dist/mcp/handlers/workflow/validation.js +1 -1
  22. package/dist/mcp/knowledge-types.js +7 -0
  23. package/dist/mcp/knowledge.js +146 -834
  24. package/dist/mcp/resources.js +610 -18
  25. package/dist/mcp/server.js +233 -2156
  26. package/dist/mcp/tools.js +91 -5
  27. package/dist/sdk/generated/agent-catalog.js +615 -0
  28. package/dist/sdk/generated/deprecated-actions.js +182 -96
  29. package/dist/sdk/generated/proto-fields.js +2 -1
  30. package/dist/sdk/generated/protos/service/agent_qa/v1/agent_qa_pb.js +460 -21
  31. package/dist/sdk/generated/protos/service/auth/v1/auth_pb.js +11 -1
  32. package/dist/sdk/generated/protos/service/dataingest/v1/dataingest_pb.js +173 -66
  33. package/dist/sdk/generated/protos/service/feedback/v1/feedback_pb.js +43 -1
  34. package/dist/sdk/generated/protos/service/llmservice/v1/llmservice_pb.js +26 -21
  35. package/dist/sdk/generated/protos/service/persona/v1/persona_config_pb.js +100 -89
  36. package/dist/sdk/generated/protos/service/persona/v1/persona_pb.js +126 -116
  37. package/dist/sdk/generated/protos/service/persona/v1/shared_widgets/widget_types_pb.js +33 -1
  38. package/dist/sdk/generated/protos/service/persona/v1/voicebot_widgets/widget_types_pb.js +60 -11
  39. package/dist/sdk/generated/protos/service/tenant/v1/tenant_pb.js +1 -1
  40. package/dist/sdk/generated/protos/service/user/v1/user_pb.js +1 -1
  41. package/dist/sdk/generated/protos/service/utils/v1/agent_qa_pb.js +35 -0
  42. package/dist/sdk/generated/protos/service/workflows/v1/action_registry_pb.js +1 -1
  43. package/dist/sdk/generated/protos/service/workflows/v1/action_type_pb.js +6 -1
  44. package/dist/sdk/generated/protos/service/workflows/v1/chatbot_pb.js +106 -11
  45. package/dist/sdk/generated/protos/service/workflows/v1/common_forms_pb.js +1 -1
  46. package/dist/sdk/generated/protos/service/workflows/v1/coordinator_pb.js +1 -1
  47. package/dist/sdk/generated/protos/service/workflows/v1/external_actions_pb.js +31 -1
  48. package/dist/sdk/generated/protos/service/workflows/v1/well_known_pb.js +5 -1
  49. package/dist/sdk/generated/protos/service/workflows/v1/workflow_pb.js +1 -1
  50. package/dist/sdk/generated/protos/util/tracking_metadata_pb.js +1 -1
  51. package/dist/sdk/generated/widget-catalog.js +60 -0
  52. package/docs/README.md +17 -9
  53. package/package.json +2 -2
  54. package/.context/public/guides/dashboard-operations.md +0 -286
  55. package/.context/public/guides/email-patterns.md +0 -125
  56. package/dist/mcp/domain/intent-architect.js +0 -914
  57. package/dist/mcp/domain/quality-gates.js +0 -110
  58. package/dist/mcp/domain/workflow-execution-analyzer.js +0 -412
  59. package/dist/mcp/domain/workflow-intent.js +0 -1806
  60. package/dist/mcp/domain/workflow-merge.js +0 -449
  61. package/dist/mcp/domain/workflow-tracer.js +0 -648
  62. package/dist/mcp/domain/workflow-transformer.js +0 -742
  63. package/dist/mcp/handlers/persona/intent.js +0 -141
  64. package/dist/mcp/handlers/workflow/analyze.js +0 -119
  65. package/dist/mcp/handlers/workflow/compare.js +0 -70
  66. package/dist/mcp/handlers/workflow/generate.js +0 -384
  67. package/dist/mcp/handlers-consolidated.js +0 -333
@@ -33,6 +33,7 @@ const __dirname = dirname(__filename);
33
33
  import { AGENT_CATALOG, WORKFLOW_PATTERNS, WIDGET_CATALOG, ALL_DEPRECATED_ACTIONS, DEPRECATED_ACTIONS_WITH_REPLACEMENT, DEPRECATED_ACTIONS_NO_REPLACEMENT, WORKFLOW_ENABLING_CONSTRAINTS, MINIMUM_VIABLE_WORKFLOWS, } from "./knowledge.js";
34
34
  import { INPUT_SOURCE_RULES, ANTI_PATTERNS, OPTIMIZATION_RULES } from "./domain/validation-rules.js";
35
35
  import { STRUCTURAL_INVARIANTS } from "./domain/structural-rules.js";
36
+ import { mineConstraints, formatConstraintReport } from "./domain/proto-constraints.js";
36
37
  import { EmaClient } from "../sdk/client.js";
37
38
  import { APISchemaRegistry } from "./domain/workflow-validator.js";
38
39
  import { loadConfigFromJsonEnv, loadConfigOptional, resolveBearerToken, getEnvByName, getMasterEnv, } from "../sdk/config.js";
@@ -231,6 +232,17 @@ const DYNAMIC_RESOURCES = [
231
232
  mimeType: "text/markdown",
232
233
  generate: async () => generateBestPracticesChecklist(),
233
234
  },
235
+ // Field Constraints - Mined from proto definitions
236
+ {
237
+ uri: "ema://docs/field-constraints",
238
+ name: "docs/field-constraints",
239
+ description: "Proto field constraints: immutable fields, read-only fields, creation-only fields, conditional fields. Mined from proto definitions.",
240
+ mimeType: "text/markdown",
241
+ generate: async () => {
242
+ const report = mineConstraints();
243
+ return formatConstraintReport(report);
244
+ },
245
+ },
234
246
  // Deprecated Actions - API-first with fallback
235
247
  {
236
248
  uri: "ema://rules/deprecated-actions",
@@ -373,11 +385,20 @@ const DYNAMIC_RESOURCES = [
373
385
  }
374
386
  md += "\n";
375
387
  }
376
- md += "## Critical Rule: WORKFLOW_OUTPUT\n\n";
377
- md += "**Every workflow must have a `results.WORKFLOW_OUTPUT` that maps to an action output.**\n\n";
388
+ md += "## Critical Rule: results Mapping\n\n";
389
+ md += "**Every workflow must have a `results` map connecting action outputs to the persona's output.**\n\n";
390
+ md += "### Chat / Voice Personas\n\n";
391
+ md += "Use `WORKFLOW_OUTPUT` as the key — this is the response shown to the user:\n\n";
378
392
  md += "```json\n";
379
393
  md += '{\n "results": {\n "WORKFLOW_OUTPUT": {\n "actionName": "respond_node",\n "outputName": "response_with_sources"\n }\n }\n}\n';
380
- md += "```\n";
394
+ md += "```\n\n";
395
+ md += "Multiple response nodes (e.g., one per categorizer branch) can all map to `WORKFLOW_OUTPUT` — only the one that executes via `runIf` will produce output.\n\n";
396
+ md += "### Dashboard Personas\n\n";
397
+ md += "Use `<actionName>.<outputName>` dot-notation keys — each key becomes a column in the dashboard:\n\n";
398
+ md += "```json\n";
399
+ md += '{\n "results": {\n "entity_extraction_node.extraction_columns": {\n "actionName": "entity_extraction_node",\n "outputName": "extraction_columns"\n },\n "rule_validation_node.ruleset_output": {\n "actionName": "rule_validation_node",\n "outputName": "ruleset_output"\n }\n }\n}\n';
400
+ md += "```\n\n";
401
+ md += "Common dashboard result outputs: `extraction_columns`, `ruleset_output`, `llm_output`.\n";
381
402
  return md;
382
403
  },
383
404
  },
@@ -538,9 +559,358 @@ When generating or fixing workflow_def that includes \`entity_extraction_with_do
538
559
  - **Within the same extraction_columns array**, every column must have a **unique \`id\`**. Duplicate \`id\` values in one array are invalid.
539
560
  - Different actions can reuse the same id strings (e.g. \`event_extraction\` has \`col1\`..\`col6\`, \`validation_results\` has \`col1\`..\`col10\`) — each action has its own array.
540
561
 
541
- ## Object / nested columns
562
+ ## dataType Reference (PrimitiveType enum)
563
+
564
+ | Value | Type | Description |
565
+ |-------|------|-------------|
566
+ | 0 | UNKNOWN | Unspecified (avoid) |
567
+ | 1 | STRING | Text values (default) |
568
+ | 2 | NUMBER | Numeric values |
569
+ | 3 | BOOLEAN | True/false values |
570
+ | 4 | ARRAY | Array of repeated values (use with \`arrayElementType\`) |
571
+ | 5 | OBJECT | Grouped/nested structure (contains sub-columns via \`value.objectValue\`) |
572
+ | 6 | DATE | Date values (use with \`formattingOptions.dateFormat\`) |
573
+ | 7 | DOCUMENT_VERSION_ID | Reference to a document version |
574
+
575
+ **Note on enums:** To create an enumerated column, use \`dataType: 1\` (STRING) with \`isEnum: true\` and populate \`possibleValues\`.
576
+
577
+ ## Object / Grouped Columns (dataType: 5)
578
+
579
+ Use \`dataType: 5\` to create a **Group** column with sub-columns (nested structure). In the UI, this appears as a parent column with expandable sub-columns.
580
+
581
+ The sub-columns are defined as a nested array in the column's \`value.objectValue.values\`. Each sub-column is a **bare \`ExtractionColumn\`** (no \`wellKnown\` wrapper — that wrapper only appears at the top-level \`extraction_columns.inline.array.values[]\`).
582
+
583
+ \`\`\`json
584
+ {
585
+ "wellKnown": {
586
+ "extractionColumn": {
587
+ "id": "address",
588
+ "name": "Address",
589
+ "description": "Structured address fields",
590
+ "dataType": 5,
591
+ "value": {
592
+ "objectValue": {
593
+ "values": [
594
+ {
595
+ "id": "street",
596
+ "name": "Street Name",
597
+ "description": "Street name",
598
+ "dataType": 1,
599
+ "detailedDescription": "",
600
+ "isEnum": false,
601
+ "possibleValues": [],
602
+ "dependencies": [],
603
+ "fileSources": [],
604
+ "auxiliarySources": []
605
+ },
606
+ {
607
+ "id": "city",
608
+ "name": "City",
609
+ "dataType": 1,
610
+ "detailedDescription": "",
611
+ "isEnum": false,
612
+ "possibleValues": [],
613
+ "dependencies": [],
614
+ "fileSources": [],
615
+ "auxiliarySources": []
616
+ },
617
+ {
618
+ "id": "postal_code",
619
+ "name": "Postal Code",
620
+ "dataType": 1,
621
+ "detailedDescription": "",
622
+ "isEnum": false,
623
+ "possibleValues": [],
624
+ "dependencies": [],
625
+ "fileSources": [],
626
+ "auxiliarySources": []
627
+ }
628
+ ]
629
+ }
630
+ },
631
+ "arrayElementType": {
632
+ "id": "address",
633
+ "name": "Address",
634
+ "dataType": 5,
635
+ "detailedDescription": "",
636
+ "isEnum": false,
637
+ "possibleValues": [],
638
+ "dependencies": [],
639
+ "fileSources": [],
640
+ "auxiliarySources": []
641
+ },
642
+ "detailedDescription": "",
643
+ "isEnum": false,
644
+ "possibleValues": [],
645
+ "dependencies": [],
646
+ "fileSources": [],
647
+ "auxiliarySources": []
648
+ }
649
+ }
650
+ }
651
+ \`\`\`
652
+
653
+ **Key rules for grouped columns:**
654
+ - Parent column uses \`dataType: 5\` (OBJECT)
655
+ - Sub-columns live in \`value.objectValue.values\` as **bare ExtractionColumn objects** (no \`wellKnown\` wrapper)
656
+ - Each sub-column needs its own unique \`id\` within the group
657
+ - Sub-columns can themselves be groups (nested groups) up to the nesting depth limit
658
+ - The UI shows the parent as a collapsible group header with sub-columns underneath
659
+ - **Auto-populated \`arrayElementType\`:** The API may auto-populate a sparse \`arrayElementType\` on OBJECT columns (with \`id\`, \`name\`, \`dataType: 5\` but NO sub-columns). This is normal — include it when copying from a working persona but it is not required when creating new columns.
660
+
661
+ **Default field pattern:** Live API data always includes these fields on every column, even when empty. Include them for consistency:
662
+ \`\`\`
663
+ "detailedDescription": "", "isEnum": false, "possibleValues": [],
664
+ "dependencies": [], "fileSources": [], "auxiliarySources": []
665
+ \`\`\`
666
+
667
+ **IMPORTANT — wellKnown wrapping rules:**
668
+ - \`extraction_columns.inline.array.values[]\` = \`Value\` proto type → each entry needs \`{ "wellKnown": { "extractionColumn": {...} } }\`
669
+ - \`objectValue.values[]\` inside a column = \`ExtractionColumn[]\` proto type → bare \`{ "id": "...", "name": "...", "dataType": ... }\`
670
+ - \`arrayElementType\` = \`ExtractionColumn\` proto type → bare \`{ "dataType": ..., ... }\`
542
671
 
543
- - For object-type columns (e.g. \`dataType: 5\`), use \`value.objectValue.values\` with nested \`extractionColumn\` entries. Each nested entry also needs unique \`id\` within that nested list.
672
+ ## Multi-Value / Array Columns (dataType: 4)
673
+
674
+ Use \`dataType: 4\` (ARRAY) with \`arrayElementType\` to extract **repeated values** from a document (e.g., multiple phone numbers, a list of names).
675
+
676
+ The \`arrayElementType\` field is a bare \`ExtractionColumn\` (no \`wellKnown\` wrapper). It typically includes \`id\` and \`name\` (often repeating the parent's values) plus the element \`dataType\`.
677
+
678
+ ### Simple Array (repeated scalar values)
679
+
680
+ Extract multiple values of the same primitive type (e.g., list of invoice IDs):
681
+
682
+ \`\`\`json
683
+ {
684
+ "wellKnown": {
685
+ "extractionColumn": {
686
+ "id": "invoice_ids",
687
+ "name": "Invoice(s)",
688
+ "description": "Invoice IDs as a list",
689
+ "dataType": 4,
690
+ "arrayElementType": {
691
+ "id": "invoice_ids",
692
+ "name": "Invoice(s)",
693
+ "description": "Single invoice ID like INV-2024-1423",
694
+ "dataType": 1,
695
+ "isEnum": false,
696
+ "possibleValues": [],
697
+ "dependencies": [],
698
+ "fileSources": [],
699
+ "auxiliarySources": []
700
+ }
701
+ }
702
+ }
703
+ }
704
+ \`\`\`
705
+
706
+ ### Array of Objects (repeated grouped values)
707
+
708
+ Extract multiple structured items (e.g., line items from an invoice). Combine \`dataType: 4\` with an \`arrayElementType\` of \`dataType: 5\` (OBJECT). Sub-columns inside \`objectValue.values\` are **bare ExtractionColumn objects** (no \`wellKnown\` wrapper):
709
+
710
+ \`\`\`json
711
+ {
712
+ "wellKnown": {
713
+ "extractionColumn": {
714
+ "id": "line_items",
715
+ "name": "Items",
716
+ "description": "All line items from the invoice",
717
+ "dataType": 4,
718
+ "value": {
719
+ "objectValue": {
720
+ "values": [
721
+ {
722
+ "id": "item_name",
723
+ "name": "Item Name",
724
+ "dataType": 1,
725
+ "description": "",
726
+ "detailedDescription": "",
727
+ "isEnum": false,
728
+ "possibleValues": [],
729
+ "dependencies": [],
730
+ "fileSources": [],
731
+ "auxiliarySources": []
732
+ },
733
+ {
734
+ "id": "quantity",
735
+ "name": "Quantity",
736
+ "dataType": 2,
737
+ "description": "",
738
+ "detailedDescription": "",
739
+ "isEnum": false,
740
+ "possibleValues": [],
741
+ "dependencies": [],
742
+ "fileSources": [],
743
+ "auxiliarySources": []
744
+ },
745
+ {
746
+ "id": "unit_price",
747
+ "name": "Unit Price",
748
+ "dataType": 2,
749
+ "description": "",
750
+ "detailedDescription": "",
751
+ "isEnum": false,
752
+ "possibleValues": [],
753
+ "dependencies": [],
754
+ "fileSources": [],
755
+ "auxiliarySources": []
756
+ }
757
+ ]
758
+ }
759
+ },
760
+ "arrayElementType": {
761
+ "id": "line_items",
762
+ "name": "Items",
763
+ "dataType": 5,
764
+ "value": {
765
+ "objectValue": {
766
+ "values": [
767
+ {
768
+ "id": "item_name",
769
+ "name": "Item Name",
770
+ "dataType": 1,
771
+ "description": "",
772
+ "detailedDescription": "",
773
+ "isEnum": false,
774
+ "possibleValues": [],
775
+ "dependencies": [],
776
+ "fileSources": [],
777
+ "auxiliarySources": []
778
+ },
779
+ {
780
+ "id": "quantity",
781
+ "name": "Quantity",
782
+ "dataType": 2,
783
+ "description": "",
784
+ "detailedDescription": "",
785
+ "isEnum": false,
786
+ "possibleValues": [],
787
+ "dependencies": [],
788
+ "fileSources": [],
789
+ "auxiliarySources": []
790
+ },
791
+ {
792
+ "id": "unit_price",
793
+ "name": "Unit Price",
794
+ "dataType": 2,
795
+ "description": "",
796
+ "detailedDescription": "",
797
+ "isEnum": false,
798
+ "possibleValues": [],
799
+ "dependencies": [],
800
+ "fileSources": [],
801
+ "auxiliarySources": []
802
+ }
803
+ ]
804
+ }
805
+ },
806
+ "description": "",
807
+ "detailedDescription": "",
808
+ "isEnum": false,
809
+ "possibleValues": [],
810
+ "dependencies": [],
811
+ "fileSources": [],
812
+ "auxiliarySources": []
813
+ },
814
+ "detailedDescription": "",
815
+ "isEnum": false,
816
+ "possibleValues": [],
817
+ "dependencies": [],
818
+ "fileSources": [],
819
+ "auxiliarySources": []
820
+ }
821
+ }
822
+ }
823
+ \`\`\`
824
+
825
+ **Key rules for array columns:**
826
+ - Parent column uses \`dataType: 4\` (ARRAY)
827
+ - \`arrayElementType\` is a bare \`ExtractionColumn\` (no \`wellKnown\` wrapper) — repeats the parent's \`id\` and \`name\`, plus the element \`dataType\`
828
+ - For simple arrays: \`arrayElementType.dataType\` = scalar type (1=STRING, 2=NUMBER, etc.)
829
+ - For arrays of objects: \`arrayElementType.dataType: 5\` with \`value.objectValue.values\` containing bare sub-columns
830
+ - **Dual storage pattern:** For array-of-objects, the sub-columns appear in BOTH \`value.objectValue.values\` (on the parent) AND \`arrayElementType.value.objectValue.values\`. Both sets must match. The \`arrayElementType\` is the canonical schema; the parent \`value.objectValue\` may be auto-populated by the API.
831
+ - Sub-columns inside \`objectValue.values\` are bare ExtractionColumn objects (no \`wellKnown\` wrapper)
832
+ - The UI labels this as "Multi Value" — in the API, it maps to \`dataType: 4\` + \`arrayElementType\`
833
+ - **There is no \`isMultiValue\` API field** — that is a UI-only label. Always use \`dataType: 4\` + \`arrayElementType\` in workflow_def.
834
+
835
+ ## Creation-Time Constraints
836
+
837
+ **IMPORTANT:** The following extraction column properties are set at creation time and **cannot be changed** after the column exists:
838
+
839
+ | Property | Constraint |
840
+ |----------|-----------|
841
+ | \`dataType\` | Cannot change after creation (e.g., STRING to ARRAY) |
842
+ | \`arrayElementType\` | Cannot change after creation; defines the array element schema |
843
+ | \`value.objectValue\` structure | Sub-column schema is fixed at creation |
844
+
845
+ To change these properties, you must **delete the column and recreate it** with the new settings.
846
+
847
+ Other properties (\`name\`, \`description\`, \`possibleValues\`, \`formattingOptions\`) can be updated after creation.
848
+
849
+ ## Enum Columns
850
+
851
+ To create a column with a fixed set of allowed values, use \`dataType: 1\` (STRING) with \`isEnum: true\` and \`possibleValues\`:
852
+
853
+ \`\`\`json
854
+ {
855
+ "wellKnown": {
856
+ "extractionColumn": {
857
+ "id": "priority",
858
+ "name": "Priority",
859
+ "description": "Task priority level",
860
+ "dataType": 1,
861
+ "isEnum": true,
862
+ "possibleValues": [
863
+ { "value": { "stringValue": "High", "case": "stringValue" } },
864
+ { "value": { "stringValue": "Medium", "case": "stringValue" } },
865
+ { "value": { "stringValue": "Low", "case": "stringValue" } }
866
+ ]
867
+ }
868
+ }
869
+ }
870
+ \`\`\`
871
+
872
+ ## Document Input Wiring (Dashboard Personas)
873
+
874
+ Dashboard extraction workflows receive documents through one of two patterns:
875
+
876
+ ### Pattern 1: workflowInput (preferred for newer workflows)
877
+
878
+ \`\`\`json
879
+ "workflowInputs": {
880
+ "document-mmf2": {
881
+ "type": { "arrayType": { "wellKnownType": 5, "isList": false }, "isList": false },
882
+ "displayName": "Document"
883
+ }
884
+ }
885
+ \`\`\`
886
+
887
+ Then in the extraction node:
888
+ \`\`\`json
889
+ "documents": { "workflowInput": { "inputName": "document-mmf2" } }
890
+ \`\`\`
891
+
892
+ ### Pattern 2: trigger output (older workflows)
893
+
894
+ \`\`\`json
895
+ "documents": { "actionOutput": { "actionName": "trigger", "output": "user_query" } }
896
+ \`\`\`
897
+
898
+ Note: The output name \`user_query\` is used for documents in older \`document_trigger\` workflows — despite the misleading name, it carries the uploaded document content.
899
+
900
+ ## Extraction Node Options
901
+
902
+ ### disableHumanInteraction
903
+
904
+ Set to \`true\` to skip the human-in-the-loop review step for extraction results. Default is \`false\` (HITL enabled):
905
+
906
+ \`\`\`json
907
+ {
908
+ "name": "entity_extraction_node",
909
+ "action": { "name": { "namespaces": ["actions", "emainternal"], "name": "entity_extraction_with_documents" }, "version": "v0" },
910
+ "inputs": { ... },
911
+ "disableHumanInteraction": true
912
+ }
913
+ \`\`\`
544
914
 
545
915
  ## How to Use
546
916
 
@@ -696,16 +1066,20 @@ Chat workflows that process \`chat_conversation\` through search, extraction, or
696
1066
  \`\`\`
697
1067
  chat_trigger.chat_conversation → conversation_to_search_query.conversation
698
1068
  conversation_to_search_query.summarized_conversation → search.query
699
- search.search_results → respond_with_sources.search_results
700
- chat_trigger.user_query → respond_with_sources.query
701
- respond_with_sources.response_with_sourcesWORKFLOW_OUTPUT
1069
+ search.search_results → respond_for_external_actions.external_action_result
1070
+ chat_trigger.user_query → respond_for_external_actions.query
1071
+ chat_trigger.chat_conversationrespond_for_external_actions.conversation
1072
+ respond_for_external_actions.response → WORKFLOW_OUTPUT
702
1073
  \`\`\`
703
1074
 
1075
+ > **Note**: \`respond_with_sources/v0\` is DEPRECATED. Use \`respond_for_external_actions\` for all new workflows (search results, tool results, or any context-aware response).
1076
+
704
1077
  ### Pattern 2: Tool/Action + Respond
705
1078
  \`\`\`
706
1079
  chat_trigger.chat_conversation → external_action_caller.conversation
707
1080
  external_action_caller.tool_execution_result → respond_for_external_actions.external_action_result
708
1081
  chat_trigger.user_query → respond_for_external_actions.query
1082
+ chat_trigger.chat_conversation → respond_for_external_actions.conversation
709
1083
  respond_for_external_actions.response → WORKFLOW_OUTPUT
710
1084
  \`\`\`
711
1085
 
@@ -732,16 +1106,18 @@ chat_trigger → entity_extraction → call_llm (stateless) → WORKFLOW_OUTPUT
732
1106
 
733
1107
  ## Key Insight
734
1108
 
735
- \`respond_with_sources\` and \`respond_for_external_actions\` are **conversation-aware by design** — they automatically incorporate conversation context when processing the \`query\` input from \`chat_trigger.user_query\`. Generic \`call_llm\` does not.
1109
+ \`respond_for_external_actions\` is **conversation-aware by design** — it incorporates conversation context via its \`conversation\` input. Generic \`call_llm\` does not — you must manually wire \`chat_conversation\` via \`named_inputs\`.
1110
+
1111
+ > **Deprecated**: \`respond_with_sources/v0\` — use \`respond_for_external_actions\` for all new workflows.
736
1112
 
737
1113
  ## When to Use Each Response Node
738
1114
 
739
1115
  | Scenario | Response Node | Why |
740
1116
  |----------|---------------|-----|
741
- | KB/document Q&A | \`respond_with_sources\` | Handles SEARCH_RESULT type, adds citations |
1117
+ | KB/document Q&A | \`respond_for_external_actions\` | Handles search results, conversation-aware |
742
1118
  | Tool/API results | \`respond_for_external_actions\` | Explains tool results in conversation context |
743
- | Complex multi-step reasoning | \`call_llm\` + named_inputs with chat_conversation | Full control but requires manual history wiring |
744
- | Static/template response | \`fixed_response\` | No LLM needed, just template + variables |
1119
+ | Complex multi-step reasoning | \`call_llm/v2\` + named_inputs with chat_conversation | Full control but requires manual history wiring |
1120
+ | Static/template response | \`fixed_response/v1\` | No LLM needed, just template + variables |
745
1121
  `;
746
1122
  },
747
1123
  },
@@ -879,6 +1255,221 @@ When you already have a clean extracted value and just need TEXT_WITH_SOURCES wr
879
1255
  },
880
1256
  },
881
1257
  // ─────────────────────────────────────────────────────────────────────────────
1258
+ // Workflow Node Reference - per-node I/O types, semantics, categorizer patterns
1259
+ // Consolidated from .context/public/guides/workflow-builder-patterns.md
1260
+ // ─────────────────────────────────────────────────────────────────────────────
1261
+ {
1262
+ uri: "ema://docs/workflow-node-reference",
1263
+ name: "docs/workflow-node-reference",
1264
+ description: "Complete workflow node reference: per-node I/O types, categorizer patterns (runIf/enumTypes), " +
1265
+ "LLM named_inputs convention, temperature guidelines, data types between nodes",
1266
+ mimeType: "text/markdown",
1267
+ generate: async () => {
1268
+ let md = `# Workflow Node Reference\n\n`;
1269
+ md += `Practical reference for building Ema workflows. Covers per-node I/O semantics, `;
1270
+ md += `categorizer configuration, LLM patterns, and data type compatibility.\n\n`;
1271
+ md += `## Data Types Between Nodes\n\n`;
1272
+ md += `| Type | Code Constant | Description | Example |\n`;
1273
+ md += `|------|---------------|-------------|---------|\n`;
1274
+ md += `| **Plain Text** | \`WELL_KNOWN_TYPE_TEXT_WITH_SOURCES\` | Text with optional citation metadata | User's message, LLM response |\n`;
1275
+ md += `| **Conversation** | \`WELL_KNOWN_TYPE_CHAT_CONVERSATION\` | Structured message history (role + content) | Full chat thread |\n`;
1276
+ md += `| **Search Results** | \`WELL_KNOWN_TYPE_SEARCH_RESULT\` | Retrieved document chunks with citations | KB search results |\n`;
1277
+ md += `| **Enum** | \`WELL_KNOWN_TYPE_ENUM\` | Category/classification signal for routing | \`category::Schedule Appointment\` |\n`;
1278
+ md += `| **Document** | \`WELL_KNOWN_TYPE_DOCUMENT\` | Uploaded file content | PDF, DOCX for extraction |\n`;
1279
+ md += `| **Any** | \`WELL_KNOWN_TYPE_ANY\` | Untyped — needs intermediary for type-safe wiring | entity_extraction output |\n\n`;
1280
+ md += `**Critical**: Types are NOT interchangeable. \`CHAT_CONVERSATION\` into a \`TEXT_WITH_SOURCES\` input causes type mismatch errors. Use converter nodes when needed.\n\n`;
1281
+ md += `---\n\n`;
1282
+ md += `## Input Semantics by Context\n\n`;
1283
+ md += `The same input name means different things depending on the node:\n\n`;
1284
+ md += `| Input Name | In Search Nodes | In Respond Nodes | In Extract Nodes | In Categorizers |\n`;
1285
+ md += `|------------|-----------------|-------------------|-------------------|------------------|\n`;
1286
+ md += `| \`query\` | Search term to look up | User's question to answer | Source text to analyze | N/A |\n`;
1287
+ md += `| \`conversation\` | N/A | N/A (use named_inputs) | N/A | Full history for classification |\n`;
1288
+ md += `| \`trigger_when\` | N/A | "Should I run?" | "Should I run?" | N/A |\n`;
1289
+ md += `| \`named_inputs_*\` | N/A | Additional context | Additional context | N/A |\n\n`;
1290
+ md += `**Mental model:** \`query\` = "What to process?" · \`conversation\` = "Full context" · \`trigger_when\` = "Should I run?" · \`named_inputs_*\` = "Extra context"\n\n`;
1291
+ md += `---\n\n`;
1292
+ md += `## Node Type Reference\n\n`;
1293
+ md += `### chat_trigger\n`;
1294
+ md += `Entry point for chat/voice workflows.\n`;
1295
+ md += `- **Inputs**: None (system event)\n`;
1296
+ md += `- **Outputs**: \`user_query\` (TEXT_WITH_SOURCES), \`chat_conversation\` (CHAT_CONVERSATION)\n`;
1297
+ md += `- **Pairs with**: Intent routers, search nodes, LLM nodes\n\n`;
1298
+ md += `### document_trigger\n`;
1299
+ md += `Entry point for dashboard workflows (file upload per row).\n`;
1300
+ md += `- **Inputs**: None (system event — triggered when row created/file uploaded)\n`;
1301
+ md += `- **Outputs**: \`document_content\` (DOCUMENT), \`row_data\` (ANY — column values)\n`;
1302
+ md += `- **Pairs with**: entity_extraction_with_documents, call_llm, search\n`;
1303
+ md += `- **Critical**: Dashboard personas only. Each row triggers one workflow execution.\n\n`;
1304
+ md += `### chat_categorizer\n`;
1305
+ md += `Classify conversation into intent categories for routing.\n`;
1306
+ md += `- **Inputs**: \`conversation\` (CHAT_CONVERSATION) — must be full history, NOT user_query\n`;
1307
+ md += `- **Outputs**: \`category\` (ENUM) — one per configured category\n`;
1308
+ md += `- **Critical**: Always include Fallback category. Every category needs a handler. Must have \`typeArguments.categories\` pointing to enumType.\n\n`;
1309
+ md += `### text_categorizer/v1\n`;
1310
+ md += `Classify text content (not conversation) for routing.\n`;
1311
+ md += `- **Inputs**: \`named_inputs\` (multiBinding)\n`;
1312
+ md += `- **Outputs**: \`category\` (ENUM)\n`;
1313
+ md += `- **Critical**: \`text_categorizer/v0\` is deprecated — use v1 with \`named_inputs\`.\n\n`;
1314
+ md += `### search/v2\n`;
1315
+ md += `Retrieve relevant documents from uploaded knowledge base.\n`;
1316
+ md += `- **Inputs**: \`query\` (TEXT_WITH_SOURCES), \`datastore_configs\` (from widget)\n`;
1317
+ md += `- **Outputs**: \`search_results\` (SEARCH_RESULT)\n`;
1318
+ md += `- **Critical**: NOT an LLM node — do NOT include \`model_config\`. Data must be uploaded first. \`search/v0\` is deprecated.\n\n`;
1319
+ md += `### conversation_to_search_query\n`;
1320
+ md += `Convert multi-turn conversation to a search-optimized query.\n`;
1321
+ md += `- **Inputs**: \`conversation\` (CHAT_CONVERSATION)\n`;
1322
+ md += `- **Outputs**: \`summarized_conversation\` (TEXT_WITH_SOURCES)\n`;
1323
+ md += `- **Critical**: Required for multi-turn chat search. Direct CHAT_CONVERSATION → search causes type mismatch.\n\n`;
1324
+ md += `### call_llm/v2\n`;
1325
+ md += `Generate natural language response using LLM.\n`;
1326
+ md += `- **Inputs**: \`query\` (TEXT_WITH_SOURCES), \`named_inputs_*\` (ANY — flexible), \`trigger_when\` (ENUM)\n`;
1327
+ md += `- **Outputs**: \`response_with_sources\` (TEXT_WITH_SOURCES)\n`;
1328
+ md += `- **Critical**: Must wire to WORKFLOW_OUTPUT or response is lost. \`call_llm/v0\` is deprecated.\n\n`;
1329
+ md += `### respond_for_external_actions\n`;
1330
+ md += `Generate conversation-aware response from search results or tool outputs.\n`;
1331
+ md += `- **Inputs**: \`query\` (TEXT_WITH_SOURCES), \`conversation\` (CHAT_CONVERSATION), \`external_action_result\` (TEXT_WITH_SOURCES/SEARCH_RESULT)\n`;
1332
+ md += `- **Outputs**: \`response\` (TEXT_WITH_SOURCES)\n`;
1333
+ md += `- **Critical**: Replaces deprecated \`respond_with_sources/v0\`. Has built-in citation handling and conversation awareness.\n\n`;
1334
+ md += `### fixed_response/v1\n`;
1335
+ md += `Return a static predefined message.\n`;
1336
+ md += `- **Inputs**: \`trigger_when\` (ENUM), \`named_inputs_*\` for template variables\n`;
1337
+ md += `- **Outputs**: \`fixed_response_with_sources\` (TEXT_WITH_SOURCES)\n`;
1338
+ md += `- **Pairs with**: Categorizers (as fallback), type conversion with \`{{variables}}\` templates, send_email_agent\n\n`;
1339
+ md += `### external_action_caller\n`;
1340
+ md += `Call external APIs/tools (ServiceNow, Salesforce, calendars).\n`;
1341
+ md += `- **Inputs**: \`query\` (TEXT_WITH_SOURCES), \`conversation\` (CHAT_CONVERSATION), tool configuration\n`;
1342
+ md += `- **Outputs**: \`tool_execution_result\` (TEXT_WITH_SOURCES)\n`;
1343
+ md += `- **Pairs with**: Entity extractors (for parameters), respond nodes (for result formatting)\n\n`;
1344
+ md += `### entity_extraction_with_documents\n`;
1345
+ md += `Extract structured entities grounded in provided documents.\n`;
1346
+ md += `- **Inputs**: \`documents\` (DOCUMENT), extraction column config\n`;
1347
+ md += `- **Outputs**: \`extraction_columns\` (ANY — structured extraction results)\n`;
1348
+ md += `- **Critical**: Output type is ANY — needs intermediary (json_mapper + fixed_response) before send_email inputs.\n`;
1349
+ md += `- See \`ema://rules/extraction-column-format\` for column schema.\n\n`;
1350
+ md += `### send_email_agent\n`;
1351
+ md += `Send email with specified recipient, subject, and body.\n`;
1352
+ md += `- **Inputs**: \`email_to\` (TEXT_WITH_SOURCES), \`email_subject\` (ANY), \`email_body\` (TEXT_WITH_SOURCES)\n`;
1353
+ md += `- **Outputs**: \`send_status\` (ANY)\n`;
1354
+ md += `- **Critical**: Inputs must be TEXT_WITH_SOURCES — use intermediary chain from ANY-typed sources.\n`;
1355
+ md += `- See \`ema://rules/email-input-wiring\` for wiring patterns.\n\n`;
1356
+ md += `### json_mapper\n`;
1357
+ md += `Extract specific fields from JSON/structured data into individual outputs.\n`;
1358
+ md += `- **Inputs**: \`input_json\` (ANY), mapping rules\n`;
1359
+ md += `- **Outputs**: \`output_json\` per mapped field\n`;
1360
+ md += `- **Pairs with**: entity_extraction (field extraction), fixed_response (type conversion)\n\n`;
1361
+ md += `### rule_validation_with_documents\n`;
1362
+ md += `Check extracted data against business rules (compliance, thresholds).\n`;
1363
+ md += `- **Inputs**: \`primary_docs\` (DOCUMENT), \`map_of_extracted_columns\` (ANY)\n`;
1364
+ md += `- **Outputs**: \`ruleset_output\` (ANY — validation results)\n`;
1365
+ md += `- **Critical**: Rules configured in UI settings panel, not via workflow_def inputs.\n\n`;
1366
+ md += `### live_web_search\n`;
1367
+ md += `Real-time web search for current information.\n`;
1368
+ md += `- **Inputs**: \`query\` (TEXT_WITH_SOURCES)\n`;
1369
+ md += `- **Outputs**: \`web_search_results\` (SEARCH_RESULT)\n`;
1370
+ md += `- **Critical**: No data upload needed (searches the web).\n\n`;
1371
+ md += `### combine_search_results\n`;
1372
+ md += `Merge results from multiple search sources with deduplication.\n`;
1373
+ md += `- **Inputs**: \`search_results_1\` (SEARCH_RESULT), \`search_results_2\` (SEARCH_RESULT)\n`;
1374
+ md += `- **Outputs**: \`combined_results\` (SEARCH_RESULT)\n`;
1375
+ md += `- **Pairs with**: search/v2 + live_web_search, or any two search sources\n`;
1376
+ md += `- **Note**: \`combine_search_results/v0\` is deprecated. For new workflows, prefer \`call_llm\` with multiple \`named_inputs\` to combine search results.\n\n`;
1377
+ md += `### response_validator\n`;
1378
+ md += `Validate LLM output against quality/compliance criteria.\n`;
1379
+ md += `- **Inputs**: \`reference_query\` (TEXT_WITH_SOURCES), \`response_to_validate\` (TEXT_WITH_SOURCES)\n`;
1380
+ md += `- **Outputs**: \`abstain_reason\` (TEXT_WITH_SOURCES — reason for rejection, empty if valid)\n\n`;
1381
+ md += `### abstain_action\n`;
1382
+ md += `Provide a safe decline response when AI should not answer.\n`;
1383
+ md += `- **Inputs**: \`abstain_reason\` (TEXT_WITH_SOURCES)\n`;
1384
+ md += `- **Outputs**: \`abstain_reason\` (TEXT_WITH_SOURCES — decline message)\n\n`;
1385
+ md += `---\n\n`;
1386
+ md += `## Categorizer Configuration\n\n`;
1387
+ md += `### Defining enumTypes\n\n`;
1388
+ md += `Categories must be defined in \`workflow_def.enumTypes\`:\n\n`;
1389
+ md += `\`\`\`json\n`;
1390
+ md += `"enumTypes": [{\n`;
1391
+ md += ` "name": { "name": "intent_categories", "namespaces": [] },\n`;
1392
+ md += ` "options": [\n`;
1393
+ md += ` { "name": "Sales", "description": "Sales inquiries" },\n`;
1394
+ md += ` { "name": "Support", "description": "Technical support" },\n`;
1395
+ md += ` { "name": "General", "description": "General questions" },\n`;
1396
+ md += ` { "name": "Fallback", "description": "Unclear or unmatched intents" }\n`;
1397
+ md += ` ]\n`;
1398
+ md += `}]\n`;
1399
+ md += `\`\`\`\n\n`;
1400
+ md += `### typeArguments\n\n`;
1401
+ md += `The categorizer's \`typeArguments\` must reference the enum:\n\n`;
1402
+ md += `\`\`\`json\n`;
1403
+ md += `"typeArguments": {\n`;
1404
+ md += ` "categories": {\n`;
1405
+ md += ` "enumType": { "name": { "name": "intent_categories", "namespaces": [] } },\n`;
1406
+ md += ` "isList": false\n`;
1407
+ md += ` }\n`;
1408
+ md += `}\n`;
1409
+ md += `\`\`\`\n\n`;
1410
+ md += `**Critical**: Empty \`typeArguments\` causes deploy failure.\n\n`;
1411
+ md += `### runIf Condition Format\n\n`;
1412
+ md += `\`\`\`json\n`;
1413
+ md += `{\n`;
1414
+ md += ` "lhs": {\n`;
1415
+ md += ` "actionOutput": { "actionName": "chat_categorizer", "output": "category" },\n`;
1416
+ md += ` "autoDetectedBinding": false\n`;
1417
+ md += ` },\n`;
1418
+ md += ` "operator": 1,\n`;
1419
+ md += ` "rhs": {\n`;
1420
+ md += ` "inline": { "enumValue": "Market_Impact" },\n`;
1421
+ md += ` "autoDetectedBinding": false\n`;
1422
+ md += ` }\n`;
1423
+ md += `}\n`;
1424
+ md += `\`\`\`\n\n`;
1425
+ md += `| Operator | Meaning | Use Case |\n`;
1426
+ md += `|----------|---------|----------|\n`;
1427
+ md += `| \`1\` | Equals (\`==\`) | Route to handler when category matches |\n`;
1428
+ md += `| \`2\` | Not equals (\`!=\`) | Run for all categories except one |\n\n`;
1429
+ md += `For OR conditions (run for Sales OR General), use \`operator: 2\` with Fallback: \`category != Fallback\`.\n\n`;
1430
+ md += `### Nested Categorizers\n\n`;
1431
+ md += `For complex routing, chain categorizers:\n`;
1432
+ md += `\`\`\`\n`;
1433
+ md += `chat_categorizer (Level 1: HR, IT, General, Fallback)\n`;
1434
+ md += ` └─→ text_categorizer (Level 2 for HR: Benefits, Leave, Payroll, Fallback)\n`;
1435
+ md += `\`\`\`\n\n`;
1436
+ md += `---\n\n`;
1437
+ md += `## LLM Configuration (call_llm)\n\n`;
1438
+ md += `### named_inputs Convention\n\n`;
1439
+ md += `\`call_llm\` accepts additional context via suffix pattern \`named_inputs_<Descriptive_Name>\`:\n\n`;
1440
+ md += `| Named Input | Type | Purpose |\n`;
1441
+ md += `|-------------|------|----------|\n`;
1442
+ md += `| \`named_inputs_Search_Results\` | SEARCH_RESULT | KB search results for RAG |\n`;
1443
+ md += `| \`named_inputs_Conversation\` | CHAT_CONVERSATION | Full conversation history |\n`;
1444
+ md += `| \`named_inputs_Intent\` | ENUM | Detected category from categorizer |\n`;
1445
+ md += `| \`named_inputs_Current_Message\` | TEXT_WITH_SOURCES | Current user message |\n`;
1446
+ md += `| \`named_inputs_Tool_Result\` | TEXT_WITH_SOURCES | External action output |\n\n`;
1447
+ md += `\`named_inputs\` accepts **ANY** type — this is how you pass CHAT_CONVERSATION and SEARCH_RESULT into LLM nodes.\n\n`;
1448
+ md += `### Temperature Guidelines\n\n`;
1449
+ md += `| Use Case | Temperature | Why |\n`;
1450
+ md += `|----------|-------------|-----|\n`;
1451
+ md += `| Entity extraction | 0.0-0.3 | Accuracy over creativity |\n`;
1452
+ md += `| Document generation | 0.3-0.5 | Consistent formatting |\n`;
1453
+ md += `| General Q&A / chat | 0.5-0.7 | Balanced creativity and accuracy |\n`;
1454
+ md += `| Creative writing | 0.7-1.0 | More varied output |\n\n`;
1455
+ md += `### Consolidation Rule\n\n`;
1456
+ md += `If multiple \`call_llm\` nodes share the same inputs and differ only by \`trigger_when\` gate, consolidate them:\n`;
1457
+ md += `1. Create one \`call_llm\` that runs when not Fallback\n`;
1458
+ md += `2. Wire \`categorizer.category → call_llm.named_inputs_Intent\`\n`;
1459
+ md += `3. Update prompt: "Based on the detected intent ({{Intent}}), respond accordingly"\n`;
1460
+ md += `4. Keep separate nodes only when intents require different tools, search sources, or safety constraints.\n\n`;
1461
+ md += `---\n\n`;
1462
+ md += `## See Also\n\n`;
1463
+ md += `- \`ema://rules/input-sources\` — Input source validation rules\n`;
1464
+ md += `- \`ema://rules/extraction-column-format\` — Entity extraction column schema\n`;
1465
+ md += `- \`ema://rules/email-input-wiring\` — Email field wiring patterns\n`;
1466
+ md += `- \`ema://rules/json-output-patterns\` — custom_agent + json_mapper patterns\n`;
1467
+ md += `- \`ema://rules/chat-response-wiring\` — Chat response wiring rules\n`;
1468
+ md += `- \`ema://rules/anti-patterns\` — Common workflow anti-patterns\n`;
1469
+ return md;
1470
+ },
1471
+ },
1472
+ // ─────────────────────────────────────────────────────────────────────────────
882
1473
  // Workflow Requirements & Guidance
883
1474
  // NOT hardcoded templates - provide requirements and let LLM generate
884
1475
  // ─────────────────────────────────────────────────────────────────────────────
@@ -925,7 +1516,7 @@ When you already have a clean extracted value and just need TEXT_WITH_SOURCES wr
925
1516
  "Use search/v2 (NOT v0) with datastore_configs",
926
1517
  "Include Fallback category in every categorizer",
927
1518
  "Use respond_for_external_actions (NOT deprecated respond_with_sources)",
928
- "For approval workflows: enable HITL flag on the agent (not a standalone general_hitl node)",
1519
+ "For approval workflows: enable HITL flag on send_email_agent or entity_extraction_with_documents (only nodes that support HITL). general_hitl is NOT deployable.",
929
1520
  ],
930
1521
  _next_step: "Call workflow(mode='get', persona_id='...') to get full schema, then generate workflow_def",
931
1522
  }, null, 2);
@@ -2098,9 +2689,9 @@ These rules are extracted from the Go validator's static validation logic. Follo
2098
2689
  **Good Pattern**:
2099
2690
  \`\`\`json
2100
2691
  {
2101
- "path": ["trigger", "categorizer", "billing_branch", "search", "respond_with_sources"],
2102
- "named_result": "response_with_sources",
2103
- "produces": ["response_with_sources"] // ✅ Produced
2692
+ "path": ["trigger", "categorizer", "billing_branch", "search", "respond_for_external_actions"],
2693
+ "named_result": "response",
2694
+ "produces": ["response"] // ✅ Produced
2104
2695
  }
2105
2696
  \`\`\`
2106
2697
 
@@ -2114,7 +2705,7 @@ These rules are extracted from the Go validator's static validation logic. Follo
2114
2705
  3. Connect the node's output to WORKFLOW_OUTPUT
2115
2706
 
2116
2707
  **Common Fixes**:
2117
- - Add \`respond_with_sources\` node after search
2708
+ - Add \`respond_for_external_actions\` node after search
2118
2709
  - Add \`call_llm\` node for text generation
2119
2710
  - Add \`fixed_response\` for static responses
2120
2711
 
@@ -2222,7 +2813,7 @@ These rules are extracted from the Go validator's static validation logic. Follo
2222
2813
  **Good Pattern**:
2223
2814
  \`\`\`json
2224
2815
  {
2225
- "path": ["trigger", "categorizer", "billing", "search", "respond_with_sources"],
2816
+ "path": ["trigger", "categorizer", "billing", "search", "respond_for_external_actions"],
2226
2817
  "has_response": true // ✅ Has response
2227
2818
  }
2228
2819
  \`\`\`
@@ -2441,6 +3032,7 @@ Before deploying, verify:
2441
3032
  - \`ema://rules/json-output-patterns\` - custom_agent + json_mapper pattern
2442
3033
  - \`ema://rules/chat-response-wiring\` - Chat response node wiring (avoid duplicate responses)
2443
3034
  - \`ema://rules/email-input-wiring\` - Email input wiring (json_mapper/fixed_response patterns)
3035
+ - \`ema://docs/workflow-node-reference\` - Per-node I/O types, categorizer patterns, LLM config
2444
3036
  - \`ema://rules/structural-invariants\` - Structural rules (JSON)
2445
3037
  - \`ema://rules/optimizations\` - Optimization rules (JSON)
2446
3038
  - \`ema://validation/rules\` - Go validator rules reference