@synap-core/workspace-templates 0.9.4 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -598,7 +598,7 @@ var DATA = {
598
598
  "displayName": "Person",
599
599
  "icon": "user",
600
600
  "color": "#0891B2",
601
- "scope": "WORKSPACE",
601
+ "entityScope": "pod",
602
602
  "description": "Team member or agent owner",
603
603
  "properties": [
604
604
  {
@@ -2736,268 +2736,1115 @@ var DATA = {
2736
2736
  "doneWhen": "The live agents exist with their model/provider, the providers are captured with budgets, key skills are catalogued, and agents are linked to active projects."
2737
2737
  }
2738
2738
  },
2739
- "blockchain-ecosystem": {
2739
+ "base": {
2740
2740
  "meta": {
2741
- "slug": "blockchain-ecosystem",
2742
- "domain": "strategy",
2743
- "name": "Blockchain Ecosystem",
2744
- "description": "Web3 market actors \u2014 market-makers, auditors, dev shops, KOLs \u2014 and how they connect. Overlays the Ecosystem workspace.",
2745
- "icon": "boxes",
2746
- "color": "#8B5CF6",
2741
+ "slug": "base",
2742
+ "domain": "core",
2743
+ "name": "Base",
2744
+ "description": "Universal operational defaults every workspace inherits \u2014 the report automation, the default relation vocabulary, and the default command pack.",
2745
+ "icon": "layers",
2746
+ "color": "#6B7280",
2747
2747
  "tags": [
2748
- "blockchain",
2749
- "web3",
2750
- "ecosystem",
2751
- "market-makers"
2748
+ "core",
2749
+ "defaults",
2750
+ "operational"
2752
2751
  ],
2753
- "isPublic": false
2752
+ "isPublic": true
2754
2753
  },
2755
- "dependencies": [
2756
- {
2757
- "slug": "ecosystem",
2758
- "kind": "workspace",
2759
- "relation": "compose",
2760
- "reason": "Adds web3 market actors + relations onto your Ecosystem."
2761
- }
2762
- ],
2763
2754
  "workspace": {
2764
- "name": "Ecosystem",
2765
- "description": "Web3 market actors and the relationships between them, layered onto your Ecosystem.",
2766
- "subtype": "ecosystem"
2755
+ "name": "Base",
2756
+ "description": "The pod-wide operational defaults \u2014 carried as config so one reconcile door seeds them into every workspace instead of three hardcoded seeders.",
2757
+ "proposalId": "base-v1",
2758
+ "visibility": "pod_visible"
2767
2759
  },
2768
- "profiles": [
2760
+ "profiles": [],
2761
+ "relationDefs": [
2762
+ {
2763
+ "slug": "assigned_to",
2764
+ "displayName": "Assigned To",
2765
+ "description": "Person assigned to task/project",
2766
+ "isDirectional": true,
2767
+ "uiHints": {
2768
+ "category": "workflow"
2769
+ }
2770
+ },
2769
2771
  {
2770
- "slug": "market-maker",
2771
- "displayName": "Market Maker",
2772
- "icon": "candlestick-chart",
2773
- "color": "sky",
2774
- "description": "A liquidity provider \u2014 makes markets and provides depth for a token.",
2775
- "scope": "shared",
2776
- "profileKind": "role",
2777
- "applicableKinds": [
2778
- "company",
2779
- "person"
2780
- ],
2781
- "properties": [
2782
- {
2783
- "slug": "status",
2784
- "label": "Status",
2785
- "valueType": "string",
2786
- "inputType": "select",
2787
- "enumValues": [
2788
- "prospect",
2789
- "active",
2790
- "paused",
2791
- "churned"
2792
- ]
2793
- },
2794
- {
2795
- "slug": "exchanges",
2796
- "label": "Exchanges / Venues",
2797
- "valueType": "array",
2798
- "inputType": "text"
2799
- },
2800
- {
2801
- "slug": "engagementStartDate",
2802
- "label": "Engagement Start",
2803
- "valueType": "date",
2804
- "inputType": "date"
2805
- },
2806
- {
2807
- "slug": "notes",
2808
- "label": "Notes",
2809
- "valueType": "string",
2810
- "inputType": "textarea"
2811
- }
2812
- ]
2772
+ "slug": "blocks",
2773
+ "displayName": "Blocks",
2774
+ "description": "Prevents progress on another task",
2775
+ "isDirectional": true,
2776
+ "uiHints": {
2777
+ "category": "workflow"
2778
+ }
2813
2779
  },
2814
2780
  {
2815
- "slug": "security-auditor",
2816
- "displayName": "Security Auditor",
2817
- "icon": "shield-check",
2818
- "color": "red",
2819
- "description": "A security firm or individual that audits smart contracts and protocols.",
2820
- "scope": "shared",
2821
- "profileKind": "role",
2822
- "applicableKinds": [
2823
- "company",
2824
- "person"
2825
- ],
2826
- "properties": [
2827
- {
2828
- "slug": "status",
2829
- "label": "Status",
2830
- "valueType": "string",
2831
- "inputType": "select",
2832
- "enumValues": [
2833
- "prospect",
2834
- "active",
2835
- "paused",
2836
- "churned"
2837
- ]
2838
- },
2839
- {
2840
- "slug": "specialty",
2841
- "label": "Specialty",
2842
- "valueType": "string",
2843
- "inputType": "text"
2844
- },
2845
- {
2846
- "slug": "engagementStartDate",
2847
- "label": "Engagement Start",
2848
- "valueType": "date",
2849
- "inputType": "date"
2850
- },
2851
- {
2852
- "slug": "notes",
2853
- "label": "Notes",
2854
- "valueType": "string",
2855
- "inputType": "textarea"
2856
- }
2857
- ]
2781
+ "slug": "depends_on",
2782
+ "displayName": "Depends On",
2783
+ "description": "Requires completion of another task",
2784
+ "isDirectional": true,
2785
+ "uiHints": {
2786
+ "category": "workflow"
2787
+ }
2858
2788
  },
2859
2789
  {
2860
- "slug": "dev-shop",
2861
- "displayName": "Dev Shop",
2862
- "icon": "code",
2863
- "color": "violet",
2864
- "description": "An outsourced engineering studio or build partner.",
2865
- "scope": "shared",
2866
- "profileKind": "role",
2867
- "applicableKinds": [
2868
- "company",
2869
- "person"
2870
- ],
2871
- "properties": [
2872
- {
2873
- "slug": "status",
2874
- "label": "Status",
2875
- "valueType": "string",
2876
- "inputType": "select",
2877
- "enumValues": [
2878
- "prospect",
2879
- "active",
2880
- "paused",
2881
- "churned"
2882
- ]
2883
- },
2884
- {
2885
- "slug": "specialty",
2886
- "label": "Specialty",
2887
- "valueType": "string",
2888
- "inputType": "text"
2889
- },
2890
- {
2891
- "slug": "engagementStartDate",
2892
- "label": "Engagement Start",
2893
- "valueType": "date",
2894
- "inputType": "date"
2895
- },
2896
- {
2897
- "slug": "notes",
2898
- "label": "Notes",
2899
- "valueType": "string",
2900
- "inputType": "textarea"
2901
- }
2902
- ]
2790
+ "slug": "relates_to",
2791
+ "displayName": "Relates To",
2792
+ "description": "General relationship between entities",
2793
+ "isDirectional": false,
2794
+ "uiHints": {
2795
+ "category": "reference"
2796
+ }
2903
2797
  },
2904
2798
  {
2905
- "slug": "marketing-agency",
2906
- "displayName": "Marketing Agency",
2907
- "icon": "megaphone",
2908
- "color": "pink",
2909
- "description": "A brand, growth, or content marketing agency.",
2910
- "scope": "shared",
2911
- "profileKind": "role",
2912
- "applicableKinds": [
2913
- "company",
2914
- "person"
2915
- ],
2916
- "properties": [
2917
- {
2918
- "slug": "status",
2919
- "label": "Status",
2920
- "valueType": "string",
2921
- "inputType": "select",
2922
- "enumValues": [
2923
- "prospect",
2924
- "active",
2925
- "paused",
2926
- "churned"
2927
- ]
2928
- },
2929
- {
2930
- "slug": "specialty",
2931
- "label": "Specialty",
2932
- "valueType": "string",
2933
- "inputType": "text"
2934
- },
2935
- {
2936
- "slug": "engagementStartDate",
2937
- "label": "Engagement Start",
2938
- "valueType": "date",
2939
- "inputType": "date"
2940
- },
2941
- {
2942
- "slug": "notes",
2943
- "label": "Notes",
2944
- "valueType": "string",
2945
- "inputType": "textarea"
2946
- }
2947
- ]
2799
+ "slug": "mentions",
2800
+ "displayName": "Mentions",
2801
+ "description": "Referenced in content",
2802
+ "isDirectional": true,
2803
+ "uiHints": {
2804
+ "category": "reference"
2805
+ }
2948
2806
  },
2949
2807
  {
2950
- "slug": "paid-ads-agency",
2951
- "displayName": "Paid-Ads Agency",
2952
- "icon": "badge-dollar-sign",
2953
- "color": "orange",
2954
- "description": "A performance / paid-media buying agency.",
2955
- "scope": "shared",
2956
- "profileKind": "role",
2957
- "applicableKinds": [
2958
- "company",
2959
- "person"
2960
- ],
2961
- "properties": [
2962
- {
2963
- "slug": "status",
2964
- "label": "Status",
2965
- "valueType": "string",
2966
- "inputType": "select",
2967
- "enumValues": [
2968
- "prospect",
2969
- "active",
2970
- "paused",
2971
- "churned"
2972
- ]
2973
- },
2974
- {
2975
- "slug": "channels",
2976
- "label": "Channels",
2977
- "valueType": "array",
2978
- "inputType": "text"
2979
- },
2980
- {
2981
- "slug": "engagementStartDate",
2982
- "label": "Engagement Start",
2983
- "valueType": "date",
2984
- "inputType": "date"
2985
- },
2986
- {
2987
- "slug": "notes",
2988
- "label": "Notes",
2989
- "valueType": "string",
2990
- "inputType": "textarea"
2991
- }
2992
- ]
2808
+ "slug": "links_to",
2809
+ "displayName": "Links To",
2810
+ "description": "Hyperlink or reference",
2811
+ "isDirectional": true,
2812
+ "uiHints": {
2813
+ "category": "reference"
2814
+ }
2993
2815
  },
2994
2816
  {
2995
- "slug": "kol",
2996
- "displayName": "KOL",
2997
- "icon": "star",
2998
- "color": "yellow",
2999
- "description": "A key opinion leader or influencer who amplifies reach.",
3000
- "scope": "shared",
2817
+ "slug": "parent_of",
2818
+ "displayName": "Parent Of",
2819
+ "description": "Hierarchical parent relationship",
2820
+ "isDirectional": true,
2821
+ "uiHints": {
2822
+ "category": "hierarchy"
2823
+ }
2824
+ },
2825
+ {
2826
+ "slug": "tagged_with",
2827
+ "displayName": "Tagged With",
2828
+ "description": "Categorization tag",
2829
+ "isDirectional": true,
2830
+ "uiHints": {
2831
+ "category": "reference"
2832
+ }
2833
+ },
2834
+ {
2835
+ "slug": "created_by",
2836
+ "displayName": "Created By",
2837
+ "description": "Author or creator",
2838
+ "isDirectional": true,
2839
+ "uiHints": {
2840
+ "category": "social"
2841
+ }
2842
+ },
2843
+ {
2844
+ "slug": "attended_by",
2845
+ "displayName": "Attended By",
2846
+ "description": "Participant in event",
2847
+ "isDirectional": true,
2848
+ "uiHints": {
2849
+ "category": "social"
2850
+ }
2851
+ },
2852
+ {
2853
+ "slug": "belongs_to_project",
2854
+ "displayName": "Belongs To Project",
2855
+ "description": "Project membership",
2856
+ "isDirectional": true,
2857
+ "uiHints": {
2858
+ "category": "hierarchy"
2859
+ }
2860
+ },
2861
+ {
2862
+ "slug": "founder_brand_of",
2863
+ "displayName": "Founder Brand Of",
2864
+ "description": "A personal-brand project is the founder brand of a company project \u2014 links a person's brand to the company it expresses",
2865
+ "isDirectional": true,
2866
+ "uiHints": {
2867
+ "category": "hierarchy",
2868
+ "inverseLabel": "Expression of"
2869
+ }
2870
+ },
2871
+ {
2872
+ "slug": "references",
2873
+ "displayName": "References",
2874
+ "description": "Cites or refers to",
2875
+ "isDirectional": true,
2876
+ "uiHints": {
2877
+ "category": "reference"
2878
+ }
2879
+ },
2880
+ {
2881
+ "slug": "works_at",
2882
+ "displayName": "Works At",
2883
+ "description": "Person works at an organization",
2884
+ "isDirectional": true,
2885
+ "uiHints": {
2886
+ "category": "social",
2887
+ "inverseLabel": "Employs"
2888
+ }
2889
+ },
2890
+ {
2891
+ "slug": "deal_for",
2892
+ "displayName": "Deal For",
2893
+ "description": "Sales deal associated with a contact",
2894
+ "isDirectional": true,
2895
+ "uiHints": {
2896
+ "category": "workflow",
2897
+ "inverseLabel": "Has Deal"
2898
+ }
2899
+ },
2900
+ {
2901
+ "slug": "advances",
2902
+ "displayName": "Advances",
2903
+ "description": "advances the company's Foundation mission/vision goal",
2904
+ "isDirectional": true,
2905
+ "uiHints": {
2906
+ "category": "workflow",
2907
+ "inverseLabel": "Advanced by"
2908
+ }
2909
+ },
2910
+ {
2911
+ "slug": "met_at",
2912
+ "displayName": "Met at",
2913
+ "description": "Person met at an event or gathering",
2914
+ "isDirectional": true,
2915
+ "uiHints": {
2916
+ "category": "social",
2917
+ "inverseLabel": "Attendees"
2918
+ }
2919
+ },
2920
+ {
2921
+ "slug": "works_on",
2922
+ "displayName": "Works on",
2923
+ "description": "Person contributes to a project",
2924
+ "isDirectional": true,
2925
+ "uiHints": {
2926
+ "category": "workflow",
2927
+ "inverseLabel": "Contributors"
2928
+ }
2929
+ },
2930
+ {
2931
+ "slug": "has_skill",
2932
+ "displayName": "Has skill",
2933
+ "description": "Person possesses a skill or expertise",
2934
+ "isDirectional": true,
2935
+ "uiHints": {
2936
+ "category": "reference",
2937
+ "inverseLabel": "Skilled people"
2938
+ }
2939
+ },
2940
+ {
2941
+ "slug": "affiliated_with",
2942
+ "displayName": "Affiliated with",
2943
+ "description": "Person affiliated with an organization",
2944
+ "isDirectional": true,
2945
+ "uiHints": {
2946
+ "category": "social",
2947
+ "inverseLabel": "Members"
2948
+ }
2949
+ },
2950
+ {
2951
+ "slug": "knows",
2952
+ "displayName": "Knows",
2953
+ "description": "Two people know each other",
2954
+ "isDirectional": false,
2955
+ "uiHints": {
2956
+ "category": "social"
2957
+ }
2958
+ },
2959
+ {
2960
+ "slug": "discussed_with",
2961
+ "displayName": "Discussed with",
2962
+ "description": "Had a discussion or conversation with",
2963
+ "isDirectional": false,
2964
+ "uiHints": {
2965
+ "category": "social"
2966
+ }
2967
+ }
2968
+ ],
2969
+ "commands": [
2970
+ {
2971
+ "title": "Create View from Description",
2972
+ "promptTemplate": "You are helping the user create a new view from a natural language description.\n\n**User's goal:** @{arg:goal}\n\nCreate a proposal for a new view that matches this description. Include:\n- View type (table, kanban, list, etc.)\n- Filters, sorts, and scope\n- Layout configuration\n\nOutput your proposal clearly so it can be turned into a Synap view.",
2973
+ "derivedInputs": [
2974
+ {
2975
+ "name": "goal",
2976
+ "label": "Goal",
2977
+ "type": "text"
2978
+ }
2979
+ ],
2980
+ "outputMode": "view",
2981
+ "canCreateViews": true,
2982
+ "permissionsProfile": "propose_writes"
2983
+ },
2984
+ {
2985
+ "title": "Extract Entities from Text",
2986
+ "promptTemplate": "You are helping the user extract entities and relationships from text.\n\n**User's text or selection:**\n\n@{context:text}\n\nAnalyze this content and propose:\n1. New entities to create (Notes, Tasks, People, Companies, etc.) with their properties\n2. Relationships between entities (blocks, related_to, parent_of, etc.)\n\nOutput proposals that the user can review and approve. Do not create anything directly\u2014always propose.",
2987
+ "derivedInputs": [],
2988
+ "outputMode": "proposal",
2989
+ "canCreateViews": false,
2990
+ "permissionsProfile": "propose_writes"
2991
+ },
2992
+ {
2993
+ "title": "Summarize Selection",
2994
+ "promptTemplate": "You are helping the user get a concise summary of their selection.\n\n**User's text or selection:**\n\n@{context:text}\n\nProvide a clear, structured summary. Highlight key points, dates, and actionable items if relevant. Keep it concise but comprehensive.",
2995
+ "derivedInputs": [],
2996
+ "outputMode": "text",
2997
+ "canCreateViews": false,
2998
+ "permissionsProfile": "read_only"
2999
+ },
3000
+ {
3001
+ "title": "Analyze Entities",
3002
+ "promptTemplate": "You are helping the user analyze selected entities.\n\n**Current entity context:**\n\n@{context:entity}\n\nAnalyze this entity and provide:\n- Overview and key properties\n- Insights, gaps, or opportunities\n- Suggestions for relationships or next steps\n\nBe concise and actionable.",
3003
+ "derivedInputs": [],
3004
+ "outputMode": "text",
3005
+ "canCreateViews": false,
3006
+ "permissionsProfile": "read_only"
3007
+ },
3008
+ {
3009
+ "title": "Create Task from Text",
3010
+ "promptTemplate": "You are helping the user turn text into tasks.\n\n**User's text (meeting notes, pasted content, or selection):**\n\n@{context:text}\n\nExtract actionable tasks. For each task propose:\n- Title\n- Optional: due date, priority, assignee, status\n- Link to source if applicable\n\nOutput proposals only\u2014do not create entities directly. The user will review and approve.",
3011
+ "derivedInputs": [],
3012
+ "outputMode": "proposal",
3013
+ "canCreateViews": false,
3014
+ "permissionsProfile": "propose_writes"
3015
+ },
3016
+ {
3017
+ "title": "Create Event from Text",
3018
+ "promptTemplate": "You are helping the user turn text into events.\n\n**User's text (meeting notes, calendar copy, or selection):**\n\n@{context:text}\n\nExtract events with dates/times. For each event propose:\n- Title\n- Start time and end time\n- Optional: description, tags\n\nOutput proposals only\u2014do not create entities directly. The user will review and approve.",
3019
+ "derivedInputs": [],
3020
+ "outputMode": "proposal",
3021
+ "canCreateViews": false,
3022
+ "permissionsProfile": "propose_writes"
3023
+ }
3024
+ ],
3025
+ "automations": [
3026
+ {
3027
+ "name": "Generate report",
3028
+ "description": `Gather this workspace's state, interpret it over three AI rounds, and write a report entity. Run it as-is for a general report, or pass a prompt (e.g. "focus on our clients") to steer what it is about.`,
3029
+ "triggerType": "manual",
3030
+ "triggerConfig": {},
3031
+ "status": "active",
3032
+ "flow": {
3033
+ "nodes": [
3034
+ {
3035
+ "id": "trigger",
3036
+ "type": "trigger",
3037
+ "position": {
3038
+ "x": 0,
3039
+ "y": 200
3040
+ },
3041
+ "data": {
3042
+ "triggerType": "manual",
3043
+ "label": "Run report",
3044
+ "config": {}
3045
+ }
3046
+ },
3047
+ {
3048
+ "id": "now",
3049
+ "type": "compute",
3050
+ "position": {
3051
+ "x": 200,
3052
+ "y": 200
3053
+ },
3054
+ "data": {
3055
+ "label": "Timestamp",
3056
+ "operation": "now"
3057
+ }
3058
+ },
3059
+ {
3060
+ "id": "gather-tasks",
3061
+ "type": "query",
3062
+ "position": {
3063
+ "x": 420,
3064
+ "y": 20
3065
+ },
3066
+ "data": {
3067
+ "label": "Tasks",
3068
+ "profileSlug": "task",
3069
+ "filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
3070
+ "orderBy": "updatedAt",
3071
+ "orderDir": "desc",
3072
+ "limit": 25,
3073
+ "errorHandling": {
3074
+ "continueOnError": true
3075
+ }
3076
+ }
3077
+ },
3078
+ {
3079
+ "id": "gather-notes",
3080
+ "type": "query",
3081
+ "position": {
3082
+ "x": 420,
3083
+ "y": 140
3084
+ },
3085
+ "data": {
3086
+ "label": "Notes",
3087
+ "profileSlug": "note",
3088
+ "filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
3089
+ "orderBy": "updatedAt",
3090
+ "orderDir": "desc",
3091
+ "limit": 15,
3092
+ "errorHandling": {
3093
+ "continueOnError": true
3094
+ }
3095
+ }
3096
+ },
3097
+ {
3098
+ "id": "gather-people",
3099
+ "type": "query",
3100
+ "position": {
3101
+ "x": 420,
3102
+ "y": 260
3103
+ },
3104
+ "data": {
3105
+ "label": "People",
3106
+ "profileSlug": "person",
3107
+ "filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
3108
+ "orderBy": "updatedAt",
3109
+ "orderDir": "desc",
3110
+ "limit": 15,
3111
+ "errorHandling": {
3112
+ "continueOnError": true
3113
+ }
3114
+ }
3115
+ },
3116
+ {
3117
+ "id": "gather-companies",
3118
+ "type": "query",
3119
+ "position": {
3120
+ "x": 420,
3121
+ "y": 380
3122
+ },
3123
+ "data": {
3124
+ "label": "Companies",
3125
+ "profileSlug": "company",
3126
+ "filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
3127
+ "orderBy": "updatedAt",
3128
+ "orderDir": "desc",
3129
+ "limit": 15,
3130
+ "errorHandling": {
3131
+ "continueOnError": true
3132
+ }
3133
+ }
3134
+ },
3135
+ {
3136
+ "id": "project-tasks",
3137
+ "type": "transform",
3138
+ "position": {
3139
+ "x": 540,
3140
+ "y": 20
3141
+ },
3142
+ "data": {
3143
+ "label": "Project tasks",
3144
+ "expression": "{{steps.gather-tasks.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 status={{item.properties.status}} \xB7 priority={{item.properties.priority}} \xB7 due={{item.properties.dueDate}} \xB7 updated={{item.updatedAt}}",
3145
+ "errorHandling": {
3146
+ "continueOnError": true
3147
+ }
3148
+ }
3149
+ },
3150
+ {
3151
+ "id": "project-notes",
3152
+ "type": "transform",
3153
+ "position": {
3154
+ "x": 540,
3155
+ "y": 140
3156
+ },
3157
+ "data": {
3158
+ "label": "Project notes",
3159
+ "expression": "{{steps.gather-notes.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 updated={{item.updatedAt}}",
3160
+ "errorHandling": {
3161
+ "continueOnError": true
3162
+ }
3163
+ }
3164
+ },
3165
+ {
3166
+ "id": "project-people",
3167
+ "type": "transform",
3168
+ "position": {
3169
+ "x": 540,
3170
+ "y": 260
3171
+ },
3172
+ "data": {
3173
+ "label": "Project people",
3174
+ "expression": "{{steps.gather-people.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 email={{item.properties.email}} \xB7 lastInteraction={{item.properties.lastInteractionAt}} \xB7 updated={{item.updatedAt}}",
3175
+ "errorHandling": {
3176
+ "continueOnError": true
3177
+ }
3178
+ }
3179
+ },
3180
+ {
3181
+ "id": "project-companies",
3182
+ "type": "transform",
3183
+ "position": {
3184
+ "x": 540,
3185
+ "y": 380
3186
+ },
3187
+ "data": {
3188
+ "label": "Project companies",
3189
+ "expression": "{{steps.gather-companies.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 industry={{item.properties.industry}} \xB7 location={{item.properties.location}} \xB7 updated={{item.updatedAt}}",
3190
+ "errorHandling": {
3191
+ "continueOnError": true
3192
+ }
3193
+ }
3194
+ },
3195
+ {
3196
+ "id": "analyze",
3197
+ "type": "capability",
3198
+ "position": {
3199
+ "x": 660,
3200
+ "y": 200
3201
+ },
3202
+ "data": {
3203
+ "label": "Analyze",
3204
+ "verbId": "ai.generate",
3205
+ "verbKind": "read",
3206
+ "inputMapping": {
3207
+ "system": 'You are the ANALYST round of a Synap workspace report.\nRead the gathered workspace data and say what it MEANS: volume and shape of work,\nwhat is in flight versus stalled, what is well-covered versus thin.\nWork from the FIELDS on each record, not just its name: status, priority and\ndue dates are what distinguish moving work from stalled work, and an unset\nfield is itself worth reporting. Listing or counting what exists is NOT a\nfinding \u2014 say what the state of it means.\nDATA INTEGRITY CHECK \u2014 do this BEFORE interpreting anything. Each kind below states how many records were found, then lists them. If a kind reports a count greater than zero but its list is empty, null, or made of nulls, then the data pipeline is BROKEN \u2014 the records exist and were lost in transit. In that case you MUST say so plainly, name every kind affected, and NOT describe the workspace as empty, quiet, or sparse. Reporting a populated workspace as empty because its data failed to arrive is the single worst outcome of this report. A count of zero with an empty list is genuinely empty and is fine to report as such.\nYou are interpreting data you did NOT collect. Every claim must trace to a row in the GATHERED DATA below. Never invent entities, ids, counts, or dates. A step whose value looks like {"error": "..."} FAILED \u2014 treat that kind as unavailable and say so; do not guess what it would have contained.\nEVERY NUMBER YOU WRITE MUST BE COPIED FROM THE COUNTS BLOCK. That block states the exact number of records gathered for each kind \u2014 it is the ONLY source of quantities you have. Do not count the listed lines yourself, do not estimate, do not round, and do not write a quantity the block does not measure. Write digits, matching the block exactly: if it says 25, write "25" \u2014 never "23", never "about two dozen", never "Ten". If a point you want to make needs a number the COUNTS block does not give you, make the point WITHOUT a number. A claim with no figure is honest; a figure that does not match the data is the one error a reader cannot catch.\nNEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.\nSTEER HANDLING \u2014 non-negotiable. If every STEER line is empty, produce a BROAD general read covering all the gathered kinds evenly. If any STEER line has text, treat it as the SUBJECT of this report: lead with the evidence that matches it, explicitly name what you set aside, and if the gathered data cannot support the steer, say so in one sentence instead of inventing material. Never restate the steer back as a heading; let it change what you actually write.\nLINK THE RECORDS YOU NAME. Every line of GATHERED DATA is `<id> \xB7 <title>`. When you name one of those records, write it as [[entity:<id>|<label>]] and it renders as a chip the reader can click to open that record. Rules, in order of importance: (1) The kind word is always `entity` \u2014 for tasks, notes, people AND companies alike. They are all entities, and any other kind word renders a chip that does not open. (2) Use only an id that appears VERBATIM in the gathered data. Copy it; never construct, shorten, complete or guess one. (3) If you are unsure of an id, write the plain label with no brackets. A wrong id is a dead chip, and a dead chip is worse than plain text. (4) The label is free text but must not contain `]`; the id must not contain `:` or `|`. A marker breaking either rule renders as raw literal text. (5) EVERY record you name in prose MUST carry a chip on its FIRST mention \u2014 this is what makes a named record a live object the reader can open rather than a string. Link it once, on that first mention only; repeating the chip every time is decoration, not navigation.\nOutput PLAIN PROSE \u2014 short paragraphs and bullets, with [[entity:\u2026]] chips\ninline wherever you name a record. No markdown headings, no ::: directive\nblocks, no code fences. Under 300 words. Another round will format this.',
3208
+ "prompt": 'STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nCOUNTS \u2014 the exact number of records gathered for each kind. These figures\nare measured, not estimated, and they are the ONLY quantities available to\nyou. Each line also gives the cap the gather was limited to.\n- Tasks: {{steps.gather-tasks.output.count}} gathered (cap 25)\n- Notes: {{steps.gather-notes.output.count}} gathered (cap 15)\n- People: {{steps.gather-people.output.count}} gathered (cap 15)\n- Companies: {{steps.gather-companies.output.count}} gathered (cap 15)\nREADING A COUNT AGAINST ITS CAP: a count BELOW the cap means every record of\nthat kind is listed below \u2014 the set is COMPLETE and you may describe it as\nsuch. A count EQUAL to the cap means the gather stopped at the cap and more\nrecords exist that you cannot see \u2014 describe that kind as "at least N" and\nnever as complete, and never claim something does not exist merely because\nit is absent from the list.\n\nTime window: only records updated on or after {{trigger.payload.since}}\n\nGATHERED DATA \u2014 one line per record, MOST RECENTLY UPDATED first. Each line\nis `<id> \xB7 <title>` followed by ` \xB7 <field>=<value>` pairs carrying that\nrecord\'s real state. An EMPTY value means the field is not set on that\nrecord \u2014 that is a fact you may report (an unset status or a missing due\ndate is a finding), and never a value to guess at. Recent does not mean\nimportant.\nTasks \u2014 {{steps.gather-tasks.output.count}} found: {{steps.project-tasks.output.result}}\nNotes \u2014 {{steps.gather-notes.output.count}} found: {{steps.project-notes.output.result}}\nPeople \u2014 {{steps.gather-people.output.count}} found: {{steps.project-people.output.result}}\nCompanies \u2014 {{steps.gather-companies.output.count}} found: {{steps.project-companies.output.result}}',
3209
+ "maxTokens": "700"
3210
+ },
3211
+ "errorHandling": {
3212
+ "continueOnError": true
3213
+ }
3214
+ }
3215
+ },
3216
+ {
3217
+ "id": "relate",
3218
+ "type": "capability",
3219
+ "position": {
3220
+ "x": 900,
3221
+ "y": 200
3222
+ },
3223
+ "data": {
3224
+ "label": "Patterns",
3225
+ "verbId": "ai.generate",
3226
+ "verbKind": "read",
3227
+ "inputMapping": {
3228
+ "system": 'You are the PATTERNS round of a Synap workspace report.\nYou receive the raw gathered data AND the analyst\'s read of it. Your job is what\nthe analyst did NOT do: surface RELATIONSHIPS and things worth attention \u2014\nclusters, repeated themes, people or companies that recur across kinds, gaps\nwhere a kind is conspicuously empty, and anything that looks like it needs a\ndecision. Prefer three sharp observations over ten shallow ones.\nYOUR SCOPE IS WHAT CONNECTS RECORDS, not what they individually are. Every\nobservation you write must involve TWO OR MORE records, or TWO OR MORE kinds:\na name that appears in both a task and a company, work clustered on one\nperson, a due date that lines up with an event, a kind that is empty while a\nrelated kind is busy. Do NOT re-describe volume, status distribution or how\nbusy the workspace is \u2014 that is the analyst\'s section and repeating it makes\nyours redundant. Do not summarise or restate the analyst\'s read.\nNEVER RETURN AN EMPTY ANSWER. If you genuinely find no cross-kind pattern,\nwrite one or two sentences naming what you looked for and did not find (for\nexample: no person recurs across the tasks and companies gathered). That is a\nreal finding and the reader needs it. Silence is not an available outcome.\nDATA INTEGRITY CHECK \u2014 do this BEFORE interpreting anything. Each kind below states how many records were found, then lists them. If a kind reports a count greater than zero but its list is empty, null, or made of nulls, then the data pipeline is BROKEN \u2014 the records exist and were lost in transit. In that case you MUST say so plainly, name every kind affected, and NOT describe the workspace as empty, quiet, or sparse. Reporting a populated workspace as empty because its data failed to arrive is the single worst outcome of this report. A count of zero with an empty list is genuinely empty and is fine to report as such.\nYou are interpreting data you did NOT collect. Every claim must trace to a row in the GATHERED DATA below. Never invent entities, ids, counts, or dates. A step whose value looks like {"error": "..."} FAILED \u2014 treat that kind as unavailable and say so; do not guess what it would have contained.\nEVERY NUMBER YOU WRITE MUST BE COPIED FROM THE COUNTS BLOCK. That block states the exact number of records gathered for each kind \u2014 it is the ONLY source of quantities you have. Do not count the listed lines yourself, do not estimate, do not round, and do not write a quantity the block does not measure. Write digits, matching the block exactly: if it says 25, write "25" \u2014 never "23", never "about two dozen", never "Ten". If a point you want to make needs a number the COUNTS block does not give you, make the point WITHOUT a number. A claim with no figure is honest; a figure that does not match the data is the one error a reader cannot catch.\nNEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.\nSTEER HANDLING \u2014 non-negotiable. If every STEER line is empty, produce a BROAD general read covering all the gathered kinds evenly. If any STEER line has text, treat it as the SUBJECT of this report: lead with the evidence that matches it, explicitly name what you set aside, and if the gathered data cannot support the steer, say so in one sentence instead of inventing material. Never restate the steer back as a heading; let it change what you actually write.\nLINK THE RECORDS YOU NAME. Every line of GATHERED DATA is `<id> \xB7 <title>`. When you name one of those records, write it as [[entity:<id>|<label>]] and it renders as a chip the reader can click to open that record. Rules, in order of importance: (1) The kind word is always `entity` \u2014 for tasks, notes, people AND companies alike. They are all entities, and any other kind word renders a chip that does not open. (2) Use only an id that appears VERBATIM in the gathered data. Copy it; never construct, shorten, complete or guess one. (3) If you are unsure of an id, write the plain label with no brackets. A wrong id is a dead chip, and a dead chip is worse than plain text. (4) The label is free text but must not contain `]`; the id must not contain `:` or `|`. A marker breaking either rule renders as raw literal text. (5) EVERY record you name in prose MUST carry a chip on its FIRST mention \u2014 this is what makes a named record a live object the reader can open rather than a string. Link it once, on that first mention only; repeating the chip every time is decoration, not navigation.\nOutput PLAIN PROSE, under 250 words, with [[entity:\u2026]] chips inline. No\nheadings, no ::: directive blocks, no code fences.',
3229
+ "prompt": "STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nCOUNTS \u2014 the exact number of records gathered for each kind. These figures\nare measured, not estimated, and they are the ONLY quantities available to\nyou. Each line also gives the cap the gather was limited to.\n- Tasks: {{steps.gather-tasks.output.count}} gathered (cap 25)\n- Notes: {{steps.gather-notes.output.count}} gathered (cap 15)\n- People: {{steps.gather-people.output.count}} gathered (cap 15)\n- Companies: {{steps.gather-companies.output.count}} gathered (cap 15)\nREADING A COUNT AGAINST ITS CAP: a count BELOW the cap means every record of\nthat kind is listed below \u2014 the set is COMPLETE and you may describe it as\nsuch. A count EQUAL to the cap means the gather stopped at the cap and more\nrecords exist that you cannot see \u2014 describe that kind as \"at least N\" and\nnever as complete, and never claim something does not exist merely because\nit is absent from the list.\n\nTime window: only records updated on or after {{trigger.payload.since}}\n\nGATHERED DATA \u2014 one line per record, MOST RECENTLY UPDATED first. Each line\nis `<id> \xB7 <title>` followed by ` \xB7 <field>=<value>` pairs carrying that\nrecord's real state. An EMPTY value means the field is not set on that\nrecord \u2014 that is a fact you may report (an unset status or a missing due\ndate is a finding), and never a value to guess at. Recent does not mean\nimportant.\nTasks \u2014 {{steps.gather-tasks.output.count}} found: {{steps.project-tasks.output.result}}\nNotes \u2014 {{steps.gather-notes.output.count}} found: {{steps.project-notes.output.result}}\nPeople \u2014 {{steps.gather-people.output.count}} found: {{steps.project-people.output.result}}\nCompanies \u2014 {{steps.gather-companies.output.count}} found: {{steps.project-companies.output.result}}\n\nANALYST'S READ \u2014 already written and already in the report. Do NOT\nrestate, summarise or re-analyse it. It is here so you do not repeat it:\n{{steps.analyze.output}}\n\nYOUR TASK \u2014 cross-kind patterns ONLY. Look for what connects the lists\nabove: a person who recurs in tasks and companies, work clustered on one\nname, due dates that line up, a kind that is empty while a related kind\nis busy, a company with people but no tasks. Write only observations\nthat span TWO OR MORE records or kinds. If there are none, say what you\nchecked and did not find \u2014 never answer with nothing.",
3230
+ "maxTokens": "700"
3231
+ },
3232
+ "errorHandling": {
3233
+ "continueOnError": true
3234
+ }
3235
+ }
3236
+ },
3237
+ {
3238
+ "id": "assemble",
3239
+ "type": "capability",
3240
+ "position": {
3241
+ "x": 1140,
3242
+ "y": 200
3243
+ },
3244
+ "data": {
3245
+ "label": "Assemble",
3246
+ "verbId": "ai.generate",
3247
+ "verbKind": "read",
3248
+ "inputMapping": {
3249
+ "system": `You are the ASSEMBLER round of a Synap workspace report. You compose the FINAL
3250
+ document body in Synap-flavoured markdown. You add no new facts \u2014 you arrange,
3251
+ title, and format what the earlier rounds produced.
3252
+
3253
+ NEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.
3254
+
3255
+ NUMBERS: you were given no COUNTS block, so you may not originate a quantity
3256
+ at all. Reproduce a figure ONLY by copying one that already appears in the
3257
+ round material, digit for digit. Never total, re-count, convert to words, or
3258
+ put a number in the title or in a ## heading that is not already in the
3259
+ material beneath it.
3260
+
3261
+ SYNAP MARKDOWN SYNTAX \u2014 these rules are absolute:
3262
+ 1. A section is a FOUR-COLON container that must be opened and closed:
3263
+ ::::synap-section{agent="analyst" round="analyze" confidence="0.8"}
3264
+ ...content...
3265
+ ::::
3266
+ 2. Leaf embeds use THREE colons and MUST BE CLOSED with their own ::: line.
3267
+ There is NO self-closing form. An unclosed embed swallows every line that
3268
+ follows it until the section ends, and that content is LOST. Always write
3269
+ the closing ::: immediately after the opening line:
3270
+ :::synap-cell{instanceId="..."}
3271
+ :::
3272
+ :::synap-view{viewId="..."}
3273
+ :::
3274
+ :::synap-entity{id="..."}
3275
+ :::
3276
+ 3. The outer container must always have STRICTLY MORE colons than anything nested
3277
+ inside it. Four outside, three inside. If you ever nest a four-colon block in a
3278
+ four-colon block the section closes early and the rest of the report leaks out.
3279
+ 4. ATTRIBUTE VALUES MUST BE QUOTED. An UNQUOTED value ends at the first } it
3280
+ contains, which silently truncates the attribute \u2014 so always quote. Use
3281
+ DOUBLE quotes for a plain value, and SINGLE quotes when the value is itself
3282
+ a JSON object, so the JSON's own double quotes survive:
3283
+ :::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"task","groupBy":"status"}'}
3284
+ :::
3285
+ A single-quoted value may contain both " and } \u2014 those are only special to
3286
+ an unquoted value. Write the JSON compact, on one line, with no trailing
3287
+ comma; malformed JSON renders the cell with empty config.
3288
+ 5. The ONLY section attributes that survive are: id, agent, round, skills,
3289
+ confidence, stepRunId, nodeId, status. Anything else is silently dropped.
3290
+ 6. confidence is a 0-1 ratio written as a decimal string, e.g. confidence="0.8".
3291
+ Never a percentage.
3292
+ 7. CHIPS ARE CONTENT \u2014 COPY THEM THROUGH VERBATIM. The round material you were
3293
+ given already contains [[entity:<id>|<label>]] markers. Reproduce every one
3294
+ EXACTLY as written: same brackets, same id, same label. Do NOT tidy them into
3295
+ plain text, do NOT rewrite or re-case an id, do NOT convert them to markdown
3296
+ links. You are told you add no new FACTS; a chip is not a fact, it is the
3297
+ earlier round's finding made navigable, and stripping it removes the only
3298
+ clickable thing in the report while leaving the prose looking fine.
3299
+ You may add a chip ONLY by copying a marker that already appears in the
3300
+ material. Never invent an id, and never use a kind word other than \`entity\` \u2014
3301
+ this report is READ-ONLY prose, and either mistake produces a dead chip.
3302
+ 8. NEVER EMIT :::synap-view, AND NEVER EMIT :::synap-cell WITH AN instanceId.
3303
+ Both need an id you have not been given and cannot obtain, and a made-up
3304
+ reference produces a permanent broken block in a document a human reads.
3305
+ 9. YOU MAY EMIT A CHART :::synap-cell \u2014 from this allowlist ONLY, configured
3306
+ through cellProps. These cells fetch their own data at render time, so they
3307
+ need no id, and they make the report a live surface instead of a text file:
3308
+ - :::synap-cell{cellKey="stat-card" cellProps='{"profileSlug":"<kind>","aggregation":"count","timePeriod":"week","label":"<Kind> created this week"}'}
3309
+ - :::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"<kind>","groupBy":"<property>","label":"<the finding this shows>"}'}
3310
+ - :::synap-cell{cellKey="chart-bar" cellProps='{"profileSlug":"<kind>","mode":"category","groupBy":"<property>","label":"<the finding this shows>"}'}
3311
+ Rules, all mandatory:
3312
+ (a) profileSlug must be one of the kinds gathered for THIS report: task,
3313
+ note, person, company. A chart over any other kind is a claim this
3314
+ report cannot back \u2014 the data was never read.
3315
+ (b) groupBy / field must name a property that plausibly exists on that
3316
+ kind. If you are not confident one does, use the stat-card line above
3317
+ EXACTLY as written \u2014 profileSlug, aggregation, timePeriod AND a label
3318
+ naming the window. Never drop timePeriod and never label a windowed
3319
+ count with a bare kind name: the card counts records CREATED in that
3320
+ window, so "Tasks" over a weekly count is a false statement about
3321
+ the workspace.
3322
+ (c) Close EVERY embed with its own ::: line, per rule 2. An unclosed embed
3323
+ swallows the rest of the section.
3324
+ (d) EDITORIAL RULE \u2014 embed to SHOW what a sentence cannot: a distribution,
3325
+ a ratio, a count. Never to decorate. At most 2-3 in the whole report,
3326
+ placed inside the section whose prose they support. A chart that merely
3327
+ restates the sentence beside it is noise, and noise is worse than
3328
+ nothing here. If no gathered kind has a distribution worth showing,
3329
+ emit no cells at all \u2014 that is a correct report, not a lesser one.
3330
+ (e) AT MOST ONE cell per section, and NEVER at the top of a section.
3331
+ Place it immediately AFTER the sentence making the claim it evidences,
3332
+ so the reader has the claim before the picture. A chart that opens a
3333
+ section makes the reader decode a graphic to find out what the section
3334
+ is about, which is the delay the ## heading and the claim sentence
3335
+ exist to remove.
3336
+ (f) The label must state the FINDING, not the dimension. Write
3337
+ label:"Half the open tasks are blocked", never label:"Distribution",
3338
+ "Status" or "Tasks by status". Always set label explicitly: with none,
3339
+ a pie chart titles itself "Distribution" and a bar chart "Breakdown" \u2014
3340
+ words true of every such chart ever drawn, which tell the reader
3341
+ nothing they cannot already see in the shapes.
3342
+ (g) A CHART IS A LIVE VIEW, THE PROSE IS A SNAPSHOT. These cells run
3343
+ their own query when the report is OPENED, which may be long after
3344
+ this text was written \u2014 so a sentence quoting a count and a chart
3345
+ measuring the same thing WILL eventually disagree, and the reader
3346
+ cannot tell which is stale. Never write a specific number in a
3347
+ sentence that sits beside a chart of the same data: let the chart
3348
+ carry the number and let the sentence carry the claim
3349
+ ("most open tasks are stalled at review", not "12 of 19 are stalled").
3350
+ And if the sentence already gives the number the chart would show,
3351
+ DROP THE CHART \u2014 one of them is redundant, and the sentence is the one
3352
+ that cannot go stale against itself.
3353
+ Everything else this report needs is ::::synap-section containers plus
3354
+ [[entity:\u2026]] chips inline.
3355
+
3356
+ 10. EVERY ::::synap-section MUST OPEN WITH A ## CLAIM HEADING \u2014 its first
3357
+ line, before any prose. The heading states WHAT THE SECTION FOUND, not
3358
+ what produced it: "## Half the open tasks are blocked", never
3359
+ "## Analysis", "## Analyze round" or "## Findings". Under 10 words, no
3360
+ trailing period, no markdown or [[entity:\u2026]] chip inside the heading
3361
+ itself \u2014 put the chip in the prose beneath it. Write exactly one ## per
3362
+ section. This heading is the section's title everywhere it is read: the
3363
+ slide deck sets it as the slide headline (with no heading it falls back
3364
+ to the round and agent names, which the slide already shows underneath),
3365
+ and the scrolling reader has no section titles at all without it. A
3366
+ section whose heading merely names the round tells the reader nothing
3367
+ they cannot already see.
3368
+
3369
+ STRUCTURE: one ::::synap-section per round, in order \u2014 analyze, then relate.
3370
+ Open with a single "# " title line ABOVE the first section, and open each
3371
+ section with its own ## claim heading per rule 10.
3372
+
3373
+ STEER HANDLING: if the STEER lines are empty, title the report generically
3374
+ ("Workspace report \u2014 <period>") and keep the rounds in their given order. If any
3375
+ STEER line has text, name that subject in the title and lead each section with
3376
+ the material that answers it. Never add facts the rounds did not give you.
3377
+
3378
+ MISSING ROUNDS: if a round's material looks like {"error": "..."} or is empty,
3379
+ you MUST still emit its section, with status="failed", containing one visible
3380
+ sentence naming what is missing. NEVER silently produce a shorter report \u2014 a
3381
+ reader must be able to see that a round did not run. A failed section is NOT
3382
+ exempt from rule 10: give it a ## heading that states what is missing
3383
+ ("## The patterns round produced nothing"), because it still occupies a slide
3384
+ and still needs a headline.
3385
+
3386
+ WORKED EXAMPLE of correct output:
3387
+ # Workspace report \u2014 July 2026
3388
+
3389
+ ::::synap-section{agent="analyst" round="analyze" confidence="0.8"}
3390
+ ## Most open tasks are stalled at review
3391
+
3392
+ Open tasks are piling up untouched since the sprint opened, and the great
3393
+ majority are sitting in one status \u2014 the oldest is
3394
+ [[entity:3f2a91c4-7b10-4e55-9c02-8ad1f6e4b2d7|Migrate the billing worker]].
3395
+ Notes are thin.
3396
+ :::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"task","groupBy":"status","label":"Review is where open tasks accumulate"}'}
3397
+ :::
3398
+ ::::
3399
+
3400
+ ::::synap-section{agent="analyst" round="relate" status="failed" confidence="0.2"}
3401
+ ## The patterns round produced nothing
3402
+
3403
+ This section is missing: the patterns round failed and produced no material.
3404
+ ::::
3405
+
3406
+ Be CONCISE \u2014 your entire output must fit in 2000 tokens. Emit ONLY the markdown
3407
+ document. No preamble, no explanation, no code fences around the whole thing.`,
3408
+ "prompt": "STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nReport generated at: {{steps.now.output.result}}\n\nANALYZE ROUND MATERIAL:\n{{steps.analyze.output}}\n\nRELATE ROUND MATERIAL:\n{{steps.relate.output}}",
3409
+ "maxTokens": "2000"
3410
+ }
3411
+ }
3412
+ },
3413
+ {
3414
+ "id": "summarize",
3415
+ "type": "capability",
3416
+ "position": {
3417
+ "x": 1380,
3418
+ "y": 200
3419
+ },
3420
+ "data": {
3421
+ "label": "One-line summary",
3422
+ "verbId": "ai.generate",
3423
+ "verbKind": "read",
3424
+ "inputMapping": {
3425
+ "system": 'Write ONE plain sentence, under 200 characters, stating what this report found.\nNo markdown, no directives, no quotes, no leading label. Just the sentence.\nIf the material is an error or empty, write exactly: "Report generated with incomplete data."',
3426
+ "prompt": "Material to summarize (may be an error object if the round failed):\n{{steps.analyze.output}}",
3427
+ "maxTokens": "500"
3428
+ }
3429
+ }
3430
+ },
3431
+ {
3432
+ "id": "guard-assembled",
3433
+ "type": "guard",
3434
+ "position": {
3435
+ "x": 1620,
3436
+ "y": 200
3437
+ },
3438
+ "data": {
3439
+ "label": "Body assembled",
3440
+ "checks": [
3441
+ {
3442
+ "path": "steps.assemble.output",
3443
+ "minLength": 200,
3444
+ "message": "The assembler produced no usable body (under 200 characters) \u2014 refusing to write an empty report."
3445
+ },
3446
+ {
3447
+ "path": "steps.assemble.output.error",
3448
+ "exists": false,
3449
+ "message": "The assembler round failed \u2014 refusing to write a report without a body."
3450
+ }
3451
+ ]
3452
+ }
3453
+ },
3454
+ {
3455
+ "id": "create-report",
3456
+ "type": "output",
3457
+ "position": {
3458
+ "x": 1860,
3459
+ "y": 200
3460
+ },
3461
+ "data": {
3462
+ "label": "Create report",
3463
+ "outputType": "entity_create",
3464
+ "config": {
3465
+ "profileSlug": "report",
3466
+ "title": "Workspace report \u2014 {{steps.now.output.result}}",
3467
+ "properties": {
3468
+ "reportPeriod": "{{steps.now.output.result}}",
3469
+ "generatedAt": "{{steps.now.output.result}}",
3470
+ "reportStatus": "ready",
3471
+ "summary": "{{steps.summarize.output}}",
3472
+ "reportSources": [
3473
+ "Tasks",
3474
+ "Notes",
3475
+ "People",
3476
+ "Companies"
3477
+ ]
3478
+ },
3479
+ "body": "{{steps.assemble.output}}",
3480
+ "systemData": {
3481
+ "renderer": {
3482
+ "kind": "cell",
3483
+ "cellKey": "{{trigger.payload.renderer}}"
3484
+ }
3485
+ }
3486
+ }
3487
+ }
3488
+ }
3489
+ ],
3490
+ "edges": [
3491
+ {
3492
+ "id": "e-trigger-now",
3493
+ "source": "trigger",
3494
+ "target": "now"
3495
+ },
3496
+ {
3497
+ "id": "e-now-tasks",
3498
+ "source": "now",
3499
+ "target": "gather-tasks"
3500
+ },
3501
+ {
3502
+ "id": "e-now-notes",
3503
+ "source": "now",
3504
+ "target": "gather-notes"
3505
+ },
3506
+ {
3507
+ "id": "e-now-people",
3508
+ "source": "now",
3509
+ "target": "gather-people"
3510
+ },
3511
+ {
3512
+ "id": "e-now-companies",
3513
+ "source": "now",
3514
+ "target": "gather-companies"
3515
+ },
3516
+ {
3517
+ "id": "e-tasks-project",
3518
+ "source": "gather-tasks",
3519
+ "target": "project-tasks"
3520
+ },
3521
+ {
3522
+ "id": "e-notes-project",
3523
+ "source": "gather-notes",
3524
+ "target": "project-notes"
3525
+ },
3526
+ {
3527
+ "id": "e-people-project",
3528
+ "source": "gather-people",
3529
+ "target": "project-people"
3530
+ },
3531
+ {
3532
+ "id": "e-companies-project",
3533
+ "source": "gather-companies",
3534
+ "target": "project-companies"
3535
+ },
3536
+ {
3537
+ "id": "e-tasks-analyze",
3538
+ "source": "project-tasks",
3539
+ "target": "analyze"
3540
+ },
3541
+ {
3542
+ "id": "e-notes-analyze",
3543
+ "source": "project-notes",
3544
+ "target": "analyze"
3545
+ },
3546
+ {
3547
+ "id": "e-people-analyze",
3548
+ "source": "project-people",
3549
+ "target": "analyze"
3550
+ },
3551
+ {
3552
+ "id": "e-companies-analyze",
3553
+ "source": "project-companies",
3554
+ "target": "analyze"
3555
+ },
3556
+ {
3557
+ "id": "e-analyze-relate",
3558
+ "source": "analyze",
3559
+ "target": "relate"
3560
+ },
3561
+ {
3562
+ "id": "e-relate-assemble",
3563
+ "source": "relate",
3564
+ "target": "assemble"
3565
+ },
3566
+ {
3567
+ "id": "e-assemble-summarize",
3568
+ "source": "assemble",
3569
+ "target": "summarize"
3570
+ },
3571
+ {
3572
+ "id": "e-summarize-guard",
3573
+ "source": "summarize",
3574
+ "target": "guard-assembled"
3575
+ },
3576
+ {
3577
+ "id": "e-guard-create",
3578
+ "source": "guard-assembled",
3579
+ "target": "create-report"
3580
+ }
3581
+ ]
3582
+ }
3583
+ }
3584
+ ]
3585
+ },
3586
+ "blockchain-ecosystem": {
3587
+ "meta": {
3588
+ "slug": "blockchain-ecosystem",
3589
+ "domain": "strategy",
3590
+ "name": "Blockchain Ecosystem",
3591
+ "description": "Web3 market actors \u2014 market-makers, auditors, dev shops, KOLs \u2014 and how they connect. Overlays the Ecosystem workspace.",
3592
+ "icon": "boxes",
3593
+ "color": "#8B5CF6",
3594
+ "tags": [
3595
+ "blockchain",
3596
+ "web3",
3597
+ "ecosystem",
3598
+ "market-makers"
3599
+ ],
3600
+ "isPublic": false
3601
+ },
3602
+ "dependencies": [
3603
+ {
3604
+ "slug": "ecosystem",
3605
+ "kind": "workspace",
3606
+ "relation": "compose",
3607
+ "reason": "Adds web3 market actors + relations onto your Ecosystem."
3608
+ }
3609
+ ],
3610
+ "workspace": {
3611
+ "name": "Ecosystem",
3612
+ "description": "Web3 market actors and the relationships between them, layered onto your Ecosystem.",
3613
+ "subtype": "ecosystem"
3614
+ },
3615
+ "profiles": [
3616
+ {
3617
+ "slug": "market-maker",
3618
+ "displayName": "Market Maker",
3619
+ "icon": "candlestick-chart",
3620
+ "color": "sky",
3621
+ "description": "A liquidity provider \u2014 makes markets and provides depth for a token.",
3622
+ "scope": "shared",
3623
+ "profileKind": "role",
3624
+ "applicableKinds": [
3625
+ "company",
3626
+ "person"
3627
+ ],
3628
+ "properties": [
3629
+ {
3630
+ "slug": "status",
3631
+ "label": "Status",
3632
+ "valueType": "string",
3633
+ "inputType": "select",
3634
+ "enumValues": [
3635
+ "prospect",
3636
+ "active",
3637
+ "paused",
3638
+ "churned"
3639
+ ]
3640
+ },
3641
+ {
3642
+ "slug": "exchanges",
3643
+ "label": "Exchanges / Venues",
3644
+ "valueType": "array",
3645
+ "inputType": "text"
3646
+ },
3647
+ {
3648
+ "slug": "engagementStartDate",
3649
+ "label": "Engagement Start",
3650
+ "valueType": "date",
3651
+ "inputType": "date"
3652
+ },
3653
+ {
3654
+ "slug": "notes",
3655
+ "label": "Notes",
3656
+ "valueType": "string",
3657
+ "inputType": "textarea"
3658
+ }
3659
+ ]
3660
+ },
3661
+ {
3662
+ "slug": "security-auditor",
3663
+ "displayName": "Security Auditor",
3664
+ "icon": "shield-check",
3665
+ "color": "red",
3666
+ "description": "A security firm or individual that audits smart contracts and protocols.",
3667
+ "scope": "shared",
3668
+ "profileKind": "role",
3669
+ "applicableKinds": [
3670
+ "company",
3671
+ "person"
3672
+ ],
3673
+ "properties": [
3674
+ {
3675
+ "slug": "status",
3676
+ "label": "Status",
3677
+ "valueType": "string",
3678
+ "inputType": "select",
3679
+ "enumValues": [
3680
+ "prospect",
3681
+ "active",
3682
+ "paused",
3683
+ "churned"
3684
+ ]
3685
+ },
3686
+ {
3687
+ "slug": "specialty",
3688
+ "label": "Specialty",
3689
+ "valueType": "string",
3690
+ "inputType": "text"
3691
+ },
3692
+ {
3693
+ "slug": "engagementStartDate",
3694
+ "label": "Engagement Start",
3695
+ "valueType": "date",
3696
+ "inputType": "date"
3697
+ },
3698
+ {
3699
+ "slug": "notes",
3700
+ "label": "Notes",
3701
+ "valueType": "string",
3702
+ "inputType": "textarea"
3703
+ }
3704
+ ]
3705
+ },
3706
+ {
3707
+ "slug": "dev-shop",
3708
+ "displayName": "Dev Shop",
3709
+ "icon": "code",
3710
+ "color": "violet",
3711
+ "description": "An outsourced engineering studio or build partner.",
3712
+ "scope": "shared",
3713
+ "profileKind": "role",
3714
+ "applicableKinds": [
3715
+ "company",
3716
+ "person"
3717
+ ],
3718
+ "properties": [
3719
+ {
3720
+ "slug": "status",
3721
+ "label": "Status",
3722
+ "valueType": "string",
3723
+ "inputType": "select",
3724
+ "enumValues": [
3725
+ "prospect",
3726
+ "active",
3727
+ "paused",
3728
+ "churned"
3729
+ ]
3730
+ },
3731
+ {
3732
+ "slug": "specialty",
3733
+ "label": "Specialty",
3734
+ "valueType": "string",
3735
+ "inputType": "text"
3736
+ },
3737
+ {
3738
+ "slug": "engagementStartDate",
3739
+ "label": "Engagement Start",
3740
+ "valueType": "date",
3741
+ "inputType": "date"
3742
+ },
3743
+ {
3744
+ "slug": "notes",
3745
+ "label": "Notes",
3746
+ "valueType": "string",
3747
+ "inputType": "textarea"
3748
+ }
3749
+ ]
3750
+ },
3751
+ {
3752
+ "slug": "marketing-agency",
3753
+ "displayName": "Marketing Agency",
3754
+ "icon": "megaphone",
3755
+ "color": "pink",
3756
+ "description": "A brand, growth, or content marketing agency.",
3757
+ "scope": "shared",
3758
+ "profileKind": "role",
3759
+ "applicableKinds": [
3760
+ "company",
3761
+ "person"
3762
+ ],
3763
+ "properties": [
3764
+ {
3765
+ "slug": "status",
3766
+ "label": "Status",
3767
+ "valueType": "string",
3768
+ "inputType": "select",
3769
+ "enumValues": [
3770
+ "prospect",
3771
+ "active",
3772
+ "paused",
3773
+ "churned"
3774
+ ]
3775
+ },
3776
+ {
3777
+ "slug": "specialty",
3778
+ "label": "Specialty",
3779
+ "valueType": "string",
3780
+ "inputType": "text"
3781
+ },
3782
+ {
3783
+ "slug": "engagementStartDate",
3784
+ "label": "Engagement Start",
3785
+ "valueType": "date",
3786
+ "inputType": "date"
3787
+ },
3788
+ {
3789
+ "slug": "notes",
3790
+ "label": "Notes",
3791
+ "valueType": "string",
3792
+ "inputType": "textarea"
3793
+ }
3794
+ ]
3795
+ },
3796
+ {
3797
+ "slug": "paid-ads-agency",
3798
+ "displayName": "Paid-Ads Agency",
3799
+ "icon": "badge-dollar-sign",
3800
+ "color": "orange",
3801
+ "description": "A performance / paid-media buying agency.",
3802
+ "scope": "shared",
3803
+ "profileKind": "role",
3804
+ "applicableKinds": [
3805
+ "company",
3806
+ "person"
3807
+ ],
3808
+ "properties": [
3809
+ {
3810
+ "slug": "status",
3811
+ "label": "Status",
3812
+ "valueType": "string",
3813
+ "inputType": "select",
3814
+ "enumValues": [
3815
+ "prospect",
3816
+ "active",
3817
+ "paused",
3818
+ "churned"
3819
+ ]
3820
+ },
3821
+ {
3822
+ "slug": "channels",
3823
+ "label": "Channels",
3824
+ "valueType": "array",
3825
+ "inputType": "text"
3826
+ },
3827
+ {
3828
+ "slug": "engagementStartDate",
3829
+ "label": "Engagement Start",
3830
+ "valueType": "date",
3831
+ "inputType": "date"
3832
+ },
3833
+ {
3834
+ "slug": "notes",
3835
+ "label": "Notes",
3836
+ "valueType": "string",
3837
+ "inputType": "textarea"
3838
+ }
3839
+ ]
3840
+ },
3841
+ {
3842
+ "slug": "kol",
3843
+ "displayName": "KOL",
3844
+ "icon": "star",
3845
+ "color": "yellow",
3846
+ "description": "A key opinion leader or influencer who amplifies reach.",
3847
+ "scope": "shared",
3001
3848
  "profileKind": "role",
3002
3849
  "applicableKinds": [
3003
3850
  "company",
@@ -6796,17 +7643,16 @@ var DATA = {
6796
7643
  },
6797
7644
  "profiles": [
6798
7645
  {
6799
- "slug": "crm-lead",
6800
- "displayName": "CRM Lead",
6801
- "icon": "user-round-search",
6802
- "color": "amber",
6803
- "description": "A person or company actively being qualified or contacted by this CRM workspace.",
6804
- "scope": "WORKSPACE",
7646
+ "slug": "lead",
7647
+ "displayName": "Lead",
7648
+ "icon": "user-plus",
7649
+ "color": "yellow",
7650
+ "description": "An early-pipeline prospect \u2014 not yet a client, being qualified or nurtured.",
7651
+ "scope": "shared",
6805
7652
  "profileKind": "role",
6806
- "semanticSlug": "crm.lead",
6807
7653
  "applicableKinds": [
6808
- "person",
6809
- "company"
7654
+ "company",
7655
+ "person"
6810
7656
  ],
6811
7657
  "properties": [
6812
7658
  {
@@ -6903,17 +7749,16 @@ var DATA = {
6903
7749
  ]
6904
7750
  },
6905
7751
  {
6906
- "slug": "crm-client",
6907
- "displayName": "CRM Client",
6908
- "icon": "badge-check",
7752
+ "slug": "client",
7753
+ "displayName": "Client",
7754
+ "icon": "briefcase",
6909
7755
  "color": "emerald",
6910
- "description": "A CRM lead converted by a won commercial proposal and ready for handoff.",
6911
- "scope": "WORKSPACE",
7756
+ "description": "An active commercial relationship \u2014 created when a deal is won.",
7757
+ "scope": "shared",
6912
7758
  "profileKind": "role",
6913
- "semanticSlug": "crm.client",
6914
7759
  "applicableKinds": [
6915
- "person",
6916
- "company"
7760
+ "company",
7761
+ "person"
6917
7762
  ],
6918
7763
  "properties": [
6919
7764
  {
@@ -7278,7 +8123,7 @@ var DATA = {
7278
8123
  "description": "Research and qualify one CRM lead, then make the next action explicit without creating a commercial proposal automatically.",
7279
8124
  "goalTemplate": "Work on CRM lead ({lead}). Verify and enrich only factual information that can be supported, assess fit, and update its CRM lifecycle stage, ICP score, qualification notes, outreach channels, and next action. Do not create a deal simply because the lead is qualified. If a commercial proposal is warranted, explain why and ask for or propose that next step separately.",
7280
8125
  "subjectProfile": {
7281
- "profileSlug": "crm-lead"
8126
+ "profileSlug": "lead"
7282
8127
  },
7283
8128
  "params": [
7284
8129
  {
@@ -7381,7 +8226,7 @@ var DATA = {
7381
8226
  },
7382
8227
  {
7383
8228
  "path": "steps.read-deal.output.entity.properties.sourcedByUserId",
7384
- "exists": true,
8229
+ "minLength": 1,
7385
8230
  "message": "Set the business developer who sourced this proposal before handing it off."
7386
8231
  },
7387
8232
  {
@@ -7451,7 +8296,7 @@ var DATA = {
7451
8296
  "checks": [
7452
8297
  {
7453
8298
  "path": "steps.read-party.output.entity.facetSlugs",
7454
- "arrayIncludes": "crm-lead",
8299
+ "arrayIncludes": "lead",
7455
8300
  "message": "The commercial counterparty must still be a CRM lead before it becomes a CRM client."
7456
8301
  }
7457
8302
  ]
@@ -7714,7 +8559,7 @@ var DATA = {
7714
8559
  "outputType": "facet_attach",
7715
8560
  "config": {
7716
8561
  "entityId": "{{steps.read-party.output.entity.id}}",
7717
- "facetSlug": "crm-client",
8562
+ "facetSlug": "client",
7718
8563
  "properties": {
7719
8564
  "becameClientAt": "{{steps.handoff-time.output.result}}",
7720
8565
  "handoffStatus": "handed_off"
@@ -7734,7 +8579,7 @@ var DATA = {
7734
8579
  "outputType": "facet_update",
7735
8580
  "config": {
7736
8581
  "entityId": "{{steps.read-party.output.entity.id}}",
7737
- "facetSlug": "crm-lead",
8582
+ "facetSlug": "lead",
7738
8583
  "properties": {
7739
8584
  "lifecycleStage": "converted",
7740
8585
  "convertedAt": "{{steps.handoff-time.output.result}}"
@@ -7877,7 +8722,7 @@ var DATA = {
7877
8722
  "label": "Qualify lead",
7878
8723
  "when": {
7879
8724
  "requiredFacetSlugs": [
7880
- "crm-lead"
8725
+ "lead"
7881
8726
  ]
7882
8727
  }
7883
8728
  },
@@ -7889,7 +8734,7 @@ var DATA = {
7889
8734
  "label": "Qualify lead",
7890
8735
  "when": {
7891
8736
  "requiredFacetSlugs": [
7892
- "crm-lead"
8737
+ "lead"
7893
8738
  ]
7894
8739
  }
7895
8740
  },
@@ -9211,7 +10056,8 @@ var DATA = {
9211
10056
  "scopeProfileSlug": "post",
9212
10057
  "description": "Your editorial calendar \u2014 every post on the day it publishes.",
9213
10058
  "config": {
9214
- "dateField": "properties.publish-date"
10059
+ "dateField": "properties.publish-date",
10060
+ "focusKind": "post"
9215
10061
  }
9216
10062
  },
9217
10063
  {
@@ -9353,8 +10199,84 @@ var DATA = {
9353
10199
  "w": 6,
9354
10200
  "h": 5
9355
10201
  }
9356
- }
9357
- ]
10202
+ }
10203
+ ]
10204
+ },
10205
+ "profileEntityBentoTemplates": {
10206
+ "series": {
10207
+ "blocks": [
10208
+ {
10209
+ "id": "series-eb-header",
10210
+ "kind": "widget",
10211
+ "widgetType": "entity-header",
10212
+ "pos": {
10213
+ "x": 0,
10214
+ "y": 0,
10215
+ "w": 12,
10216
+ "h": 2
10217
+ }
10218
+ },
10219
+ {
10220
+ "id": "series-eb-props",
10221
+ "kind": "widget",
10222
+ "widgetType": "entity-properties",
10223
+ "pos": {
10224
+ "x": 0,
10225
+ "y": 2,
10226
+ "w": 5,
10227
+ "h": 8
10228
+ }
10229
+ },
10230
+ {
10231
+ "id": "series-eb-curriculum",
10232
+ "kind": "widget",
10233
+ "widgetType": "series-curriculum",
10234
+ "pos": {
10235
+ "x": 5,
10236
+ "y": 2,
10237
+ "w": 7,
10238
+ "h": 8
10239
+ }
10240
+ }
10241
+ ]
10242
+ },
10243
+ "pillar": {
10244
+ "blocks": [
10245
+ {
10246
+ "id": "pillar-eb-header",
10247
+ "kind": "widget",
10248
+ "widgetType": "entity-header",
10249
+ "pos": {
10250
+ "x": 0,
10251
+ "y": 0,
10252
+ "w": 12,
10253
+ "h": 2
10254
+ }
10255
+ },
10256
+ {
10257
+ "id": "pillar-eb-props",
10258
+ "kind": "widget",
10259
+ "widgetType": "entity-properties",
10260
+ "pos": {
10261
+ "x": 0,
10262
+ "y": 2,
10263
+ "w": 5,
10264
+ "h": 8
10265
+ }
10266
+ },
10267
+ {
10268
+ "id": "pillar-eb-chapter",
10269
+ "kind": "widget",
10270
+ "widgetType": "pillar-chapter",
10271
+ "pos": {
10272
+ "x": 5,
10273
+ "y": 2,
10274
+ "w": 7,
10275
+ "h": 8
10276
+ }
10277
+ }
10278
+ ]
10279
+ }
9358
10280
  },
9359
10281
  "entityLinks": [
9360
10282
  {
@@ -10085,11 +11007,12 @@ var DATA = {
10085
11007
  ],
10086
11008
  "isPublic": true
10087
11009
  },
10088
- "app": {
10089
- "kind": "url",
10090
- "appId": "crm",
10091
- "url": "https://crm.synap.live",
10092
- "title": "CRM"
11010
+ "layout": {
11011
+ "primarySurface": {
11012
+ "kind": "url",
11013
+ "url": "https://crm.synap.live",
11014
+ "title": "CRM"
11015
+ }
10093
11016
  },
10094
11017
  "dependencies": [
10095
11018
  {
@@ -10126,6 +11049,7 @@ var DATA = {
10126
11049
  "icon": "user",
10127
11050
  "color": "amber",
10128
11051
  "description": "A prospect, stakeholder, or contact.",
11052
+ "entityScope": "pod",
10129
11053
  "properties": [
10130
11054
  {
10131
11055
  "slug": "email",
@@ -10211,7 +11135,7 @@ var DATA = {
10211
11135
  "icon": "building-2",
10212
11136
  "color": "#10B981",
10213
11137
  "description": "An organisation being qualified or sold to.",
10214
- "scope": "WORKSPACE",
11138
+ "entityScope": "pod",
10215
11139
  "properties": [
10216
11140
  {
10217
11141
  "slug": "contextSummary",
@@ -10481,6 +11405,18 @@ var DATA = {
10481
11405
  "at_risk",
10482
11406
  "critical"
10483
11407
  ]
11408
+ },
11409
+ {
11410
+ "slug": "driveLink",
11411
+ "label": "Drive folder",
11412
+ "valueType": "string",
11413
+ "inputType": "url"
11414
+ },
11415
+ {
11416
+ "slug": "driveFolderId",
11417
+ "label": "Drive folder ID",
11418
+ "valueType": "string",
11419
+ "inputType": "text"
10484
11420
  }
10485
11421
  ]
10486
11422
  },
@@ -10656,6 +11592,30 @@ var DATA = {
10656
11592
  "sourceProfileSlug": "company",
10657
11593
  "targetProfileSlug": "deal",
10658
11594
  "type": "linked_to_deal"
11595
+ },
11596
+ {
11597
+ "sourceProfileSlug": "person",
11598
+ "targetProfileSlug": "deal",
11599
+ "type": "client_on_deal",
11600
+ "label": "Client side"
11601
+ },
11602
+ {
11603
+ "sourceProfileSlug": "company",
11604
+ "targetProfileSlug": "deal",
11605
+ "type": "client_on_deal",
11606
+ "label": "Client side"
11607
+ },
11608
+ {
11609
+ "sourceProfileSlug": "person",
11610
+ "targetProfileSlug": "deal",
11611
+ "type": "partner_on_deal",
11612
+ "label": "Partner side"
11613
+ },
11614
+ {
11615
+ "sourceProfileSlug": "company",
11616
+ "targetProfileSlug": "deal",
11617
+ "type": "partner_on_deal",
11618
+ "label": "Partner side"
10659
11619
  }
10660
11620
  ],
10661
11621
  "onboarding": {
@@ -12524,6 +13484,7 @@ var DATA = {
12524
13484
  "icon": "user",
12525
13485
  "color": "amber",
12526
13486
  "description": "A person in the ecosystem \u2014 a founder, operator, or contact.",
13487
+ "entityScope": "pod",
12527
13488
  "properties": [
12528
13489
  {
12529
13490
  "slug": "email",
@@ -12545,6 +13506,7 @@ var DATA = {
12545
13506
  "icon": "building-2",
12546
13507
  "color": "#10B981",
12547
13508
  "description": "A company in the ecosystem \u2014 a player you operate around.",
13509
+ "entityScope": "pod",
12548
13510
  "properties": [
12549
13511
  {
12550
13512
  "slug": "website",
@@ -13273,7 +14235,7 @@ var DATA = {
13273
14235
  "slug": "business-developer",
13274
14236
  "kind": "workspace",
13275
14237
  "relation": "require",
13276
- "reason": "The lead-first sales motion (crm-lead/crm-client roles, commercial proposals, commission handoff) that the CRM app runs on. Composes onto the CRM workspace above \u2014 without it the CRM lens is provisioned with pod-wide lead/client only and the app's pipeline is empty."
14238
+ "reason": "The lead-first sales motion (the business-development overlay on the pod-wide lead/client roles, commercial proposals, commission handoff) that the CRM app runs on. Composes onto the CRM workspace above \u2014 without it the CRM lens has the bare lead/client roles with no lifecycle or handoff fields and the app's pipeline is empty."
13277
14239
  },
13278
14240
  {
13279
14241
  "slug": "content-os",
@@ -14757,6 +15719,18 @@ var DATA = {
14757
15719
  "at_risk",
14758
15720
  "critical"
14759
15721
  ]
15722
+ },
15723
+ {
15724
+ "slug": "driveLink",
15725
+ "label": "Drive folder",
15726
+ "valueType": "string",
15727
+ "inputType": "url"
15728
+ },
15729
+ {
15730
+ "slug": "driveFolderId",
15731
+ "label": "Drive folder ID",
15732
+ "valueType": "string",
15733
+ "inputType": "text"
14760
15734
  }
14761
15735
  ]
14762
15736
  },
@@ -16209,6 +17183,18 @@ var DATA = {
16209
17183
  "at_risk",
16210
17184
  "critical"
16211
17185
  ]
17186
+ },
17187
+ {
17188
+ "slug": "driveLink",
17189
+ "label": "Drive folder",
17190
+ "valueType": "string",
17191
+ "inputType": "url"
17192
+ },
17193
+ {
17194
+ "slug": "driveFolderId",
17195
+ "label": "Drive folder ID",
17196
+ "valueType": "string",
17197
+ "inputType": "text"
16212
17198
  }
16213
17199
  ]
16214
17200
  },
@@ -19537,7 +20523,7 @@ var DATA = {
19537
20523
  "icon": "User",
19538
20524
  "color": "#06B6D4",
19539
20525
  "description": "People in your intellectual and professional world.",
19540
- "scope": "WORKSPACE",
20526
+ "entityScope": "pod",
19541
20527
  "properties": [
19542
20528
  {
19543
20529
  "slug": "name",
@@ -23888,6 +24874,24 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
23888
24874
  "targetProfileSlug": "company",
23889
24875
  "type": "briefing_about_client",
23890
24876
  "label": "Client"
24877
+ },
24878
+ {
24879
+ "sourceProfileSlug": "engagement",
24880
+ "targetProfileSlug": "company",
24881
+ "type": "engagement_for_client",
24882
+ "label": "Client"
24883
+ },
24884
+ {
24885
+ "sourceProfileSlug": "engagement",
24886
+ "targetProfileSlug": "person",
24887
+ "type": "engagement_for_client",
24888
+ "label": "Client"
24889
+ },
24890
+ {
24891
+ "sourceProfileSlug": "contract",
24892
+ "targetProfileSlug": "engagement",
24893
+ "type": "contract_for_engagement",
24894
+ "label": "Engagement"
23891
24895
  }
23892
24896
  ],
23893
24897
  "views": [
@@ -23946,83 +24950,327 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
23946
24950
  "due-date"
23947
24951
  ]
23948
24952
  }
23949
- ],
23950
- "openingQuestions": [
23951
- "Which client engagements are active right now, and who's the client on each?",
23952
- "What's due next \u2014 which deliverables are in flight, and when are they owed?",
23953
- "Which engagements have a contract behind them, and which are still on a handshake?"
23954
- ],
23955
- "doneWhen": "at least one active engagement with a deliverable placed inside it."
23956
- },
23957
- "app": {
23958
- "kind": "stored",
23959
- "cellKey": "entity-detail"
23960
- },
23961
- "layout": {
23962
- "pinnedApps": [
23963
- "dashboard",
23964
- "data"
23965
- ],
23966
- "defaultView": "Engagements",
23967
- "defaultApp": null,
23968
- "sidebarItems": [
23969
- {
23970
- "kind": "view",
23971
- "viewName": "Engagements",
23972
- "label": "Engagements",
23973
- "icon": "briefcase"
23974
- },
23975
- {
23976
- "kind": "view",
23977
- "viewName": "Deliverables",
23978
- "label": "Deliverables",
23979
- "icon": "package-check"
23980
- },
23981
- {
23982
- "kind": "view",
23983
- "viewName": "Contracts",
23984
- "label": "Contracts",
23985
- "icon": "file-signature"
23986
- }
23987
- ]
23988
- },
23989
- "bento": {
23990
- "viewName": "Engagements",
23991
- "blocks": [
23992
- {
23993
- "kind": "view",
23994
- "viewName": "Deliverables",
23995
- "pos": {
23996
- "x": 0,
23997
- "y": 0,
23998
- "w": 6,
23999
- "h": 5
24000
- }
24001
- },
24002
- {
24003
- "kind": "view",
24004
- "viewName": "Contracts",
24005
- "pos": {
24006
- "x": 6,
24007
- "y": 0,
24008
- "w": 6,
24009
- "h": 5
24010
- }
24011
- }
24012
- ]
24013
- },
24014
- "automations": [
24953
+ ],
24954
+ "openingQuestions": [
24955
+ "Which client engagements are active right now, and who's the client on each?",
24956
+ "What's due next \u2014 which deliverables are in flight, and when are they owed?",
24957
+ "Which engagements have a contract behind them, and which are still on a handshake?"
24958
+ ],
24959
+ "doneWhen": "at least one active engagement with a deliverable placed inside it."
24960
+ },
24961
+ "app": {
24962
+ "kind": "stored",
24963
+ "cellKey": "entity-detail"
24964
+ },
24965
+ "layout": {
24966
+ "pinnedApps": [
24967
+ "dashboard",
24968
+ "data"
24969
+ ],
24970
+ "defaultView": "Engagements",
24971
+ "defaultApp": null,
24972
+ "sidebarItems": [
24973
+ {
24974
+ "kind": "view",
24975
+ "viewName": "Engagements",
24976
+ "label": "Engagements",
24977
+ "icon": "briefcase"
24978
+ },
24979
+ {
24980
+ "kind": "view",
24981
+ "viewName": "Deliverables",
24982
+ "label": "Deliverables",
24983
+ "icon": "package-check"
24984
+ },
24985
+ {
24986
+ "kind": "view",
24987
+ "viewName": "Contracts",
24988
+ "label": "Contracts",
24989
+ "icon": "file-signature"
24990
+ }
24991
+ ]
24992
+ },
24993
+ "bento": {
24994
+ "viewName": "Engagements",
24995
+ "blocks": [
24996
+ {
24997
+ "kind": "view",
24998
+ "viewName": "Deliverables",
24999
+ "pos": {
25000
+ "x": 0,
25001
+ "y": 0,
25002
+ "w": 6,
25003
+ "h": 5
25004
+ }
25005
+ },
25006
+ {
25007
+ "kind": "view",
25008
+ "viewName": "Contracts",
25009
+ "pos": {
25010
+ "x": 6,
25011
+ "y": 0,
25012
+ "w": 6,
25013
+ "h": 5
25014
+ }
25015
+ }
25016
+ ]
25017
+ },
25018
+ "automations": [
25019
+ {
25020
+ "key": "operations-daily-client-briefing",
25021
+ "name": "Daily client briefing",
25022
+ "description": "Operations-native daily client briefing. Writes a durable, dated client-briefing ENTITY per client (queryable history, linked to the client) from their recent comms, and posts the recap to the client's internal TEAM channel (never the client-facing channel). Knowledge-building and entity-first \u2014 the delivery OS's structured memory of each client. For a lighter, portable message-only digest installable on any pod, use the client-comms-report capability.",
25023
+ "triggerType": "cron",
25024
+ "triggerConfig": {
25025
+ "expression": "0 8 * * *"
25026
+ },
25027
+ "status": "active",
25028
+ "metadata": {
25029
+ "templateKey": "operations-daily-client-briefing",
25030
+ "requiresPodWideEntityQueryLens": true
25031
+ },
25032
+ "flow": {
25033
+ "nodes": [
25034
+ {
25035
+ "id": "trigger",
25036
+ "type": "trigger",
25037
+ "position": {
25038
+ "x": 0,
25039
+ "y": 0
25040
+ },
25041
+ "data": {
25042
+ "label": "Daily at 08:00",
25043
+ "triggerType": "cron",
25044
+ "config": {
25045
+ "expression": "0 8 * * *"
25046
+ }
25047
+ }
25048
+ },
25049
+ {
25050
+ "id": "list-clients",
25051
+ "type": "capability",
25052
+ "position": {
25053
+ "x": 0,
25054
+ "y": 120
25055
+ },
25056
+ "data": {
25057
+ "label": "List clients (pod-wide `client` role)",
25058
+ "verbId": "entity.query",
25059
+ "inputMapping": {
25060
+ "profileSlug": "client",
25061
+ "scope": "pod",
25062
+ "limit": "100"
25063
+ }
25064
+ }
25065
+ },
25066
+ {
25067
+ "id": "clients-loop",
25068
+ "type": "loop",
25069
+ "position": {
25070
+ "x": 0,
25071
+ "y": 240
25072
+ },
25073
+ "data": {
25074
+ "label": "For each client",
25075
+ "iteratorExpression": "steps.list-clients.output.entities",
25076
+ "itemVariable": "client"
25077
+ }
25078
+ },
25079
+ {
25080
+ "id": "read-comms",
25081
+ "type": "messages_query",
25082
+ "position": {
25083
+ "x": 0,
25084
+ "y": 360
25085
+ },
25086
+ "data": {
25087
+ "label": "Read this client's recent comms",
25088
+ "subjectEntityId": "{{loop.item.id}}",
25089
+ "limit": 40
25090
+ }
25091
+ },
25092
+ {
25093
+ "id": "digest",
25094
+ "type": "capability",
25095
+ "position": {
25096
+ "x": 0,
25097
+ "y": 480
25098
+ },
25099
+ "data": {
25100
+ "label": "Distil the real signal \u2192 JSON",
25101
+ "verbId": "ai.generate",
25102
+ "inputMapping": {
25103
+ "json": "true",
25104
+ "maxTokens": "1200",
25105
+ "system": "You advise a client-delivery team. You read one client's recent inbound messages and surface only what genuinely matters. You are firewall-aware: your output is for the INTERNAL team, never the client.",
25106
+ "prompt": `Digest the recent communications for the client "{{loop.item.title}}" into a concise internal briefing for OUR delivery team.
25107
+ PEOPLE GUARD (do not break): distinguish the CLIENT's people from OUR team members who also appear in the thread. Never treat a teammate as a client contact, lead, or new person. Only real, client-side signal counts.
25108
+ Surface ONLY real signal: status changes, decisions, deals/opportunities, competitor mentions, new client-side leads/contacts, risks/blockers, and deadlines/commitments. Skip small talk and noise. Lead with what matters; keep it tight.
25109
+ Respond with STRICT JSON and NOTHING else: {
25110
+ "hasSignal": "true" or "false" (a STRING),
25111
+ "headline": "<one-line summary; empty string when hasSignal is 'false'>",
25112
+ "summary": "<a short markdown digest for the team; empty string when 'false'>",
25113
+ "risks": "<risks/blockers as short markdown, or empty string>",
25114
+ "nextActions": "<recommended next actions as short markdown, or empty string>"
25115
+ } Set hasSignal to "false" when there is no notable activity (no messages, or only chatter).
25116
+ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
25117
+ }
25118
+ }
25119
+ },
25120
+ {
25121
+ "id": "signal-gate",
25122
+ "type": "condition",
25123
+ "position": {
25124
+ "x": 0,
25125
+ "y": 600
25126
+ },
25127
+ "data": {
25128
+ "label": "Only continue when there's real signal",
25129
+ "expression": "steps.digest.output.hasSignal === true"
25130
+ }
25131
+ },
25132
+ {
25133
+ "id": "write-briefing",
25134
+ "type": "output",
25135
+ "position": {
25136
+ "x": 0,
25137
+ "y": 720
25138
+ },
25139
+ "data": {
25140
+ "label": "Save the client briefing",
25141
+ "outputType": "entity_create",
25142
+ "config": {
25143
+ "profileSlug": "client-briefing",
25144
+ "title": "Briefing \u2014 {{loop.item.title}}",
25145
+ "dedupeBy": "briefing-key",
25146
+ "properties": {
25147
+ "briefing-key": "{{loop.item.id}}::{{trigger.payload.scheduledAt}}",
25148
+ "briefing-client": "{{loop.item.id}}",
25149
+ "briefing-date": "{{trigger.payload.scheduledAt}}",
25150
+ "briefing-headline": "{{steps.digest.output.headline}}",
25151
+ "briefing-summary": "{{steps.digest.output.summary}}",
25152
+ "briefing-risks": "{{steps.digest.output.risks}}",
25153
+ "briefing-next-actions": "{{steps.digest.output.nextActions}}",
25154
+ "briefing-message-count": "{{steps.read-comms.output.count}}"
25155
+ }
25156
+ }
25157
+ }
25158
+ },
25159
+ {
25160
+ "id": "link-briefing",
25161
+ "type": "output",
25162
+ "position": {
25163
+ "x": 0,
25164
+ "y": 840
25165
+ },
25166
+ "data": {
25167
+ "label": "Link briefing \u2192 client",
25168
+ "outputType": "relation_create",
25169
+ "config": {
25170
+ "fromEntityId": "{{steps.write-briefing.output.entityId}}",
25171
+ "toEntityId": "{{loop.item.id}}",
25172
+ "relationType": "briefing_about_client"
25173
+ }
25174
+ }
25175
+ },
25176
+ {
25177
+ "id": "resolve-team",
25178
+ "type": "capability",
25179
+ "position": {
25180
+ "x": 0,
25181
+ "y": 960
25182
+ },
25183
+ "data": {
25184
+ "label": "Resolve the client's TEAM channel (FIREWALL)",
25185
+ "verbId": "channel.resolve",
25186
+ "inputMapping": {
25187
+ "contextObjectType": "entity",
25188
+ "contextObjectId": "{{loop.item.id}}",
25189
+ "branchPurpose": "team"
25190
+ }
25191
+ }
25192
+ },
25193
+ {
25194
+ "id": "post-recap",
25195
+ "type": "output",
25196
+ "position": {
25197
+ "x": 0,
25198
+ "y": 1080
25199
+ },
25200
+ "data": {
25201
+ "label": "Post the recap to the TEAM channel (FIREWALL)",
25202
+ "outputType": "channel_message",
25203
+ "config": {
25204
+ "channelId": "{{steps.resolve-team.output.channelId}}",
25205
+ "content": "**{{loop.item.title}} \u2014 daily briefing**\n{{steps.digest.output.headline}}\n{{steps.digest.output.summary}}\n**Risks:** {{steps.digest.output.risks}}\n**Next:** {{steps.digest.output.nextActions}}",
25206
+ "metadata": {
25207
+ "automationMessage": true,
25208
+ "kind": "client-briefing"
25209
+ }
25210
+ }
25211
+ }
25212
+ }
25213
+ ],
25214
+ "edges": [
25215
+ {
25216
+ "id": "e-trigger-list",
25217
+ "source": "trigger",
25218
+ "target": "list-clients"
25219
+ },
25220
+ {
25221
+ "id": "e-list-loop",
25222
+ "source": "list-clients",
25223
+ "target": "clients-loop"
25224
+ },
25225
+ {
25226
+ "id": "e-loop-read",
25227
+ "source": "clients-loop",
25228
+ "target": "read-comms"
25229
+ },
25230
+ {
25231
+ "id": "e-read-digest",
25232
+ "source": "read-comms",
25233
+ "target": "digest"
25234
+ },
25235
+ {
25236
+ "id": "e-digest-gate",
25237
+ "source": "digest",
25238
+ "target": "signal-gate"
25239
+ },
25240
+ {
25241
+ "id": "e-gate-write",
25242
+ "source": "signal-gate",
25243
+ "target": "write-briefing"
25244
+ },
25245
+ {
25246
+ "id": "e-write-link",
25247
+ "source": "write-briefing",
25248
+ "target": "link-briefing"
25249
+ },
25250
+ {
25251
+ "id": "e-link-resolve",
25252
+ "source": "link-briefing",
25253
+ "target": "resolve-team"
25254
+ },
25255
+ {
25256
+ "id": "e-resolve-post",
25257
+ "source": "resolve-team",
25258
+ "target": "post-recap"
25259
+ }
25260
+ ]
25261
+ }
25262
+ },
24015
25263
  {
24016
- "key": "operations-daily-client-briefing",
24017
- "name": "Daily client briefing",
24018
- "description": "Operations-native daily client briefing. Writes a durable, dated client-briefing ENTITY per client (queryable history, linked to the client) from their recent comms, and posts the recap to the client's internal TEAM channel (never the client-facing channel). Knowledge-building and entity-first \u2014 the delivery OS's structured memory of each client. For a lighter, portable message-only digest installable on any pod, use the client-comms-report capability.",
25264
+ "key": "operations-crm-handoff-pickup",
25265
+ "name": "Pick up handed-off clients",
25266
+ "description": "Daily. Finds pod-wide clients the CRM marked ready or handed off that have no engagement yet, and opens the delivery shell for them \u2014 a proposed engagement plus a draft contract, both linked to the client. Deduped per client, so it is safe to re-run.",
24019
25267
  "triggerType": "cron",
24020
25268
  "triggerConfig": {
24021
- "expression": "0 8 * * *"
25269
+ "expression": "0 7 * * *"
24022
25270
  },
24023
25271
  "status": "active",
24024
25272
  "metadata": {
24025
- "templateKey": "operations-daily-client-briefing",
25273
+ "templateKey": "operations-crm-handoff-pickup",
24026
25274
  "requiresPodWideEntityQueryLens": true
24027
25275
  },
24028
25276
  "flow": {
@@ -24035,10 +25283,10 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
24035
25283
  "y": 0
24036
25284
  },
24037
25285
  "data": {
24038
- "label": "Daily at 08:00",
25286
+ "label": "Daily at 07:00",
24039
25287
  "triggerType": "cron",
24040
25288
  "config": {
24041
- "expression": "0 8 * * *"
25289
+ "expression": "0 7 * * *"
24042
25290
  }
24043
25291
  }
24044
25292
  },
@@ -24060,7 +25308,7 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
24060
25308
  }
24061
25309
  },
24062
25310
  {
24063
- "id": "clients-loop",
25311
+ "id": "handoff-loop",
24064
25312
  "type": "loop",
24065
25313
  "position": {
24066
25314
  "x": 0,
@@ -24073,185 +25321,223 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
24073
25321
  }
24074
25322
  },
24075
25323
  {
24076
- "id": "read-comms",
24077
- "type": "messages_query",
25324
+ "id": "read-client-facets",
25325
+ "type": "capability",
25326
+ "position": {
25327
+ "x": 0,
25328
+ "y": 360
25329
+ },
25330
+ "data": {
25331
+ "label": "Read this party's role facets",
25332
+ "verbId": "entity_facet.list",
25333
+ "inputMapping": {
25334
+ "entityId": "{{loop.item.id}}"
25335
+ }
25336
+ }
25337
+ },
25338
+ {
25339
+ "id": "client-handoff-status",
25340
+ "type": "transform",
25341
+ "position": {
25342
+ "x": 0,
25343
+ "y": 480
25344
+ },
25345
+ "data": {
25346
+ "label": "Read the client facet's handoff status",
25347
+ "expression": "{{steps.read-client-facets.output.facets}} | filter:item.profileSlug === client | map:{{item.properties.handoffStatus}}"
25348
+ }
25349
+ },
25350
+ {
25351
+ "id": "handoff-gate",
25352
+ "type": "condition",
25353
+ "position": {
25354
+ "x": 0,
25355
+ "y": 600
25356
+ },
25357
+ "data": {
25358
+ "label": "Only clients the CRM marked ready or handed off",
25359
+ "expression": "steps.client-handoff-status.output.result contains-any ready,handed_off"
25360
+ }
25361
+ },
25362
+ {
25363
+ "id": "existing-engagements",
25364
+ "type": "query",
24078
25365
  "position": {
24079
25366
  "x": 0,
24080
- "y": 360
25367
+ "y": 720
24081
25368
  },
24082
25369
  "data": {
24083
- "label": "Read this client's recent comms",
24084
- "subjectEntityId": "{{loop.item.id}}",
24085
- "limit": 40
25370
+ "label": "Does this client already have an engagement?",
25371
+ "profileSlug": "engagement",
25372
+ "filter": '{"engagement-client": "{{loop.item.id}}"}',
25373
+ "limit": 1
24086
25374
  }
24087
25375
  },
24088
25376
  {
24089
- "id": "digest",
24090
- "type": "capability",
25377
+ "id": "engagement-gate",
25378
+ "type": "condition",
24091
25379
  "position": {
24092
25380
  "x": 0,
24093
- "y": 480
25381
+ "y": 840
24094
25382
  },
24095
25383
  "data": {
24096
- "label": "Distil the real signal \u2192 JSON",
24097
- "verbId": "ai.generate",
24098
- "inputMapping": {
24099
- "json": "true",
24100
- "maxTokens": "1200",
24101
- "system": "You advise a client-delivery team. You read one client's recent inbound messages and surface only what genuinely matters. You are firewall-aware: your output is for the INTERNAL team, never the client.",
24102
- "prompt": `Digest the recent communications for the client "{{loop.item.title}}" into a concise internal briefing for OUR delivery team.
24103
- PEOPLE GUARD (do not break): distinguish the CLIENT's people from OUR team members who also appear in the thread. Never treat a teammate as a client contact, lead, or new person. Only real, client-side signal counts.
24104
- Surface ONLY real signal: status changes, decisions, deals/opportunities, competitor mentions, new client-side leads/contacts, risks/blockers, and deadlines/commitments. Skip small talk and noise. Lead with what matters; keep it tight.
24105
- Respond with STRICT JSON and NOTHING else: {
24106
- "hasSignal": "true" or "false" (a STRING),
24107
- "headline": "<one-line summary; empty string when hasSignal is 'false'>",
24108
- "summary": "<a short markdown digest for the team; empty string when 'false'>",
24109
- "risks": "<risks/blockers as short markdown, or empty string>",
24110
- "nextActions": "<recommended next actions as short markdown, or empty string>"
24111
- } Set hasSignal to "false" when there is no notable activity (no messages, or only chatter).
24112
- Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
24113
- }
25384
+ "label": "Only clients with no engagement yet",
25385
+ "expression": "steps.existing-engagements.output.count === 0"
24114
25386
  }
24115
25387
  },
24116
25388
  {
24117
- "id": "signal-gate",
24118
- "type": "condition",
25389
+ "id": "handoff-time",
25390
+ "type": "compute",
24119
25391
  "position": {
24120
25392
  "x": 0,
24121
- "y": 600
25393
+ "y": 900
24122
25394
  },
24123
25395
  "data": {
24124
- "label": "Only continue when there's real signal",
24125
- "expression": "steps.digest.output.hasSignal === true"
25396
+ "label": "Record handoff time",
25397
+ "operation": "now"
24126
25398
  }
24127
25399
  },
24128
25400
  {
24129
- "id": "write-briefing",
25401
+ "id": "create-engagement",
24130
25402
  "type": "output",
24131
25403
  "position": {
24132
25404
  "x": 0,
24133
- "y": 720
25405
+ "y": 960
24134
25406
  },
24135
25407
  "data": {
24136
- "label": "Save the client briefing",
25408
+ "label": "Open a proposed engagement",
24137
25409
  "outputType": "entity_create",
24138
25410
  "config": {
24139
- "profileSlug": "client-briefing",
24140
- "title": "Briefing \u2014 {{loop.item.title}}",
24141
- "dedupeBy": "briefing-key",
25411
+ "profileSlug": "engagement",
25412
+ "title": "Engagement \u2014 {{loop.item.title}}",
25413
+ "dedupeBy": "engagement-client",
24142
25414
  "properties": {
24143
- "briefing-key": "{{loop.item.id}}::{{trigger.payload.scheduledAt}}",
24144
- "briefing-client": "{{loop.item.id}}",
24145
- "briefing-date": "{{trigger.payload.scheduledAt}}",
24146
- "briefing-headline": "{{steps.digest.output.headline}}",
24147
- "briefing-summary": "{{steps.digest.output.summary}}",
24148
- "briefing-risks": "{{steps.digest.output.risks}}",
24149
- "briefing-next-actions": "{{steps.digest.output.nextActions}}",
24150
- "briefing-message-count": "{{steps.read-comms.output.count}}"
25415
+ "engagement-status": "proposed",
25416
+ "engagement-client": "{{loop.item.id}}",
25417
+ "engagement-start": "{{steps.handoff-time.output.result}}",
25418
+ "engagement-notes": "Opened automatically from the CRM handoff. Confirm scope, dates, and owner before activating."
24151
25419
  }
24152
25420
  }
24153
25421
  }
24154
25422
  },
24155
25423
  {
24156
- "id": "link-briefing",
25424
+ "id": "link-engagement",
24157
25425
  "type": "output",
24158
25426
  "position": {
24159
25427
  "x": 0,
24160
- "y": 840
25428
+ "y": 1080
24161
25429
  },
24162
25430
  "data": {
24163
- "label": "Link briefing \u2192 client",
25431
+ "label": "Link engagement \u2192 client",
24164
25432
  "outputType": "relation_create",
24165
25433
  "config": {
24166
- "fromEntityId": "{{steps.write-briefing.output.entityId}}",
25434
+ "fromEntityId": "{{steps.create-engagement.output.entityId}}",
24167
25435
  "toEntityId": "{{loop.item.id}}",
24168
- "relationType": "briefing_about_client"
25436
+ "relationType": "engagement_for_client"
24169
25437
  }
24170
25438
  }
24171
25439
  },
24172
25440
  {
24173
- "id": "resolve-team",
24174
- "type": "capability",
25441
+ "id": "create-contract",
25442
+ "type": "output",
24175
25443
  "position": {
24176
25444
  "x": 0,
24177
- "y": 960
25445
+ "y": 1200
24178
25446
  },
24179
25447
  "data": {
24180
- "label": "Resolve the client's TEAM channel (FIREWALL)",
24181
- "verbId": "channel.resolve",
24182
- "inputMapping": {
24183
- "contextObjectType": "entity",
24184
- "contextObjectId": "{{loop.item.id}}",
24185
- "branchPurpose": "team"
25448
+ "label": "Open a draft contract",
25449
+ "outputType": "entity_create",
25450
+ "config": {
25451
+ "profileSlug": "contract",
25452
+ "title": "Contract \u2014 {{loop.item.title}}",
25453
+ "dedupeBy": "contract-engagement",
25454
+ "properties": {
25455
+ "contract-status": "draft",
25456
+ "contract-engagement": "{{steps.create-engagement.output.entityId}}",
25457
+ "start-date": "{{steps.handoff-time.output.result}}",
25458
+ "contract-notes": "Drafted automatically from the CRM handoff. Attach the signed document and set the term."
25459
+ }
24186
25460
  }
24187
25461
  }
24188
25462
  },
24189
25463
  {
24190
- "id": "post-recap",
25464
+ "id": "link-contract",
24191
25465
  "type": "output",
24192
25466
  "position": {
24193
25467
  "x": 0,
24194
- "y": 1080
25468
+ "y": 1320
24195
25469
  },
24196
25470
  "data": {
24197
- "label": "Post the recap to the TEAM channel (FIREWALL)",
24198
- "outputType": "channel_message",
25471
+ "label": "Link contract \u2192 engagement",
25472
+ "outputType": "relation_create",
24199
25473
  "config": {
24200
- "channelId": "{{steps.resolve-team.output.channelId}}",
24201
- "content": "**{{loop.item.title}} \u2014 daily briefing**\n{{steps.digest.output.headline}}\n{{steps.digest.output.summary}}\n**Risks:** {{steps.digest.output.risks}}\n**Next:** {{steps.digest.output.nextActions}}",
24202
- "metadata": {
24203
- "automationMessage": true,
24204
- "kind": "client-briefing"
24205
- }
25474
+ "fromEntityId": "{{steps.create-contract.output.entityId}}",
25475
+ "toEntityId": "{{steps.create-engagement.output.entityId}}",
25476
+ "relationType": "contract_for_engagement"
24206
25477
  }
24207
25478
  }
24208
25479
  }
24209
25480
  ],
24210
25481
  "edges": [
24211
25482
  {
24212
- "id": "e-trigger-list",
25483
+ "id": "h-trigger-list",
24213
25484
  "source": "trigger",
24214
25485
  "target": "list-clients"
24215
25486
  },
24216
25487
  {
24217
- "id": "e-list-loop",
25488
+ "id": "h-list-time",
24218
25489
  "source": "list-clients",
24219
- "target": "clients-loop"
25490
+ "target": "handoff-time"
24220
25491
  },
24221
25492
  {
24222
- "id": "e-loop-read",
24223
- "source": "clients-loop",
24224
- "target": "read-comms"
25493
+ "id": "h-time-loop",
25494
+ "source": "handoff-time",
25495
+ "target": "handoff-loop"
24225
25496
  },
24226
25497
  {
24227
- "id": "e-read-digest",
24228
- "source": "read-comms",
24229
- "target": "digest"
25498
+ "id": "h-loop-facets",
25499
+ "source": "handoff-loop",
25500
+ "target": "read-client-facets"
24230
25501
  },
24231
25502
  {
24232
- "id": "e-digest-gate",
24233
- "source": "digest",
24234
- "target": "signal-gate"
25503
+ "id": "h-facets-status",
25504
+ "source": "read-client-facets",
25505
+ "target": "client-handoff-status"
24235
25506
  },
24236
25507
  {
24237
- "id": "e-gate-write",
24238
- "source": "signal-gate",
24239
- "target": "write-briefing"
25508
+ "id": "h-status-gate",
25509
+ "source": "client-handoff-status",
25510
+ "target": "handoff-gate"
24240
25511
  },
24241
25512
  {
24242
- "id": "e-write-link",
24243
- "source": "write-briefing",
24244
- "target": "link-briefing"
25513
+ "id": "h-gate-existing",
25514
+ "source": "handoff-gate",
25515
+ "target": "existing-engagements"
24245
25516
  },
24246
25517
  {
24247
- "id": "e-link-resolve",
24248
- "source": "link-briefing",
24249
- "target": "resolve-team"
25518
+ "id": "h-existing-gate",
25519
+ "source": "existing-engagements",
25520
+ "target": "engagement-gate"
24250
25521
  },
24251
25522
  {
24252
- "id": "e-resolve-post",
24253
- "source": "resolve-team",
24254
- "target": "post-recap"
25523
+ "id": "h-gate-engagement",
25524
+ "source": "engagement-gate",
25525
+ "target": "create-engagement"
25526
+ },
25527
+ {
25528
+ "id": "h-engagement-link",
25529
+ "source": "create-engagement",
25530
+ "target": "link-engagement"
25531
+ },
25532
+ {
25533
+ "id": "h-link-contract",
25534
+ "source": "link-engagement",
25535
+ "target": "create-contract"
25536
+ },
25537
+ {
25538
+ "id": "h-contract-link",
25539
+ "source": "create-contract",
25540
+ "target": "link-contract"
24255
25541
  }
24256
25542
  ]
24257
25543
  }
@@ -24565,6 +25851,164 @@ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
24565
25851
  "status": "active"
24566
25852
  }
24567
25853
  ],
25854
+ "automations": [
25855
+ {
25856
+ "key": "linkedin-conversation-ingest",
25857
+ "name": "LinkedIn conversation ingest",
25858
+ "description": "Pull LinkedIn conversations via Unipile and record each thread as an external channel + its messages through channel.ingest (idempotent, so re-runs are safe). Cron = low-frequency reconciliation; manual trigger = on-demand backfill.",
25859
+ "triggerType": "cron",
25860
+ "triggerConfig": {
25861
+ "expression": "0 6 * * *"
25862
+ },
25863
+ "status": "draft",
25864
+ "flow": {
25865
+ "nodes": [
25866
+ {
25867
+ "id": "trigger",
25868
+ "type": "trigger",
25869
+ "position": {
25870
+ "x": 0,
25871
+ "y": 0
25872
+ },
25873
+ "data": {
25874
+ "label": "Daily 06:00 reconciliation (also fired manually for backfill)",
25875
+ "triggerType": "cron",
25876
+ "config": {
25877
+ "expression": "0 6 * * *"
25878
+ }
25879
+ }
25880
+ },
25881
+ {
25882
+ "id": "list-accounts",
25883
+ "type": "capability",
25884
+ "position": {
25885
+ "x": 0,
25886
+ "y": 120
25887
+ },
25888
+ "data": {
25889
+ "label": "Resolve the connected Unipile account",
25890
+ "verbId": "unipile_list_accounts"
25891
+ }
25892
+ },
25893
+ {
25894
+ "id": "filter-linkedin-account",
25895
+ "type": "transform",
25896
+ "position": {
25897
+ "x": 0,
25898
+ "y": 180
25899
+ },
25900
+ "data": {
25901
+ "label": "Keep only LinkedIn Unipile accounts",
25902
+ "expression": "{{steps.list-accounts.output.accounts}} | filter:item.type == 'LINKEDIN'"
25903
+ }
25904
+ },
25905
+ {
25906
+ "id": "list-conversations",
25907
+ "type": "capability",
25908
+ "position": {
25909
+ "x": 0,
25910
+ "y": 240
25911
+ },
25912
+ "data": {
25913
+ "label": "List LinkedIn conversations for the account",
25914
+ "verbId": "unipile_list_conversations",
25915
+ "inputMapping": {
25916
+ "accountId": "{{steps.filter-linkedin-account.output.result.0.id}}"
25917
+ }
25918
+ }
25919
+ },
25920
+ {
25921
+ "id": "conversations-loop",
25922
+ "type": "loop",
25923
+ "position": {
25924
+ "x": 0,
25925
+ "y": 360
25926
+ },
25927
+ "data": {
25928
+ "label": "For each conversation",
25929
+ "iteratorExpression": "steps.list-conversations.output.conversations",
25930
+ "itemVariable": "conversation"
25931
+ }
25932
+ },
25933
+ {
25934
+ "id": "list-messages",
25935
+ "type": "capability",
25936
+ "position": {
25937
+ "x": 0,
25938
+ "y": 480
25939
+ },
25940
+ "data": {
25941
+ "label": "List this thread's messages",
25942
+ "verbId": "unipile_list_messages",
25943
+ "inputMapping": {
25944
+ "threadId": "{{loop.item.id}}",
25945
+ "accountId": "{{steps.filter-linkedin-account.output.result.0.id}}"
25946
+ }
25947
+ }
25948
+ },
25949
+ {
25950
+ "id": "ingest",
25951
+ "type": "capability",
25952
+ "position": {
25953
+ "x": 0,
25954
+ "y": 600
25955
+ },
25956
+ "data": {
25957
+ "label": "Ingest thread + messages (idempotent, generic)",
25958
+ "verbId": "channel.ingest",
25959
+ "inputMapping": {
25960
+ "provider": "linkedin",
25961
+ "externalId": "{{loop.item.id}}",
25962
+ "title": "{{loop.item.name}}",
25963
+ "accountExternalId": "{{steps.filter-linkedin-account.output.result.0.id}}",
25964
+ "workspaceId": "499328b7-fd35-4143-b1bc-67b9e45d9b4b",
25965
+ "suppressSideEffects": true,
25966
+ "messages": "{{steps.list-messages.output.messages}}",
25967
+ "messageMap": {
25968
+ "text": "text",
25969
+ "id": "id",
25970
+ "sentAt": "timestamp",
25971
+ "isOutbound": "is_sender"
25972
+ }
25973
+ }
25974
+ }
25975
+ }
25976
+ ],
25977
+ "edges": [
25978
+ {
25979
+ "id": "e-trigger-accounts",
25980
+ "source": "trigger",
25981
+ "target": "list-accounts"
25982
+ },
25983
+ {
25984
+ "id": "e-accounts-filter",
25985
+ "source": "list-accounts",
25986
+ "target": "filter-linkedin-account"
25987
+ },
25988
+ {
25989
+ "id": "e-filter-convos",
25990
+ "source": "filter-linkedin-account",
25991
+ "target": "list-conversations"
25992
+ },
25993
+ {
25994
+ "id": "e-convos-loop",
25995
+ "source": "list-conversations",
25996
+ "target": "conversations-loop"
25997
+ },
25998
+ {
25999
+ "id": "e-loop-messages",
26000
+ "source": "conversations-loop",
26001
+ "target": "list-messages"
26002
+ },
26003
+ {
26004
+ "id": "e-messages-ingest",
26005
+ "source": "list-messages",
26006
+ "target": "ingest"
26007
+ }
26008
+ ]
26009
+ }
26010
+ }
26011
+ ],
24568
26012
  "integrations": [
24569
26013
  {
24570
26014
  "templateKey": "unipile-linkedin"
@@ -25130,7 +26574,7 @@ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
25130
26574
  "icon": "user",
25131
26575
  "color": "#0891B2",
25132
26576
  "description": "Team member or stakeholder",
25133
- "scope": "WORKSPACE",
26577
+ "entityScope": "pod",
25134
26578
  "properties": [
25135
26579
  {
25136
26580
  "slug": "person-role",
@@ -28889,17 +30333,7 @@ function layerTemplateGraph(input) {
28889
30333
  }
28890
30334
 
28891
30335
  // src/concept-guard.ts
28892
- var DECLARED_DIVERGENCES = {
28893
- // Two-tier lead vocabulary, documented at business-developer.yaml:1-16.
28894
- // Foundation's `lead` is a SHARED pod-wide role: being a lead is true
28895
- // everywhere. `crm-lead` is a WORKSPACE-scoped role so a person can be a lead
28896
- // inside one CRM workspace without becoming a lead across the whole pod. The
28897
- // tiers are intentionally separate rows; `semanticSlug: crm.lead` records the
28898
- // kinship so the two are discoverable as one concept via getBySemanticSlug.
28899
- "crm-lead|lead": "business-developer: workspace-tier lead, deliberately NOT pod-wide foundation `lead` (two-tier CRM design)",
28900
- // Same reasoning for the post-sale side of the same lifecycle.
28901
- "client|crm-client": "business-developer: workspace-tier client, deliberately NOT pod-wide foundation `client` (two-tier CRM design)"
28902
- };
30336
+ var DECLARED_DIVERGENCES = {};
28903
30337
  function effectiveSemanticSlug(profile) {
28904
30338
  return profile.semanticSlug ?? profile.slug;
28905
30339
  }
@@ -28918,7 +30352,7 @@ function declaredValue(profile, field) {
28918
30352
  function pairKey(a, b) {
28919
30353
  return [a, b].sort().join("|");
28920
30354
  }
28921
- function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
30355
+ function findConceptViolations(templates = WORKSPACE_TEMPLATES, declaredDivergences = DECLARED_DIVERGENCES) {
28922
30356
  const violations = [];
28923
30357
  const all = [];
28924
30358
  for (const [templateSlug, template] of Object.entries(templates)) {
@@ -28938,7 +30372,7 @@ function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
28938
30372
  const term = conceptTerm(effectiveSemanticSlug(a.profile));
28939
30373
  if (term !== conceptTerm(effectiveSemanticSlug(b.profile))) continue;
28940
30374
  const key = pairKey(a.profile.slug, b.profile.slug);
28941
- if (key in DECLARED_DIVERGENCES) {
30375
+ if (key in declaredDivergences) {
28942
30376
  usedAllowlistKeys.add(key);
28943
30377
  continue;
28944
30378
  }
@@ -28977,7 +30411,7 @@ function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
28977
30411
  }
28978
30412
  }
28979
30413
  }
28980
- for (const key of Object.keys(DECLARED_DIVERGENCES)) {
30414
+ for (const key of Object.keys(declaredDivergences)) {
28981
30415
  if (usedAllowlistKeys.has(key)) continue;
28982
30416
  violations.push({
28983
30417
  rule: "stale-allowlist",
@@ -29057,6 +30491,8 @@ var FLOW_NODE_TYPES = [
29057
30491
  "select",
29058
30492
  "claim",
29059
30493
  "messages_query",
30494
+ "runs_query",
30495
+ "proposals_query",
29060
30496
  "switch",
29061
30497
  "skill",
29062
30498
  "capability",
@@ -29081,7 +30517,24 @@ var CHANNEL_MESSAGE_CHANNEL_TYPES = [
29081
30517
  "proactive",
29082
30518
  "subjectEntity"
29083
30519
  ];
30520
+ var LOOP_BODY_NODE_TYPES = [
30521
+ "command",
30522
+ "output",
30523
+ "playbook_run",
30524
+ "messages_query",
30525
+ "runs_query",
30526
+ "proposals_query",
30527
+ "query",
30528
+ "fetch",
30529
+ "transform",
30530
+ "condition",
30531
+ "skill",
30532
+ "capability"
30533
+ ];
30534
+ var LOOP_PROVIDED_BINDINGS = ["item", "index"];
29084
30535
  var NODE_TYPE_SET = new Set(FLOW_NODE_TYPES);
30536
+ var LOOP_BODY_TYPE_SET = new Set(LOOP_BODY_NODE_TYPES);
30537
+ var LOOP_BINDING_SET = new Set(LOOP_PROVIDED_BINDINGS);
29085
30538
  var OUTPUT_TYPE_SET = new Set(FLOW_OUTPUT_TYPES);
29086
30539
  var CHANNEL_TYPE_SET = new Set(CHANNEL_MESSAGE_CHANNEL_TYPES);
29087
30540
  function isRecord(v) {
@@ -29323,6 +30776,40 @@ function validateFlowDefinition(flow, resolvers) {
29323
30776
  }
29324
30777
  }
29325
30778
  }
30779
+ const loopBodyNodeIds = /* @__PURE__ */ new Set();
30780
+ for (const node of nodes) {
30781
+ if (!nonEmptyString(node.id) || node.type !== "loop") continue;
30782
+ for (const id of collectLoopBody(node.id, nodesById, edges)) {
30783
+ loopBodyNodeIds.add(id);
30784
+ }
30785
+ }
30786
+ for (const node of nodes) {
30787
+ if (!nonEmptyString(node.id)) continue;
30788
+ const refs = collectLoopBindingRefs(node.data);
30789
+ if (refs.length === 0) continue;
30790
+ if (!loopBodyNodeIds.has(node.id)) {
30791
+ errors.push({
30792
+ nodeId: node.id,
30793
+ code: "loop_ref_outside_loop_body",
30794
+ message: `Node "${node.id}" references ${refs.map((r) => `\`{{loop.${r}...}}\``).join(
30795
+ ", "
30796
+ )} but is not inside a loop body, so there is no item to bind \u2014 it would run once with an empty value. Connect it downstream of a loop node with no switch/delay/nested-loop/sub_automation node in between.`
30797
+ });
30798
+ continue;
30799
+ }
30800
+ const unknown = refs.filter((r) => !LOOP_BINDING_SET.has(r));
30801
+ if (unknown.length > 0) {
30802
+ errors.push({
30803
+ nodeId: node.id,
30804
+ code: "loop_ref_unknown_path",
30805
+ message: `Node "${node.id}" references ${unknown.map((r) => `\`{{loop.${r}}}\``).join(
30806
+ ", "
30807
+ )}, which a loop does not provide. A loop binds only: ${LOOP_PROVIDED_BINDINGS.map(
30808
+ (b) => `\`loop.${b}\``
30809
+ ).join(", ")}.`
30810
+ });
30811
+ }
30812
+ }
29326
30813
  const cyclic = detectCycle(nodeIds, edges);
29327
30814
  if (cyclic.length > 0) {
29328
30815
  errors.push({
@@ -29334,6 +30821,53 @@ function validateFlowDefinition(flow, resolvers) {
29334
30821
  }
29335
30822
  return { valid: errors.length === 0, errors };
29336
30823
  }
30824
+ function collectLoopBody(loopNodeId, nodesById, edges) {
30825
+ const body = /* @__PURE__ */ new Set();
30826
+ const stack = [];
30827
+ for (const edge of edges) {
30828
+ if (edge.source === loopNodeId && nonEmptyString(edge.target))
30829
+ stack.push(edge.target);
30830
+ }
30831
+ while (stack.length > 0) {
30832
+ const id = stack.pop();
30833
+ if (body.has(id)) continue;
30834
+ const node = nodesById.get(id);
30835
+ if (!node || !nonEmptyString(node.type) || !LOOP_BODY_TYPE_SET.has(node.type))
30836
+ continue;
30837
+ body.add(id);
30838
+ for (const edge of edges) {
30839
+ if (edge.source === id && nonEmptyString(edge.target))
30840
+ stack.push(edge.target);
30841
+ }
30842
+ }
30843
+ return body;
30844
+ }
30845
+ function collectLoopBindingRefs(data) {
30846
+ const found = /* @__PURE__ */ new Set();
30847
+ const walk = (v) => {
30848
+ if (typeof v === "string") {
30849
+ const head = v.split("|")[0];
30850
+ const re = /\{\{\s*loop\.([A-Za-z0-9_$]+)/g;
30851
+ let m;
30852
+ let matched = false;
30853
+ while ((m = re.exec(head)) !== null) {
30854
+ found.add(m[1]);
30855
+ matched = true;
30856
+ }
30857
+ if (!matched && /\{\{\s*loop\./.test(head)) found.add("");
30858
+ return;
30859
+ }
30860
+ if (Array.isArray(v)) {
30861
+ for (const x of v) walk(x);
30862
+ return;
30863
+ }
30864
+ if (isRecord(v)) {
30865
+ for (const x of Object.values(v)) walk(x);
30866
+ }
30867
+ };
30868
+ walk(data);
30869
+ return [...found];
30870
+ }
29337
30871
  function detectCycle(nodeIds, edges) {
29338
30872
  const inDegree = /* @__PURE__ */ new Map();
29339
30873
  const adjacency = /* @__PURE__ */ new Map();
@@ -29404,7 +30938,12 @@ var SYSTEM_PROFILES = /* @__PURE__ */ new Set([
29404
30938
  "question",
29405
30939
  "research",
29406
30940
  "knowledge",
29407
- "user_observation"
30941
+ "user_observation",
30942
+ // Generated artifact — a workspace-scoped pod system profile
30943
+ // (`ensure-system-profiles.ts`) written by the `base` report automation. It
30944
+ // was simply missing from this hand-maintained list; the flow's
30945
+ // `create-report` node references it, so referential integrity needs it here.
30946
+ "report"
29408
30947
  ]);
29409
30948
  function isPodWideProfile(p) {
29410
30949
  return String(p.scope || "").toLowerCase() === "shared";
@@ -29474,8 +31013,13 @@ function validateTemplate(tpl, opts = {}) {
29474
31013
  );
29475
31014
  }
29476
31015
  }
29477
- if (!tpl.profiles || tpl.profiles.length === 0) {
29478
- add("profiles-required", "profiles", "At least one profile is required");
31016
+ const carriesOperationalDefaults = tpl.relationDefs && tpl.relationDefs.length > 0 || tpl.commands && tpl.commands.length > 0 || tpl.automations && tpl.automations.length > 0;
31017
+ if ((!tpl.profiles || tpl.profiles.length === 0) && !carriesOperationalDefaults) {
31018
+ add(
31019
+ "profiles-required",
31020
+ "profiles",
31021
+ "At least one profile is required (or operational defaults: relationDefs / commands / automations)"
31022
+ );
29479
31023
  }
29480
31024
  if (tpl.app && tpl.layout && Object.prototype.hasOwnProperty.call(tpl.layout, "primarySurface")) {
29481
31025
  add(
@@ -30038,6 +31582,11 @@ function toWorkspaceDefinition(slugOrYaml, nameOverride) {
30038
31582
  seedEntities: tpl.seedEntities ?? [],
30039
31583
  suggestedRelations: tpl.suggestedRelations,
30040
31584
  displayTemplates: tpl.displayTemplates,
31585
+ // Relation-type vocabulary + intelligence commands — carried verbatim so the
31586
+ // one reconcile door can seed the universal defaults (the `base` template)
31587
+ // that three hardcoded seeders used to. Authored form IS the wire form.
31588
+ relationDefs: tpl.relationDefs,
31589
+ commands: tpl.commands,
30041
31590
  layoutConfig: normalizeTemplateLayout(tpl.layout, tpl.app),
30042
31591
  bentoViewName: tpl.bento?.viewName,
30043
31592
  bentoLayout: bento.bentoLayout,
@@ -30108,6 +31657,8 @@ function toPackageDefinition(slugOrYaml) {
30108
31657
  suggestedRelations: tpl.suggestedRelations,
30109
31658
  displayTemplates: tpl.displayTemplates,
30110
31659
  entityLinks: tpl.entityLinks,
31660
+ relationDefs: tpl.relationDefs,
31661
+ commands: tpl.commands,
30111
31662
  bentoLayout: bento.bentoLayout,
30112
31663
  bentoViewBlocks: bento.bentoViewBlocks,
30113
31664
  bentoViewName: tpl.bento?.viewName,