@synap-core/workspace-templates 0.9.4 → 0.11.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/src/templates.ts CHANGED
@@ -596,7 +596,7 @@ const DATA = {
596
596
  "displayName": "Person",
597
597
  "icon": "user",
598
598
  "color": "#0891B2",
599
- "scope": "WORKSPACE",
599
+ "entityScope": "pod",
600
600
  "description": "Team member or agent owner",
601
601
  "properties": [
602
602
  {
@@ -2734,6 +2734,695 @@ const DATA = {
2734
2734
  "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."
2735
2735
  }
2736
2736
  },
2737
+ "base": {
2738
+ "meta": {
2739
+ "slug": "base",
2740
+ "domain": "core",
2741
+ "name": "Base",
2742
+ "description": "Universal operational defaults every workspace inherits — the report automation, the default relation vocabulary, and the default command pack.",
2743
+ "icon": "layers",
2744
+ "color": "#6B7280",
2745
+ "tags": [
2746
+ "core",
2747
+ "defaults",
2748
+ "operational"
2749
+ ],
2750
+ "isPublic": true
2751
+ },
2752
+ "workspace": {
2753
+ "name": "Base",
2754
+ "description": "The pod-wide operational defaults — carried as config so one reconcile door seeds them into every workspace instead of three hardcoded seeders.",
2755
+ "proposalId": "base-v1",
2756
+ "visibility": "pod_visible"
2757
+ },
2758
+ "profiles": [],
2759
+ "relationDefs": [
2760
+ {
2761
+ "slug": "assigned_to",
2762
+ "displayName": "Assigned To",
2763
+ "description": "Person assigned to task/project",
2764
+ "isDirectional": true,
2765
+ "uiHints": {
2766
+ "category": "workflow"
2767
+ }
2768
+ },
2769
+ {
2770
+ "slug": "blocks",
2771
+ "displayName": "Blocks",
2772
+ "description": "Prevents progress on another task",
2773
+ "isDirectional": true,
2774
+ "uiHints": {
2775
+ "category": "workflow"
2776
+ }
2777
+ },
2778
+ {
2779
+ "slug": "depends_on",
2780
+ "displayName": "Depends On",
2781
+ "description": "Requires completion of another task",
2782
+ "isDirectional": true,
2783
+ "uiHints": {
2784
+ "category": "workflow"
2785
+ }
2786
+ },
2787
+ {
2788
+ "slug": "relates_to",
2789
+ "displayName": "Relates To",
2790
+ "description": "General relationship between entities",
2791
+ "isDirectional": false,
2792
+ "uiHints": {
2793
+ "category": "reference"
2794
+ }
2795
+ },
2796
+ {
2797
+ "slug": "mentions",
2798
+ "displayName": "Mentions",
2799
+ "description": "Referenced in content",
2800
+ "isDirectional": true,
2801
+ "uiHints": {
2802
+ "category": "reference"
2803
+ }
2804
+ },
2805
+ {
2806
+ "slug": "links_to",
2807
+ "displayName": "Links To",
2808
+ "description": "Hyperlink or reference",
2809
+ "isDirectional": true,
2810
+ "uiHints": {
2811
+ "category": "reference"
2812
+ }
2813
+ },
2814
+ {
2815
+ "slug": "parent_of",
2816
+ "displayName": "Parent Of",
2817
+ "description": "Hierarchical parent relationship",
2818
+ "isDirectional": true,
2819
+ "uiHints": {
2820
+ "category": "hierarchy"
2821
+ }
2822
+ },
2823
+ {
2824
+ "slug": "tagged_with",
2825
+ "displayName": "Tagged With",
2826
+ "description": "Categorization tag",
2827
+ "isDirectional": true,
2828
+ "uiHints": {
2829
+ "category": "reference"
2830
+ }
2831
+ },
2832
+ {
2833
+ "slug": "created_by",
2834
+ "displayName": "Created By",
2835
+ "description": "Author or creator",
2836
+ "isDirectional": true,
2837
+ "uiHints": {
2838
+ "category": "social"
2839
+ }
2840
+ },
2841
+ {
2842
+ "slug": "attended_by",
2843
+ "displayName": "Attended By",
2844
+ "description": "Participant in event",
2845
+ "isDirectional": true,
2846
+ "uiHints": {
2847
+ "category": "social"
2848
+ }
2849
+ },
2850
+ {
2851
+ "slug": "belongs_to_project",
2852
+ "displayName": "Belongs To Project",
2853
+ "description": "Project membership",
2854
+ "isDirectional": true,
2855
+ "uiHints": {
2856
+ "category": "hierarchy"
2857
+ }
2858
+ },
2859
+ {
2860
+ "slug": "founder_brand_of",
2861
+ "displayName": "Founder Brand Of",
2862
+ "description": "A personal-brand project is the founder brand of a company project — links a person's brand to the company it expresses",
2863
+ "isDirectional": true,
2864
+ "uiHints": {
2865
+ "category": "hierarchy",
2866
+ "inverseLabel": "Expression of"
2867
+ }
2868
+ },
2869
+ {
2870
+ "slug": "references",
2871
+ "displayName": "References",
2872
+ "description": "Cites or refers to",
2873
+ "isDirectional": true,
2874
+ "uiHints": {
2875
+ "category": "reference"
2876
+ }
2877
+ },
2878
+ {
2879
+ "slug": "works_at",
2880
+ "displayName": "Works At",
2881
+ "description": "Person works at an organization",
2882
+ "isDirectional": true,
2883
+ "uiHints": {
2884
+ "category": "social",
2885
+ "inverseLabel": "Employs"
2886
+ }
2887
+ },
2888
+ {
2889
+ "slug": "deal_for",
2890
+ "displayName": "Deal For",
2891
+ "description": "Sales deal associated with a contact",
2892
+ "isDirectional": true,
2893
+ "uiHints": {
2894
+ "category": "workflow",
2895
+ "inverseLabel": "Has Deal"
2896
+ }
2897
+ },
2898
+ {
2899
+ "slug": "advances",
2900
+ "displayName": "Advances",
2901
+ "description": "advances the company's Foundation mission/vision goal",
2902
+ "isDirectional": true,
2903
+ "uiHints": {
2904
+ "category": "workflow",
2905
+ "inverseLabel": "Advanced by"
2906
+ }
2907
+ },
2908
+ {
2909
+ "slug": "met_at",
2910
+ "displayName": "Met at",
2911
+ "description": "Person met at an event or gathering",
2912
+ "isDirectional": true,
2913
+ "uiHints": {
2914
+ "category": "social",
2915
+ "inverseLabel": "Attendees"
2916
+ }
2917
+ },
2918
+ {
2919
+ "slug": "works_on",
2920
+ "displayName": "Works on",
2921
+ "description": "Person contributes to a project",
2922
+ "isDirectional": true,
2923
+ "uiHints": {
2924
+ "category": "workflow",
2925
+ "inverseLabel": "Contributors"
2926
+ }
2927
+ },
2928
+ {
2929
+ "slug": "has_skill",
2930
+ "displayName": "Has skill",
2931
+ "description": "Person possesses a skill or expertise",
2932
+ "isDirectional": true,
2933
+ "uiHints": {
2934
+ "category": "reference",
2935
+ "inverseLabel": "Skilled people"
2936
+ }
2937
+ },
2938
+ {
2939
+ "slug": "affiliated_with",
2940
+ "displayName": "Affiliated with",
2941
+ "description": "Person affiliated with an organization",
2942
+ "isDirectional": true,
2943
+ "uiHints": {
2944
+ "category": "social",
2945
+ "inverseLabel": "Members"
2946
+ }
2947
+ },
2948
+ {
2949
+ "slug": "knows",
2950
+ "displayName": "Knows",
2951
+ "description": "Two people know each other",
2952
+ "isDirectional": false,
2953
+ "uiHints": {
2954
+ "category": "social"
2955
+ }
2956
+ },
2957
+ {
2958
+ "slug": "discussed_with",
2959
+ "displayName": "Discussed with",
2960
+ "description": "Had a discussion or conversation with",
2961
+ "isDirectional": false,
2962
+ "uiHints": {
2963
+ "category": "social"
2964
+ }
2965
+ }
2966
+ ],
2967
+ "commands": [
2968
+ {
2969
+ "title": "Create View from Description",
2970
+ "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.",
2971
+ "derivedInputs": [
2972
+ {
2973
+ "name": "goal",
2974
+ "label": "Goal",
2975
+ "type": "text"
2976
+ }
2977
+ ],
2978
+ "outputMode": "view",
2979
+ "canCreateViews": true,
2980
+ "permissionsProfile": "propose_writes"
2981
+ },
2982
+ {
2983
+ "title": "Extract Entities from Text",
2984
+ "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—always propose.",
2985
+ "derivedInputs": [],
2986
+ "outputMode": "proposal",
2987
+ "canCreateViews": false,
2988
+ "permissionsProfile": "propose_writes"
2989
+ },
2990
+ {
2991
+ "title": "Summarize Selection",
2992
+ "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.",
2993
+ "derivedInputs": [],
2994
+ "outputMode": "text",
2995
+ "canCreateViews": false,
2996
+ "permissionsProfile": "read_only"
2997
+ },
2998
+ {
2999
+ "title": "Analyze Entities",
3000
+ "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.",
3001
+ "derivedInputs": [],
3002
+ "outputMode": "text",
3003
+ "canCreateViews": false,
3004
+ "permissionsProfile": "read_only"
3005
+ },
3006
+ {
3007
+ "title": "Create Task from Text",
3008
+ "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—do not create entities directly. The user will review and approve.",
3009
+ "derivedInputs": [],
3010
+ "outputMode": "proposal",
3011
+ "canCreateViews": false,
3012
+ "permissionsProfile": "propose_writes"
3013
+ },
3014
+ {
3015
+ "title": "Create Event from Text",
3016
+ "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—do not create entities directly. The user will review and approve.",
3017
+ "derivedInputs": [],
3018
+ "outputMode": "proposal",
3019
+ "canCreateViews": false,
3020
+ "permissionsProfile": "propose_writes"
3021
+ }
3022
+ ],
3023
+ "automations": [
3024
+ {
3025
+ "name": "Generate report",
3026
+ "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.",
3027
+ "triggerType": "manual",
3028
+ "triggerConfig": {},
3029
+ "status": "active",
3030
+ "flow": {
3031
+ "nodes": [
3032
+ {
3033
+ "id": "trigger",
3034
+ "type": "trigger",
3035
+ "position": {
3036
+ "x": 0,
3037
+ "y": 200
3038
+ },
3039
+ "data": {
3040
+ "triggerType": "manual",
3041
+ "label": "Run report",
3042
+ "config": {}
3043
+ }
3044
+ },
3045
+ {
3046
+ "id": "now",
3047
+ "type": "compute",
3048
+ "position": {
3049
+ "x": 200,
3050
+ "y": 200
3051
+ },
3052
+ "data": {
3053
+ "label": "Timestamp",
3054
+ "operation": "now"
3055
+ }
3056
+ },
3057
+ {
3058
+ "id": "gather-tasks",
3059
+ "type": "query",
3060
+ "position": {
3061
+ "x": 420,
3062
+ "y": 20
3063
+ },
3064
+ "data": {
3065
+ "label": "Tasks",
3066
+ "profileSlug": "task",
3067
+ "filter": "{\"updatedAt\": {\"$gte\": \"{{trigger.payload.since}}\"}}",
3068
+ "orderBy": "updatedAt",
3069
+ "orderDir": "desc",
3070
+ "limit": 25,
3071
+ "errorHandling": {
3072
+ "continueOnError": true
3073
+ }
3074
+ }
3075
+ },
3076
+ {
3077
+ "id": "gather-notes",
3078
+ "type": "query",
3079
+ "position": {
3080
+ "x": 420,
3081
+ "y": 140
3082
+ },
3083
+ "data": {
3084
+ "label": "Notes",
3085
+ "profileSlug": "note",
3086
+ "filter": "{\"updatedAt\": {\"$gte\": \"{{trigger.payload.since}}\"}}",
3087
+ "orderBy": "updatedAt",
3088
+ "orderDir": "desc",
3089
+ "limit": 15,
3090
+ "errorHandling": {
3091
+ "continueOnError": true
3092
+ }
3093
+ }
3094
+ },
3095
+ {
3096
+ "id": "gather-people",
3097
+ "type": "query",
3098
+ "position": {
3099
+ "x": 420,
3100
+ "y": 260
3101
+ },
3102
+ "data": {
3103
+ "label": "People",
3104
+ "profileSlug": "person",
3105
+ "filter": "{\"updatedAt\": {\"$gte\": \"{{trigger.payload.since}}\"}}",
3106
+ "orderBy": "updatedAt",
3107
+ "orderDir": "desc",
3108
+ "limit": 15,
3109
+ "errorHandling": {
3110
+ "continueOnError": true
3111
+ }
3112
+ }
3113
+ },
3114
+ {
3115
+ "id": "gather-companies",
3116
+ "type": "query",
3117
+ "position": {
3118
+ "x": 420,
3119
+ "y": 380
3120
+ },
3121
+ "data": {
3122
+ "label": "Companies",
3123
+ "profileSlug": "company",
3124
+ "filter": "{\"updatedAt\": {\"$gte\": \"{{trigger.payload.since}}\"}}",
3125
+ "orderBy": "updatedAt",
3126
+ "orderDir": "desc",
3127
+ "limit": 15,
3128
+ "errorHandling": {
3129
+ "continueOnError": true
3130
+ }
3131
+ }
3132
+ },
3133
+ {
3134
+ "id": "project-tasks",
3135
+ "type": "transform",
3136
+ "position": {
3137
+ "x": 540,
3138
+ "y": 20
3139
+ },
3140
+ "data": {
3141
+ "label": "Project tasks",
3142
+ "expression": "{{steps.gather-tasks.output.entities}} | map: {{item.id}} · {{item.title}} · status={{item.properties.status}} · priority={{item.properties.priority}} · due={{item.properties.dueDate}} · updated={{item.updatedAt}}",
3143
+ "errorHandling": {
3144
+ "continueOnError": true
3145
+ }
3146
+ }
3147
+ },
3148
+ {
3149
+ "id": "project-notes",
3150
+ "type": "transform",
3151
+ "position": {
3152
+ "x": 540,
3153
+ "y": 140
3154
+ },
3155
+ "data": {
3156
+ "label": "Project notes",
3157
+ "expression": "{{steps.gather-notes.output.entities}} | map: {{item.id}} · {{item.title}} · updated={{item.updatedAt}}",
3158
+ "errorHandling": {
3159
+ "continueOnError": true
3160
+ }
3161
+ }
3162
+ },
3163
+ {
3164
+ "id": "project-people",
3165
+ "type": "transform",
3166
+ "position": {
3167
+ "x": 540,
3168
+ "y": 260
3169
+ },
3170
+ "data": {
3171
+ "label": "Project people",
3172
+ "expression": "{{steps.gather-people.output.entities}} | map: {{item.id}} · {{item.title}} · email={{item.properties.email}} · lastInteraction={{item.properties.lastInteractionAt}} · updated={{item.updatedAt}}",
3173
+ "errorHandling": {
3174
+ "continueOnError": true
3175
+ }
3176
+ }
3177
+ },
3178
+ {
3179
+ "id": "project-companies",
3180
+ "type": "transform",
3181
+ "position": {
3182
+ "x": 540,
3183
+ "y": 380
3184
+ },
3185
+ "data": {
3186
+ "label": "Project companies",
3187
+ "expression": "{{steps.gather-companies.output.entities}} | map: {{item.id}} · {{item.title}} · industry={{item.properties.industry}} · location={{item.properties.location}} · updated={{item.updatedAt}}",
3188
+ "errorHandling": {
3189
+ "continueOnError": true
3190
+ }
3191
+ }
3192
+ },
3193
+ {
3194
+ "id": "analyze",
3195
+ "type": "capability",
3196
+ "position": {
3197
+ "x": 660,
3198
+ "y": 200
3199
+ },
3200
+ "data": {
3201
+ "label": "Analyze",
3202
+ "verbId": "ai.generate",
3203
+ "verbKind": "read",
3204
+ "inputMapping": {
3205
+ "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 — say what the state of it means.\nDATA INTEGRITY CHECK — 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 — 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 — 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 — 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\" — 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 — 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…\" 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 — 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> · <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` — 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 — 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 — short paragraphs and bullets, with [[entity:…]] chips\ninline wherever you name a record. No markdown headings, no ::: directive\nblocks, no code fences. Under 300 words. Another round will format this.",
3206
+ "prompt": "STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nCOUNTS — 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 — 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 — 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 — one line per record, MOST RECENTLY UPDATED first. Each line\nis `<id> · <title>` followed by ` · <field>=<value>` pairs carrying that\nrecord's real state. An EMPTY value means the field is not set on that\nrecord — 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 — {{steps.gather-tasks.output.count}} found: {{steps.project-tasks.output.result}}\nNotes — {{steps.gather-notes.output.count}} found: {{steps.project-notes.output.result}}\nPeople — {{steps.gather-people.output.count}} found: {{steps.project-people.output.result}}\nCompanies — {{steps.gather-companies.output.count}} found: {{steps.project-companies.output.result}}",
3207
+ "maxTokens": "2000"
3208
+ },
3209
+ "errorHandling": {
3210
+ "continueOnError": true
3211
+ }
3212
+ }
3213
+ },
3214
+ {
3215
+ "id": "relate",
3216
+ "type": "capability",
3217
+ "position": {
3218
+ "x": 900,
3219
+ "y": 200
3220
+ },
3221
+ "data": {
3222
+ "label": "Patterns",
3223
+ "verbId": "ai.generate",
3224
+ "verbKind": "read",
3225
+ "inputMapping": {
3226
+ "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 —\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 — 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 — 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 — 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 — 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 — 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\" — 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 — 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…\" 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 — 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> · <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` — 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 — 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:…]] chips inline. No\nheadings, no ::: directive blocks, no code fences.",
3227
+ "prompt": "STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nCOUNTS — 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 — 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 — 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 — one line per record, MOST RECENTLY UPDATED first. Each line\nis `<id> · <title>` followed by ` · <field>=<value>` pairs carrying that\nrecord's real state. An EMPTY value means the field is not set on that\nrecord — 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 — {{steps.gather-tasks.output.count}} found: {{steps.project-tasks.output.result}}\nNotes — {{steps.gather-notes.output.count}} found: {{steps.project-notes.output.result}}\nPeople — {{steps.gather-people.output.count}} found: {{steps.project-people.output.result}}\nCompanies — {{steps.gather-companies.output.count}} found: {{steps.project-companies.output.result}}\n\nANALYST'S READ — 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 — 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 — never answer with nothing.",
3228
+ "maxTokens": "2000"
3229
+ },
3230
+ "errorHandling": {
3231
+ "continueOnError": true
3232
+ }
3233
+ }
3234
+ },
3235
+ {
3236
+ "id": "assemble",
3237
+ "type": "capability",
3238
+ "position": {
3239
+ "x": 1140,
3240
+ "y": 200
3241
+ },
3242
+ "data": {
3243
+ "label": "Assemble",
3244
+ "verbId": "ai.generate",
3245
+ "verbKind": "read",
3246
+ "inputMapping": {
3247
+ "system": "You are the ASSEMBLER round of a Synap workspace report. You compose the FINAL\ndocument body in Synap-flavoured markdown. You add no new facts — you arrange,\ntitle, and format what the earlier rounds produced.\n\nNEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else — 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…\" 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.\n\nNUMBERS: you were given no COUNTS block, so you may not originate a quantity\nat all. Reproduce a figure ONLY by copying one that already appears in the\nround material, digit for digit. Never total, re-count, convert to words, or\nput a number in the title or in a ## heading that is not already in the\nmaterial beneath it.\n\nSYNAP MARKDOWN SYNTAX — these rules are absolute:\n1. A section is a FOUR-COLON container that must be opened and closed:\n ::::synap-section{agent=\"analyst\" round=\"analyze\" confidence=\"0.8\"}\n ...content...\n ::::\n2. Leaf embeds use THREE colons and MUST BE CLOSED with their own ::: line.\n There is NO self-closing form. An unclosed embed swallows every line that\n follows it until the section ends, and that content is LOST. Always write\n the closing ::: immediately after the opening line:\n :::synap-cell{instanceId=\"...\"}\n :::\n :::synap-view{viewId=\"...\"}\n :::\n :::synap-entity{id=\"...\"}\n :::\n3. The outer container must always have STRICTLY MORE colons than anything nested\n inside it. Four outside, three inside. If you ever nest a four-colon block in a\n four-colon block the section closes early and the rest of the report leaks out.\n4. ATTRIBUTE VALUES MUST BE QUOTED. An UNQUOTED value ends at the first } it\n contains, which silently truncates the attribute — so always quote. Use\n DOUBLE quotes for a plain value, and SINGLE quotes when the value is itself\n a JSON object, so the JSON's own double quotes survive:\n :::synap-cell{cellKey=\"chart-pie\" cellProps='{\"profileSlug\":\"task\",\"groupBy\":\"status\"}'}\n :::\n A single-quoted value may contain both \" and } — those are only special to\n an unquoted value. Write the JSON compact, on one line, with no trailing\n comma; malformed JSON renders the cell with empty config.\n5. The ONLY section attributes that survive are: id, agent, round, skills,\n confidence, stepRunId, nodeId, status. Anything else is silently dropped.\n6. confidence is a 0-1 ratio written as a decimal string, e.g. confidence=\"0.8\".\n Never a percentage.\n7. CHIPS ARE CONTENT — COPY THEM THROUGH VERBATIM. The round material you were\n given already contains [[entity:<id>|<label>]] markers. Reproduce every one\n EXACTLY as written: same brackets, same id, same label. Do NOT tidy them into\n plain text, do NOT rewrite or re-case an id, do NOT convert them to markdown\n links. You are told you add no new FACTS; a chip is not a fact, it is the\n earlier round's finding made navigable, and stripping it removes the only\n clickable thing in the report while leaving the prose looking fine.\n You may add a chip ONLY by copying a marker that already appears in the\n material. Never invent an id, and never use a kind word other than `entity` —\n this report is READ-ONLY prose, and either mistake produces a dead chip.\n8. NEVER EMIT :::synap-view, AND NEVER EMIT :::synap-cell WITH AN instanceId.\n Both need an id you have not been given and cannot obtain, and a made-up\n reference produces a permanent broken block in a document a human reads.\n9. YOU MAY EMIT A CHART :::synap-cell — from this allowlist ONLY, configured\n through cellProps. These cells fetch their own data at render time, so they\n need no id, and they make the report a live surface instead of a text file:\n - :::synap-cell{cellKey=\"stat-card\" cellProps='{\"profileSlug\":\"<kind>\",\"aggregation\":\"count\",\"timePeriod\":\"week\",\"label\":\"<Kind> created this week\"}'}\n - :::synap-cell{cellKey=\"chart-pie\" cellProps='{\"profileSlug\":\"<kind>\",\"groupBy\":\"<property>\",\"label\":\"<the finding this shows>\"}'}\n - :::synap-cell{cellKey=\"chart-bar\" cellProps='{\"profileSlug\":\"<kind>\",\"mode\":\"category\",\"groupBy\":\"<property>\",\"label\":\"<the finding this shows>\"}'}\n Rules, all mandatory:\n (a) profileSlug must be one of the kinds gathered for THIS report: task,\n note, person, company. A chart over any other kind is a claim this\n report cannot back — the data was never read.\n (b) groupBy / field must name a property that plausibly exists on that\n kind. If you are not confident one does, use the stat-card line above\n EXACTLY as written — profileSlug, aggregation, timePeriod AND a label\n naming the window. Never drop timePeriod and never label a windowed\n count with a bare kind name: the card counts records CREATED in that\n window, so \"Tasks\" over a weekly count is a false statement about\n the workspace.\n (c) Close EVERY embed with its own ::: line, per rule 2. An unclosed embed\n swallows the rest of the section.\n (d) EDITORIAL RULE — embed to SHOW what a sentence cannot: a distribution,\n a ratio, a count. Never to decorate. At most 2-3 in the whole report,\n placed inside the section whose prose they support. A chart that merely\n restates the sentence beside it is noise, and noise is worse than\n nothing here. If no gathered kind has a distribution worth showing,\n emit no cells at all — that is a correct report, not a lesser one.\n (e) AT MOST ONE cell per section, and NEVER at the top of a section.\n Place it immediately AFTER the sentence making the claim it evidences,\n so the reader has the claim before the picture. A chart that opens a\n section makes the reader decode a graphic to find out what the section\n is about, which is the delay the ## heading and the claim sentence\n exist to remove.\n (f) The label must state the FINDING, not the dimension. Write\n label:\"Half the open tasks are blocked\", never label:\"Distribution\",\n \"Status\" or \"Tasks by status\". Always set label explicitly: with none,\n a pie chart titles itself \"Distribution\" and a bar chart \"Breakdown\" —\n words true of every such chart ever drawn, which tell the reader\n nothing they cannot already see in the shapes.\n (g) A CHART IS A LIVE VIEW, THE PROSE IS A SNAPSHOT. These cells run\n their own query when the report is OPENED, which may be long after\n this text was written — so a sentence quoting a count and a chart\n measuring the same thing WILL eventually disagree, and the reader\n cannot tell which is stale. Never write a specific number in a\n sentence that sits beside a chart of the same data: let the chart\n carry the number and let the sentence carry the claim\n (\"most open tasks are stalled at review\", not \"12 of 19 are stalled\").\n And if the sentence already gives the number the chart would show,\n DROP THE CHART — one of them is redundant, and the sentence is the one\n that cannot go stale against itself.\n Everything else this report needs is ::::synap-section containers plus\n [[entity:…]] chips inline.\n\n10. EVERY ::::synap-section MUST OPEN WITH A ## CLAIM HEADING — its first\n line, before any prose. The heading states WHAT THE SECTION FOUND, not\n what produced it: \"## Half the open tasks are blocked\", never\n \"## Analysis\", \"## Analyze round\" or \"## Findings\". Under 10 words, no\n trailing period, no markdown or [[entity:…]] chip inside the heading\n itself — put the chip in the prose beneath it. Write exactly one ## per\n section. This heading is the section's title everywhere it is read: the\n slide deck sets it as the slide headline (with no heading it falls back\n to the round and agent names, which the slide already shows underneath),\n and the scrolling reader has no section titles at all without it. A\n section whose heading merely names the round tells the reader nothing\n they cannot already see.\n\nSTRUCTURE: one ::::synap-section per round, in order — analyze, then relate.\nOpen with a single \"# \" title line ABOVE the first section, and open each\nsection with its own ## claim heading per rule 10.\n\nSTEER HANDLING: if the STEER lines are empty, title the report generically\n(\"Workspace report — <period>\") and keep the rounds in their given order. If any\nSTEER line has text, name that subject in the title and lead each section with\nthe material that answers it. Never add facts the rounds did not give you.\n\nMISSING ROUNDS: if a round's material looks like {\"error\": \"...\"} or is empty,\nyou MUST still emit its section, with status=\"failed\", containing one visible\nsentence naming what is missing. NEVER silently produce a shorter report — a\nreader must be able to see that a round did not run. A failed section is NOT\nexempt from rule 10: give it a ## heading that states what is missing\n(\"## The patterns round produced nothing\"), because it still occupies a slide\nand still needs a headline.\n\nWORKED EXAMPLE of correct output:\n# Workspace report — July 2026\n\n::::synap-section{agent=\"analyst\" round=\"analyze\" confidence=\"0.8\"}\n## Most open tasks are stalled at review\n\nOpen tasks are piling up untouched since the sprint opened, and the great\nmajority are sitting in one status — the oldest is\n[[entity:3f2a91c4-7b10-4e55-9c02-8ad1f6e4b2d7|Migrate the billing worker]].\nNotes are thin.\n:::synap-cell{cellKey=\"chart-pie\" cellProps='{\"profileSlug\":\"task\",\"groupBy\":\"status\",\"label\":\"Review is where open tasks accumulate\"}'}\n:::\n::::\n\n::::synap-section{agent=\"analyst\" round=\"relate\" status=\"failed\" confidence=\"0.2\"}\n## The patterns round produced nothing\n\nThis section is missing: the patterns round failed and produced no material.\n::::\n\nBe CONCISE — your entire output must fit in 2000 tokens. Emit ONLY the markdown\ndocument. No preamble, no explanation, no code fences around the whole thing.",
3248
+ "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}}",
3249
+ "maxTokens": "2000"
3250
+ }
3251
+ }
3252
+ },
3253
+ {
3254
+ "id": "summarize",
3255
+ "type": "capability",
3256
+ "position": {
3257
+ "x": 1380,
3258
+ "y": 200
3259
+ },
3260
+ "data": {
3261
+ "label": "One-line summary",
3262
+ "verbId": "ai.generate",
3263
+ "verbKind": "read",
3264
+ "inputMapping": {
3265
+ "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.\"",
3266
+ "prompt": "Material to summarize (may be an error object if the round failed):\n{{steps.analyze.output}}",
3267
+ "maxTokens": "500"
3268
+ }
3269
+ }
3270
+ },
3271
+ {
3272
+ "id": "guard-assembled",
3273
+ "type": "guard",
3274
+ "position": {
3275
+ "x": 1620,
3276
+ "y": 200
3277
+ },
3278
+ "data": {
3279
+ "label": "Body assembled",
3280
+ "checks": [
3281
+ {
3282
+ "path": "steps.assemble.output",
3283
+ "minLength": 200,
3284
+ "message": "The assembler produced no usable body (under 200 characters) — refusing to write an empty report."
3285
+ },
3286
+ {
3287
+ "path": "steps.assemble.output.error",
3288
+ "exists": false,
3289
+ "message": "The assembler round failed — refusing to write a report without a body."
3290
+ }
3291
+ ]
3292
+ }
3293
+ },
3294
+ {
3295
+ "id": "create-report",
3296
+ "type": "output",
3297
+ "position": {
3298
+ "x": 1860,
3299
+ "y": 200
3300
+ },
3301
+ "data": {
3302
+ "label": "Create report",
3303
+ "outputType": "entity_create",
3304
+ "config": {
3305
+ "profileSlug": "report",
3306
+ "title": "Workspace report — {{steps.now.output.result}}",
3307
+ "properties": {
3308
+ "reportPeriod": "{{steps.now.output.result}}",
3309
+ "generatedAt": "{{steps.now.output.result}}",
3310
+ "reportStatus": "ready",
3311
+ "summary": "{{steps.summarize.output}}",
3312
+ "reportSources": [
3313
+ "Tasks",
3314
+ "Notes",
3315
+ "People",
3316
+ "Companies"
3317
+ ]
3318
+ },
3319
+ "body": "{{steps.assemble.output}}",
3320
+ "systemData": {
3321
+ "renderer": {
3322
+ "kind": "cell",
3323
+ "cellKey": "{{trigger.payload.renderer}}"
3324
+ }
3325
+ }
3326
+ }
3327
+ }
3328
+ }
3329
+ ],
3330
+ "edges": [
3331
+ {
3332
+ "id": "e-trigger-now",
3333
+ "source": "trigger",
3334
+ "target": "now"
3335
+ },
3336
+ {
3337
+ "id": "e-now-tasks",
3338
+ "source": "now",
3339
+ "target": "gather-tasks"
3340
+ },
3341
+ {
3342
+ "id": "e-now-notes",
3343
+ "source": "now",
3344
+ "target": "gather-notes"
3345
+ },
3346
+ {
3347
+ "id": "e-now-people",
3348
+ "source": "now",
3349
+ "target": "gather-people"
3350
+ },
3351
+ {
3352
+ "id": "e-now-companies",
3353
+ "source": "now",
3354
+ "target": "gather-companies"
3355
+ },
3356
+ {
3357
+ "id": "e-tasks-project",
3358
+ "source": "gather-tasks",
3359
+ "target": "project-tasks"
3360
+ },
3361
+ {
3362
+ "id": "e-notes-project",
3363
+ "source": "gather-notes",
3364
+ "target": "project-notes"
3365
+ },
3366
+ {
3367
+ "id": "e-people-project",
3368
+ "source": "gather-people",
3369
+ "target": "project-people"
3370
+ },
3371
+ {
3372
+ "id": "e-companies-project",
3373
+ "source": "gather-companies",
3374
+ "target": "project-companies"
3375
+ },
3376
+ {
3377
+ "id": "e-tasks-analyze",
3378
+ "source": "project-tasks",
3379
+ "target": "analyze"
3380
+ },
3381
+ {
3382
+ "id": "e-notes-analyze",
3383
+ "source": "project-notes",
3384
+ "target": "analyze"
3385
+ },
3386
+ {
3387
+ "id": "e-people-analyze",
3388
+ "source": "project-people",
3389
+ "target": "analyze"
3390
+ },
3391
+ {
3392
+ "id": "e-companies-analyze",
3393
+ "source": "project-companies",
3394
+ "target": "analyze"
3395
+ },
3396
+ {
3397
+ "id": "e-analyze-relate",
3398
+ "source": "analyze",
3399
+ "target": "relate"
3400
+ },
3401
+ {
3402
+ "id": "e-relate-assemble",
3403
+ "source": "relate",
3404
+ "target": "assemble"
3405
+ },
3406
+ {
3407
+ "id": "e-assemble-summarize",
3408
+ "source": "assemble",
3409
+ "target": "summarize"
3410
+ },
3411
+ {
3412
+ "id": "e-summarize-guard",
3413
+ "source": "summarize",
3414
+ "target": "guard-assembled"
3415
+ },
3416
+ {
3417
+ "id": "e-guard-create",
3418
+ "source": "guard-assembled",
3419
+ "target": "create-report"
3420
+ }
3421
+ ]
3422
+ }
3423
+ }
3424
+ ]
3425
+ },
2737
3426
  "blockchain-ecosystem": {
2738
3427
  "meta": {
2739
3428
  "slug": "blockchain-ecosystem",
@@ -2772,6 +3461,7 @@ const DATA = {
2772
3461
  "description": "A liquidity provider — makes markets and provides depth for a token.",
2773
3462
  "scope": "shared",
2774
3463
  "profileKind": "role",
3464
+ "roleCategory": "provider",
2775
3465
  "applicableKinds": [
2776
3466
  "company",
2777
3467
  "person"
@@ -2817,6 +3507,7 @@ const DATA = {
2817
3507
  "description": "A security firm or individual that audits smart contracts and protocols.",
2818
3508
  "scope": "shared",
2819
3509
  "profileKind": "role",
3510
+ "roleCategory": "provider",
2820
3511
  "applicableKinds": [
2821
3512
  "company",
2822
3513
  "person"
@@ -2862,6 +3553,7 @@ const DATA = {
2862
3553
  "description": "An outsourced engineering studio or build partner.",
2863
3554
  "scope": "shared",
2864
3555
  "profileKind": "role",
3556
+ "roleCategory": "provider",
2865
3557
  "applicableKinds": [
2866
3558
  "company",
2867
3559
  "person"
@@ -2907,6 +3599,7 @@ const DATA = {
2907
3599
  "description": "A brand, growth, or content marketing agency.",
2908
3600
  "scope": "shared",
2909
3601
  "profileKind": "role",
3602
+ "roleCategory": "provider",
2910
3603
  "applicableKinds": [
2911
3604
  "company",
2912
3605
  "person"
@@ -2952,6 +3645,7 @@ const DATA = {
2952
3645
  "description": "A performance / paid-media buying agency.",
2953
3646
  "scope": "shared",
2954
3647
  "profileKind": "role",
3648
+ "roleCategory": "provider",
2955
3649
  "applicableKinds": [
2956
3650
  "company",
2957
3651
  "person"
@@ -2997,6 +3691,7 @@ const DATA = {
2997
3691
  "description": "A key opinion leader or influencer who amplifies reach.",
2998
3692
  "scope": "shared",
2999
3693
  "profileKind": "role",
3694
+ "roleCategory": "provider",
3000
3695
  "applicableKinds": [
3001
3696
  "company",
3002
3697
  "person"
@@ -3033,6 +3728,100 @@ const DATA = {
3033
3728
  "inputType": "textarea"
3034
3729
  }
3035
3730
  ]
3731
+ },
3732
+ {
3733
+ "slug": "grant-program",
3734
+ "displayName": "Grant Program",
3735
+ "icon": "landmark",
3736
+ "color": "emerald",
3737
+ "description": "A per-chain funding program (e.g. Stellar SCF, Optimism RetroPGF) — the long-lived funder above a round.",
3738
+ "profileKind": "kind",
3739
+ "entityScope": "pod",
3740
+ "scope": "shared",
3741
+ "properties": [
3742
+ {
3743
+ "slug": "blockchain",
3744
+ "label": "Blockchain / Chain",
3745
+ "valueType": "string",
3746
+ "inputType": "text"
3747
+ },
3748
+ {
3749
+ "slug": "programName",
3750
+ "label": "Program Name",
3751
+ "valueType": "string",
3752
+ "inputType": "text"
3753
+ },
3754
+ {
3755
+ "slug": "programType",
3756
+ "label": "Program Type",
3757
+ "valueType": "string",
3758
+ "inputType": "select",
3759
+ "enumValues": [
3760
+ "direct",
3761
+ "retroactive",
3762
+ "quadratic",
3763
+ "rfp"
3764
+ ]
3765
+ },
3766
+ {
3767
+ "slug": "status",
3768
+ "label": "Status",
3769
+ "valueType": "string",
3770
+ "inputType": "select",
3771
+ "enumValues": [
3772
+ "active",
3773
+ "paused",
3774
+ "retired"
3775
+ ]
3776
+ },
3777
+ {
3778
+ "slug": "url",
3779
+ "label": "Program URL",
3780
+ "valueType": "string",
3781
+ "inputType": "url"
3782
+ },
3783
+ {
3784
+ "slug": "cadence",
3785
+ "label": "Cadence",
3786
+ "valueType": "string",
3787
+ "inputType": "text",
3788
+ "placeholder": "every 6 weeks"
3789
+ },
3790
+ {
3791
+ "slug": "nextDeadline",
3792
+ "label": "Next Deadline",
3793
+ "valueType": "date",
3794
+ "inputType": "date"
3795
+ },
3796
+ {
3797
+ "slug": "submissionTracks",
3798
+ "label": "Submission Tracks",
3799
+ "valueType": "array",
3800
+ "inputType": "text"
3801
+ },
3802
+ {
3803
+ "slug": "trancheModel",
3804
+ "label": "Tranche Model",
3805
+ "valueType": "string",
3806
+ "inputType": "text",
3807
+ "placeholder": "4 tranches / 90 days"
3808
+ },
3809
+ {
3810
+ "slug": "defaultPlaybook",
3811
+ "label": "Default Playbook",
3812
+ "valueType": "string",
3813
+ "inputType": "text",
3814
+ "constraints": {
3815
+ "defaultValue": "Grant Process"
3816
+ }
3817
+ },
3818
+ {
3819
+ "slug": "description",
3820
+ "label": "Description",
3821
+ "valueType": "string",
3822
+ "inputType": "textarea"
3823
+ }
3824
+ ]
3036
3825
  }
3037
3826
  ],
3038
3827
  "entityLinks": [
@@ -6788,17 +7577,16 @@ const DATA = {
6788
7577
  },
6789
7578
  "profiles": [
6790
7579
  {
6791
- "slug": "crm-lead",
6792
- "displayName": "CRM Lead",
6793
- "icon": "user-round-search",
6794
- "color": "amber",
6795
- "description": "A person or company actively being qualified or contacted by this CRM workspace.",
6796
- "scope": "WORKSPACE",
7580
+ "slug": "lead",
7581
+ "displayName": "Lead",
7582
+ "icon": "user-plus",
7583
+ "color": "yellow",
7584
+ "description": "An early-pipeline prospect — not yet a client, being qualified or nurtured.",
7585
+ "scope": "shared",
6797
7586
  "profileKind": "role",
6798
- "semanticSlug": "crm.lead",
6799
7587
  "applicableKinds": [
6800
- "person",
6801
- "company"
7588
+ "company",
7589
+ "person"
6802
7590
  ],
6803
7591
  "properties": [
6804
7592
  {
@@ -6895,17 +7683,16 @@ const DATA = {
6895
7683
  ]
6896
7684
  },
6897
7685
  {
6898
- "slug": "crm-client",
6899
- "displayName": "CRM Client",
6900
- "icon": "badge-check",
7686
+ "slug": "client",
7687
+ "displayName": "Client",
7688
+ "icon": "briefcase",
6901
7689
  "color": "emerald",
6902
- "description": "A CRM lead converted by a won commercial proposal and ready for handoff.",
6903
- "scope": "WORKSPACE",
7690
+ "description": "An active commercial relationship — created when a deal is won.",
7691
+ "scope": "shared",
6904
7692
  "profileKind": "role",
6905
- "semanticSlug": "crm.client",
6906
7693
  "applicableKinds": [
6907
- "person",
6908
- "company"
7694
+ "company",
7695
+ "person"
6909
7696
  ],
6910
7697
  "properties": [
6911
7698
  {
@@ -7270,7 +8057,7 @@ const DATA = {
7270
8057
  "description": "Research and qualify one CRM lead, then make the next action explicit without creating a commercial proposal automatically.",
7271
8058
  "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.",
7272
8059
  "subjectProfile": {
7273
- "profileSlug": "crm-lead"
8060
+ "profileSlug": "lead"
7274
8061
  },
7275
8062
  "params": [
7276
8063
  {
@@ -7373,7 +8160,7 @@ const DATA = {
7373
8160
  },
7374
8161
  {
7375
8162
  "path": "steps.read-deal.output.entity.properties.sourcedByUserId",
7376
- "exists": true,
8163
+ "minLength": 1,
7377
8164
  "message": "Set the business developer who sourced this proposal before handing it off."
7378
8165
  },
7379
8166
  {
@@ -7443,7 +8230,7 @@ const DATA = {
7443
8230
  "checks": [
7444
8231
  {
7445
8232
  "path": "steps.read-party.output.entity.facetSlugs",
7446
- "arrayIncludes": "crm-lead",
8233
+ "arrayIncludes": "lead",
7447
8234
  "message": "The commercial counterparty must still be a CRM lead before it becomes a CRM client."
7448
8235
  }
7449
8236
  ]
@@ -7706,7 +8493,7 @@ const DATA = {
7706
8493
  "outputType": "facet_attach",
7707
8494
  "config": {
7708
8495
  "entityId": "{{steps.read-party.output.entity.id}}",
7709
- "facetSlug": "crm-client",
8496
+ "facetSlug": "client",
7710
8497
  "properties": {
7711
8498
  "becameClientAt": "{{steps.handoff-time.output.result}}",
7712
8499
  "handoffStatus": "handed_off"
@@ -7726,7 +8513,7 @@ const DATA = {
7726
8513
  "outputType": "facet_update",
7727
8514
  "config": {
7728
8515
  "entityId": "{{steps.read-party.output.entity.id}}",
7729
- "facetSlug": "crm-lead",
8516
+ "facetSlug": "lead",
7730
8517
  "properties": {
7731
8518
  "lifecycleStage": "converted",
7732
8519
  "convertedAt": "{{steps.handoff-time.output.result}}"
@@ -7869,7 +8656,7 @@ const DATA = {
7869
8656
  "label": "Qualify lead",
7870
8657
  "when": {
7871
8658
  "requiredFacetSlugs": [
7872
- "crm-lead"
8659
+ "lead"
7873
8660
  ]
7874
8661
  }
7875
8662
  },
@@ -7881,7 +8668,7 @@ const DATA = {
7881
8668
  "label": "Qualify lead",
7882
8669
  "when": {
7883
8670
  "requiredFacetSlugs": [
7884
- "crm-lead"
8671
+ "lead"
7885
8672
  ]
7886
8673
  }
7887
8674
  },
@@ -9198,7 +9985,8 @@ const DATA = {
9198
9985
  "scopeProfileSlug": "post",
9199
9986
  "description": "Your editorial calendar — every post on the day it publishes.",
9200
9987
  "config": {
9201
- "dateField": "properties.publish-date"
9988
+ "dateField": "properties.publish-date",
9989
+ "focusKind": "post"
9202
9990
  }
9203
9991
  },
9204
9992
  {
@@ -9343,6 +10131,82 @@ const DATA = {
9343
10131
  }
9344
10132
  ]
9345
10133
  },
10134
+ "profileEntityBentoTemplates": {
10135
+ "series": {
10136
+ "blocks": [
10137
+ {
10138
+ "id": "series-eb-header",
10139
+ "kind": "widget",
10140
+ "widgetType": "entity-header",
10141
+ "pos": {
10142
+ "x": 0,
10143
+ "y": 0,
10144
+ "w": 12,
10145
+ "h": 2
10146
+ }
10147
+ },
10148
+ {
10149
+ "id": "series-eb-props",
10150
+ "kind": "widget",
10151
+ "widgetType": "entity-properties",
10152
+ "pos": {
10153
+ "x": 0,
10154
+ "y": 2,
10155
+ "w": 5,
10156
+ "h": 8
10157
+ }
10158
+ },
10159
+ {
10160
+ "id": "series-eb-curriculum",
10161
+ "kind": "widget",
10162
+ "widgetType": "series-curriculum",
10163
+ "pos": {
10164
+ "x": 5,
10165
+ "y": 2,
10166
+ "w": 7,
10167
+ "h": 8
10168
+ }
10169
+ }
10170
+ ]
10171
+ },
10172
+ "pillar": {
10173
+ "blocks": [
10174
+ {
10175
+ "id": "pillar-eb-header",
10176
+ "kind": "widget",
10177
+ "widgetType": "entity-header",
10178
+ "pos": {
10179
+ "x": 0,
10180
+ "y": 0,
10181
+ "w": 12,
10182
+ "h": 2
10183
+ }
10184
+ },
10185
+ {
10186
+ "id": "pillar-eb-props",
10187
+ "kind": "widget",
10188
+ "widgetType": "entity-properties",
10189
+ "pos": {
10190
+ "x": 0,
10191
+ "y": 2,
10192
+ "w": 5,
10193
+ "h": 8
10194
+ }
10195
+ },
10196
+ {
10197
+ "id": "pillar-eb-chapter",
10198
+ "kind": "widget",
10199
+ "widgetType": "pillar-chapter",
10200
+ "pos": {
10201
+ "x": 5,
10202
+ "y": 2,
10203
+ "w": 7,
10204
+ "h": 8
10205
+ }
10206
+ }
10207
+ ]
10208
+ }
10209
+ },
9346
10210
  "entityLinks": [
9347
10211
  {
9348
10212
  "sourceProfileSlug": "post",
@@ -10070,11 +10934,12 @@ const DATA = {
10070
10934
  ],
10071
10935
  "isPublic": true
10072
10936
  },
10073
- "app": {
10074
- "kind": "url",
10075
- "appId": "crm",
10076
- "url": "https://crm.synap.live",
10077
- "title": "CRM"
10937
+ "layout": {
10938
+ "primarySurface": {
10939
+ "kind": "url",
10940
+ "url": "https://crm.synap.live",
10941
+ "title": "CRM"
10942
+ }
10078
10943
  },
10079
10944
  "dependencies": [
10080
10945
  {
@@ -10111,6 +10976,7 @@ const DATA = {
10111
10976
  "icon": "user",
10112
10977
  "color": "amber",
10113
10978
  "description": "A prospect, stakeholder, or contact.",
10979
+ "entityScope": "pod",
10114
10980
  "properties": [
10115
10981
  {
10116
10982
  "slug": "email",
@@ -10196,7 +11062,7 @@ const DATA = {
10196
11062
  "icon": "building-2",
10197
11063
  "color": "#10B981",
10198
11064
  "description": "An organisation being qualified or sold to.",
10199
- "scope": "WORKSPACE",
11065
+ "entityScope": "pod",
10200
11066
  "properties": [
10201
11067
  {
10202
11068
  "slug": "contextSummary",
@@ -10466,6 +11332,18 @@ const DATA = {
10466
11332
  "at_risk",
10467
11333
  "critical"
10468
11334
  ]
11335
+ },
11336
+ {
11337
+ "slug": "driveLink",
11338
+ "label": "Drive folder",
11339
+ "valueType": "string",
11340
+ "inputType": "url"
11341
+ },
11342
+ {
11343
+ "slug": "driveFolderId",
11344
+ "label": "Drive folder ID",
11345
+ "valueType": "string",
11346
+ "inputType": "text"
10469
11347
  }
10470
11348
  ]
10471
11349
  },
@@ -10641,6 +11519,30 @@ const DATA = {
10641
11519
  "sourceProfileSlug": "company",
10642
11520
  "targetProfileSlug": "deal",
10643
11521
  "type": "linked_to_deal"
11522
+ },
11523
+ {
11524
+ "sourceProfileSlug": "person",
11525
+ "targetProfileSlug": "deal",
11526
+ "type": "client_on_deal",
11527
+ "label": "Client side"
11528
+ },
11529
+ {
11530
+ "sourceProfileSlug": "company",
11531
+ "targetProfileSlug": "deal",
11532
+ "type": "client_on_deal",
11533
+ "label": "Client side"
11534
+ },
11535
+ {
11536
+ "sourceProfileSlug": "person",
11537
+ "targetProfileSlug": "deal",
11538
+ "type": "partner_on_deal",
11539
+ "label": "Partner side"
11540
+ },
11541
+ {
11542
+ "sourceProfileSlug": "company",
11543
+ "targetProfileSlug": "deal",
11544
+ "type": "partner_on_deal",
11545
+ "label": "Partner side"
10644
11546
  }
10645
11547
  ],
10646
11548
  "onboarding": {
@@ -12508,6 +13410,7 @@ const DATA = {
12508
13410
  "icon": "user",
12509
13411
  "color": "amber",
12510
13412
  "description": "A person in the ecosystem — a founder, operator, or contact.",
13413
+ "entityScope": "pod",
12511
13414
  "properties": [
12512
13415
  {
12513
13416
  "slug": "email",
@@ -12529,6 +13432,7 @@ const DATA = {
12529
13432
  "icon": "building-2",
12530
13433
  "color": "#10B981",
12531
13434
  "description": "A company in the ecosystem — a player you operate around.",
13435
+ "entityScope": "pod",
12532
13436
  "properties": [
12533
13437
  {
12534
13438
  "slug": "website",
@@ -13256,7 +14160,7 @@ const DATA = {
13256
14160
  "slug": "business-developer",
13257
14161
  "kind": "workspace",
13258
14162
  "relation": "require",
13259
- "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 — without it the CRM lens is provisioned with pod-wide lead/client only and the app's pipeline is empty."
14163
+ "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 — without it the CRM lens has the bare lead/client roles with no lifecycle or handoff fields and the app's pipeline is empty."
13260
14164
  },
13261
14165
  {
13262
14166
  "slug": "content-os",
@@ -14739,6 +15643,18 @@ const DATA = {
14739
15643
  "at_risk",
14740
15644
  "critical"
14741
15645
  ]
15646
+ },
15647
+ {
15648
+ "slug": "driveLink",
15649
+ "label": "Drive folder",
15650
+ "valueType": "string",
15651
+ "inputType": "url"
15652
+ },
15653
+ {
15654
+ "slug": "driveFolderId",
15655
+ "label": "Drive folder ID",
15656
+ "valueType": "string",
15657
+ "inputType": "text"
14742
15658
  }
14743
15659
  ]
14744
15660
  },
@@ -15227,6 +16143,12 @@ const DATA = {
15227
16143
  "kind": "workspace",
15228
16144
  "relation": "require",
15229
16145
  "reason": "Provides the pod-wide client / partner / sponsor role vocabulary this workspace reuses."
16146
+ },
16147
+ {
16148
+ "slug": "blockchain-ecosystem",
16149
+ "kind": "workspace",
16150
+ "relation": "require",
16151
+ "reason": "Provides the pod-wide grant-program kind (per-chain funding programs) this workspace's rounds belong to."
15230
16152
  }
15231
16153
  ],
15232
16154
  "workspace": {
@@ -16188,6 +17110,18 @@ const DATA = {
16188
17110
  "at_risk",
16189
17111
  "critical"
16190
17112
  ]
17113
+ },
17114
+ {
17115
+ "slug": "driveLink",
17116
+ "label": "Drive folder",
17117
+ "valueType": "string",
17118
+ "inputType": "url"
17119
+ },
17120
+ {
17121
+ "slug": "driveFolderId",
17122
+ "label": "Drive folder ID",
17123
+ "valueType": "string",
17124
+ "inputType": "text"
16191
17125
  }
16192
17126
  ]
16193
17127
  },
@@ -16374,12 +17308,21 @@ const DATA = {
16374
17308
  "sortDir": "desc"
16375
17309
  }
16376
17310
  },
17311
+ {
17312
+ "name": "Deadlines",
17313
+ "type": "calendar",
17314
+ "scopeProfileSlug": "round",
17315
+ "description": "Upcoming round submission deadlines, on one time axis.",
17316
+ "config": {
17317
+ "dateField": "properties.submissionDeadline",
17318
+ "colorField": "status"
17319
+ }
17320
+ },
16377
17321
  {
16378
17322
  "name": "Awarded Projects",
16379
17323
  "type": "gallery",
16380
17324
  "scopeProfileSlug": "grant-project",
16381
17325
  "config": {
16382
- "coverField": "summary",
16383
17326
  "showFields": [
16384
17327
  "currentStatus",
16385
17328
  "awardedRoundCount",
@@ -16423,6 +17366,30 @@ const DATA = {
16423
17366
  "type": "submitted_by",
16424
17367
  "label": "Submitted By"
16425
17368
  },
17369
+ {
17370
+ "sourceProfileSlug": "grant_submission",
17371
+ "targetProfileSlug": "contract",
17372
+ "type": "for_contract",
17373
+ "label": "Engagement"
17374
+ },
17375
+ {
17376
+ "sourceProfileSlug": "grant_submission",
17377
+ "targetProfileSlug": "deal",
17378
+ "type": "billed_as",
17379
+ "label": "Billed as"
17380
+ },
17381
+ {
17382
+ "sourceProfileSlug": "round",
17383
+ "targetProfileSlug": "grant-program",
17384
+ "type": "belongs_to",
17385
+ "label": "Program"
17386
+ },
17387
+ {
17388
+ "sourceProfileSlug": "contract",
17389
+ "targetProfileSlug": "grant-program",
17390
+ "type": "pursues",
17391
+ "label": "Pursues"
17392
+ },
16426
17393
  {
16427
17394
  "sourceProfileSlug": "grant-project",
16428
17395
  "targetProfileSlug": "round",
@@ -16448,6 +17415,215 @@ const DATA = {
16448
17415
  "label": "Evidenced By"
16449
17416
  }
16450
17417
  ],
17418
+ "playbooks": [
17419
+ {
17420
+ "name": "Grant Process",
17421
+ "description": "Run an engagement through a grant program end to end — identify fit, draft the application, submit, track the decision, and deliver the tranches. Reads the program's own cadence / tracks / tranche model so one playbook fits every chain.",
17422
+ "goalTemplate": "Advance this engagement ({contract}) through the grant program ({grantProgram}) it is pursuing. FIRST read the program entity's own data — its cadence, submissionTracks, trancheModel, and nextDeadline — plus the client context on the contract, and let THOSE drive the plan: do not assume a chain's mechanics, read them from the program. Then work the stages in order and keep the client's `grant_submission` card as the durable state of record:\n1. Identify — understand the project and which of the program's\n submissionTracks fit; set the submission `stage` to `scope`.\n2. Draft — write the abstract, then the full proposal (products & services,\n success criteria, GTM, traction); move `stage` `abstract` → `build` and\n fill the submission's body fields.\n3. Submit — finalize against the program's nextDeadline and submit; once\n submitted, set `stage` to `awaiting-results`.\n4. Awaiting — track the decision and keep `status` current\n (in-review / waiting-client / needs-document / blocked).\n5. Awarded — on a decision, set `outcome` (awarded / rejected / partial)\n and record the awarded amount.\n6. Delivering — for an award, work the program's trancheModel: fill the\n tranche deliverables and update trancheCompletion as each milestone ships.\n\nNever fabricate — leave a field blank rather than guess. Every write is proposal-gated for the user to review.",
17423
+ "subjectProfile": {
17424
+ "profileSlug": "contract"
17425
+ },
17426
+ "params": [
17427
+ {
17428
+ "name": "contract",
17429
+ "type": "entity",
17430
+ "profileSlug": "contract",
17431
+ "label": "Engagement",
17432
+ "required": true
17433
+ },
17434
+ {
17435
+ "name": "grantProgram",
17436
+ "type": "entity",
17437
+ "profileSlug": "grant-program",
17438
+ "label": "Grant Program",
17439
+ "required": true
17440
+ }
17441
+ ],
17442
+ "stages": [
17443
+ {
17444
+ "key": "identify",
17445
+ "name": "Identify",
17446
+ "description": "Understand the project and which of the program's submission tracks fit — before any writing.",
17447
+ "goal": "A clear project understanding and the fitting submission track; submission stage = scope."
17448
+ },
17449
+ {
17450
+ "key": "draft",
17451
+ "name": "Draft",
17452
+ "description": "Write the abstract, then the full proposal, from the client's real material.",
17453
+ "goal": "Abstract then full proposal drafted; submission stage abstract → build with body fields filled."
17454
+ },
17455
+ {
17456
+ "key": "submit",
17457
+ "name": "Submit",
17458
+ "description": "Finalize against the program deadline and submit the application.",
17459
+ "goal": "Application submitted on time; submission stage = awaiting-results."
17460
+ },
17461
+ {
17462
+ "key": "awaiting",
17463
+ "name": "Awaiting",
17464
+ "description": "Track the decision and keep the client status current.",
17465
+ "goal": "Submission status reflects where the decision stands (in-review / waiting-client / needs-document / blocked)."
17466
+ },
17467
+ {
17468
+ "key": "awarded",
17469
+ "name": "Awarded",
17470
+ "description": "Record the funder's decision and the awarded amount.",
17471
+ "goal": "Submission outcome set (awarded / rejected / partial) with the awarded amount."
17472
+ },
17473
+ {
17474
+ "key": "delivering",
17475
+ "name": "Delivering",
17476
+ "description": "Work the program's tranche model — ship and record each tranche.",
17477
+ "goal": "Tranche deliverables filled and trancheCompletion updated as milestones ship."
17478
+ }
17479
+ ],
17480
+ "executor": "is-agent",
17481
+ "grants": [
17482
+ "entity.create"
17483
+ ],
17484
+ "status": "active"
17485
+ }
17486
+ ],
17487
+ "automations": [
17488
+ {
17489
+ "key": "grant-submission-sync",
17490
+ "name": "Grant Submission Sync",
17491
+ "description": "Once a day, keep each grant application's grant_submission card current — read the submission's recent activity, assess where the pursuit stands, and advance its stage + status so the pipeline stays the durable, kanban-visible state of record.",
17492
+ "triggerType": "cron",
17493
+ "triggerConfig": {
17494
+ "expression": "0 8 * * *"
17495
+ },
17496
+ "status": "draft",
17497
+ "metadata": {
17498
+ "templateKey": "grant-submission-sync"
17499
+ },
17500
+ "flow": {
17501
+ "nodes": [
17502
+ {
17503
+ "id": "trigger",
17504
+ "type": "trigger",
17505
+ "position": {
17506
+ "x": 0,
17507
+ "y": 0
17508
+ },
17509
+ "data": {
17510
+ "label": "Daily at 08:00",
17511
+ "triggerType": "cron",
17512
+ "config": {
17513
+ "expression": "0 8 * * *"
17514
+ }
17515
+ }
17516
+ },
17517
+ {
17518
+ "id": "list-submissions",
17519
+ "type": "capability",
17520
+ "position": {
17521
+ "x": 0,
17522
+ "y": 120
17523
+ },
17524
+ "data": {
17525
+ "label": "List grant submissions to maintain",
17526
+ "verbId": "entity.query",
17527
+ "inputMapping": {
17528
+ "profileSlug": "grant_submission",
17529
+ "limit": "100"
17530
+ }
17531
+ }
17532
+ },
17533
+ {
17534
+ "id": "submissions-loop",
17535
+ "type": "loop",
17536
+ "position": {
17537
+ "x": 0,
17538
+ "y": 240
17539
+ },
17540
+ "data": {
17541
+ "label": "For each grant submission",
17542
+ "iteratorExpression": "steps.list-submissions.output.entities",
17543
+ "itemVariable": "submission"
17544
+ }
17545
+ },
17546
+ {
17547
+ "id": "read-activity",
17548
+ "type": "messages_query",
17549
+ "position": {
17550
+ "x": 0,
17551
+ "y": 360
17552
+ },
17553
+ "data": {
17554
+ "label": "Read the submission's recent activity",
17555
+ "subjectEntityId": "{{loop.item.id}}",
17556
+ "limit": 40
17557
+ }
17558
+ },
17559
+ {
17560
+ "id": "assess",
17561
+ "type": "capability",
17562
+ "position": {
17563
+ "x": 0,
17564
+ "y": 480
17565
+ },
17566
+ "data": {
17567
+ "label": "Assess the submission's stage + status → JSON",
17568
+ "verbId": "ai.generate",
17569
+ "inputMapping": {
17570
+ "json": "true",
17571
+ "maxTokens": "500",
17572
+ "system": "You are a strict data-extraction function for a grants pipeline. Read a grant application's recent activity and report where the application currently stands. Respond with STRICT JSON and NOTHING else. Never fabricate — if the activity does not clearly indicate a value, keep the submission's current one.",
17573
+ "prompt": "The grant submission: {{loop.item.title}} Current stage: {{loop.item.properties.stage}} Current status: {{loop.item.properties.status}}\nRecent activity (oldest first): {{steps.read-activity.output.messages}}\nReport the submission's current state as STRICT JSON, using ONLY these vocabularies and NOTHING else: {\n \"stage\": \"scope|abstract|build|awaiting-results\",\n \"status\": \"answered|waiting-client|needs-document|in-review|blocked\"\n}"
17574
+ }
17575
+ }
17576
+ },
17577
+ {
17578
+ "id": "advance-submission",
17579
+ "type": "output",
17580
+ "position": {
17581
+ "x": 0,
17582
+ "y": 600
17583
+ },
17584
+ "data": {
17585
+ "label": "Advance the submission stage + status",
17586
+ "outputType": "entity_update",
17587
+ "config": {
17588
+ "entityId": "{{loop.item.id}}",
17589
+ "properties": {
17590
+ "stage": "{{steps.assess.output.stage}}",
17591
+ "status": "{{steps.assess.output.status}}"
17592
+ }
17593
+ }
17594
+ }
17595
+ }
17596
+ ],
17597
+ "edges": [
17598
+ {
17599
+ "id": "gs-trigger-list",
17600
+ "source": "trigger",
17601
+ "target": "list-submissions"
17602
+ },
17603
+ {
17604
+ "id": "gs-list-loop",
17605
+ "source": "list-submissions",
17606
+ "target": "submissions-loop"
17607
+ },
17608
+ {
17609
+ "id": "gs-loop-read",
17610
+ "source": "submissions-loop",
17611
+ "target": "read-activity"
17612
+ },
17613
+ {
17614
+ "id": "gs-read-assess",
17615
+ "source": "read-activity",
17616
+ "target": "assess"
17617
+ },
17618
+ {
17619
+ "id": "gs-assess-advance",
17620
+ "source": "assess",
17621
+ "target": "advance-submission"
17622
+ }
17623
+ ]
17624
+ }
17625
+ }
17626
+ ],
16451
17627
  "app": {
16452
17628
  "kind": "stored",
16453
17629
  "cellKey": "entity-detail"
@@ -19506,7 +20682,7 @@ const DATA = {
19506
20682
  "icon": "User",
19507
20683
  "color": "#06B6D4",
19508
20684
  "description": "People in your intellectual and professional world.",
19509
- "scope": "WORKSPACE",
20685
+ "entityScope": "pod",
19510
20686
  "properties": [
19511
20687
  {
19512
20688
  "slug": "name",
@@ -23113,6 +24289,7 @@ const DATA = {
23113
24289
  "description": "A company or person that provides a capability The Arch can broker a client need against.",
23114
24290
  "scope": "shared",
23115
24291
  "profileKind": "role",
24292
+ "roleCategory": "provider",
23116
24293
  "applicableKinds": [
23117
24294
  "company",
23118
24295
  "person"
@@ -23292,10 +24469,10 @@ const DATA = {
23292
24469
  "y": 240
23293
24470
  },
23294
24471
  "data": {
23295
- "label": "List the solution-provider roster",
24472
+ "label": "List the provider roster (any roleCategory=provider)",
23296
24473
  "verbId": "entity.query",
23297
24474
  "inputMapping": {
23298
- "profileSlug": "solution-provider",
24475
+ "roleCategory": "provider",
23299
24476
  "limit": "100"
23300
24477
  }
23301
24478
  }
@@ -23843,6 +25020,24 @@ const DATA = {
23843
25020
  "targetProfileSlug": "company",
23844
25021
  "type": "briefing_about_client",
23845
25022
  "label": "Client"
25023
+ },
25024
+ {
25025
+ "sourceProfileSlug": "engagement",
25026
+ "targetProfileSlug": "company",
25027
+ "type": "engagement_for_client",
25028
+ "label": "Client"
25029
+ },
25030
+ {
25031
+ "sourceProfileSlug": "engagement",
25032
+ "targetProfileSlug": "person",
25033
+ "type": "engagement_for_client",
25034
+ "label": "Client"
25035
+ },
25036
+ {
25037
+ "sourceProfileSlug": "contract",
25038
+ "targetProfileSlug": "engagement",
25039
+ "type": "contract_for_engagement",
25040
+ "label": "Engagement"
23846
25041
  }
23847
25042
  ],
23848
25043
  "views": [
@@ -23963,20 +25158,254 @@ const DATA = {
23963
25158
  "h": 5
23964
25159
  }
23965
25160
  }
23966
- ]
23967
- },
23968
- "automations": [
25161
+ ]
25162
+ },
25163
+ "automations": [
25164
+ {
25165
+ "key": "operations-daily-client-briefing",
25166
+ "name": "Daily client briefing",
25167
+ "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 — 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.",
25168
+ "triggerType": "cron",
25169
+ "triggerConfig": {
25170
+ "expression": "0 8 * * *"
25171
+ },
25172
+ "status": "active",
25173
+ "metadata": {
25174
+ "templateKey": "operations-daily-client-briefing",
25175
+ "requiresPodWideEntityQueryLens": true
25176
+ },
25177
+ "flow": {
25178
+ "nodes": [
25179
+ {
25180
+ "id": "trigger",
25181
+ "type": "trigger",
25182
+ "position": {
25183
+ "x": 0,
25184
+ "y": 0
25185
+ },
25186
+ "data": {
25187
+ "label": "Daily at 08:00",
25188
+ "triggerType": "cron",
25189
+ "config": {
25190
+ "expression": "0 8 * * *"
25191
+ }
25192
+ }
25193
+ },
25194
+ {
25195
+ "id": "list-clients",
25196
+ "type": "capability",
25197
+ "position": {
25198
+ "x": 0,
25199
+ "y": 120
25200
+ },
25201
+ "data": {
25202
+ "label": "List clients (pod-wide `client` role)",
25203
+ "verbId": "entity.query",
25204
+ "inputMapping": {
25205
+ "profileSlug": "client",
25206
+ "scope": "pod",
25207
+ "limit": "100"
25208
+ }
25209
+ }
25210
+ },
25211
+ {
25212
+ "id": "clients-loop",
25213
+ "type": "loop",
25214
+ "position": {
25215
+ "x": 0,
25216
+ "y": 240
25217
+ },
25218
+ "data": {
25219
+ "label": "For each client",
25220
+ "iteratorExpression": "steps.list-clients.output.entities",
25221
+ "itemVariable": "client"
25222
+ }
25223
+ },
25224
+ {
25225
+ "id": "read-comms",
25226
+ "type": "messages_query",
25227
+ "position": {
25228
+ "x": 0,
25229
+ "y": 360
25230
+ },
25231
+ "data": {
25232
+ "label": "Read this client's recent comms",
25233
+ "subjectEntityId": "{{loop.item.id}}",
25234
+ "limit": 40
25235
+ }
25236
+ },
25237
+ {
25238
+ "id": "digest",
25239
+ "type": "capability",
25240
+ "position": {
25241
+ "x": 0,
25242
+ "y": 480
25243
+ },
25244
+ "data": {
25245
+ "label": "Distil the real signal → JSON",
25246
+ "verbId": "ai.generate",
25247
+ "inputMapping": {
25248
+ "json": "true",
25249
+ "maxTokens": "1200",
25250
+ "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.",
25251
+ "prompt": "Digest the recent communications for the client \"{{loop.item.title}}\" into a concise internal briefing for OUR delivery team.\nPEOPLE 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.\nSurface 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.\nRespond with STRICT JSON and NOTHING else: {\n \"hasSignal\": \"true\" or \"false\" (a STRING),\n \"headline\": \"<one-line summary; empty string when hasSignal is 'false'>\",\n \"summary\": \"<a short markdown digest for the team; empty string when 'false'>\",\n \"risks\": \"<risks/blockers as short markdown, or empty string>\",\n \"nextActions\": \"<recommended next actions as short markdown, or empty string>\"\n} Set hasSignal to \"false\" when there is no notable activity (no messages, or only chatter).\nRecent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}"
25252
+ }
25253
+ }
25254
+ },
25255
+ {
25256
+ "id": "signal-gate",
25257
+ "type": "condition",
25258
+ "position": {
25259
+ "x": 0,
25260
+ "y": 600
25261
+ },
25262
+ "data": {
25263
+ "label": "Only continue when there's real signal",
25264
+ "expression": "steps.digest.output.hasSignal === true"
25265
+ }
25266
+ },
25267
+ {
25268
+ "id": "write-briefing",
25269
+ "type": "output",
25270
+ "position": {
25271
+ "x": 0,
25272
+ "y": 720
25273
+ },
25274
+ "data": {
25275
+ "label": "Save the client briefing",
25276
+ "outputType": "entity_create",
25277
+ "config": {
25278
+ "profileSlug": "client-briefing",
25279
+ "title": "Briefing — {{loop.item.title}}",
25280
+ "dedupeBy": "briefing-key",
25281
+ "properties": {
25282
+ "briefing-key": "{{loop.item.id}}::{{trigger.payload.scheduledAt}}",
25283
+ "briefing-client": "{{loop.item.id}}",
25284
+ "briefing-date": "{{trigger.payload.scheduledAt}}",
25285
+ "briefing-headline": "{{steps.digest.output.headline}}",
25286
+ "briefing-summary": "{{steps.digest.output.summary}}",
25287
+ "briefing-risks": "{{steps.digest.output.risks}}",
25288
+ "briefing-next-actions": "{{steps.digest.output.nextActions}}",
25289
+ "briefing-message-count": "{{steps.read-comms.output.count}}"
25290
+ }
25291
+ }
25292
+ }
25293
+ },
25294
+ {
25295
+ "id": "link-briefing",
25296
+ "type": "output",
25297
+ "position": {
25298
+ "x": 0,
25299
+ "y": 840
25300
+ },
25301
+ "data": {
25302
+ "label": "Link briefing → client",
25303
+ "outputType": "relation_create",
25304
+ "config": {
25305
+ "fromEntityId": "{{steps.write-briefing.output.entityId}}",
25306
+ "toEntityId": "{{loop.item.id}}",
25307
+ "relationType": "briefing_about_client"
25308
+ }
25309
+ }
25310
+ },
25311
+ {
25312
+ "id": "resolve-team",
25313
+ "type": "capability",
25314
+ "position": {
25315
+ "x": 0,
25316
+ "y": 960
25317
+ },
25318
+ "data": {
25319
+ "label": "Resolve the client's TEAM channel (FIREWALL)",
25320
+ "verbId": "channel.resolve",
25321
+ "inputMapping": {
25322
+ "contextObjectType": "entity",
25323
+ "contextObjectId": "{{loop.item.id}}",
25324
+ "branchPurpose": "team"
25325
+ }
25326
+ }
25327
+ },
25328
+ {
25329
+ "id": "post-recap",
25330
+ "type": "output",
25331
+ "position": {
25332
+ "x": 0,
25333
+ "y": 1080
25334
+ },
25335
+ "data": {
25336
+ "label": "Post the recap to the TEAM channel (FIREWALL)",
25337
+ "outputType": "channel_message",
25338
+ "config": {
25339
+ "channelId": "{{steps.resolve-team.output.channelId}}",
25340
+ "content": "**{{loop.item.title}} — daily briefing**\n{{steps.digest.output.headline}}\n{{steps.digest.output.summary}}\n**Risks:** {{steps.digest.output.risks}}\n**Next:** {{steps.digest.output.nextActions}}",
25341
+ "metadata": {
25342
+ "automationMessage": true,
25343
+ "kind": "client-briefing"
25344
+ }
25345
+ }
25346
+ }
25347
+ }
25348
+ ],
25349
+ "edges": [
25350
+ {
25351
+ "id": "e-trigger-list",
25352
+ "source": "trigger",
25353
+ "target": "list-clients"
25354
+ },
25355
+ {
25356
+ "id": "e-list-loop",
25357
+ "source": "list-clients",
25358
+ "target": "clients-loop"
25359
+ },
25360
+ {
25361
+ "id": "e-loop-read",
25362
+ "source": "clients-loop",
25363
+ "target": "read-comms"
25364
+ },
25365
+ {
25366
+ "id": "e-read-digest",
25367
+ "source": "read-comms",
25368
+ "target": "digest"
25369
+ },
25370
+ {
25371
+ "id": "e-digest-gate",
25372
+ "source": "digest",
25373
+ "target": "signal-gate"
25374
+ },
25375
+ {
25376
+ "id": "e-gate-write",
25377
+ "source": "signal-gate",
25378
+ "target": "write-briefing"
25379
+ },
25380
+ {
25381
+ "id": "e-write-link",
25382
+ "source": "write-briefing",
25383
+ "target": "link-briefing"
25384
+ },
25385
+ {
25386
+ "id": "e-link-resolve",
25387
+ "source": "link-briefing",
25388
+ "target": "resolve-team"
25389
+ },
25390
+ {
25391
+ "id": "e-resolve-post",
25392
+ "source": "resolve-team",
25393
+ "target": "post-recap"
25394
+ }
25395
+ ]
25396
+ }
25397
+ },
23969
25398
  {
23970
- "key": "operations-daily-client-briefing",
23971
- "name": "Daily client briefing",
23972
- "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 — 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.",
25399
+ "key": "operations-crm-handoff-pickup",
25400
+ "name": "Pick up handed-off clients",
25401
+ "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 — a proposed engagement plus a draft contract, both linked to the client. Deduped per client, so it is safe to re-run.",
23973
25402
  "triggerType": "cron",
23974
25403
  "triggerConfig": {
23975
- "expression": "0 8 * * *"
25404
+ "expression": "0 7 * * *"
23976
25405
  },
23977
25406
  "status": "active",
23978
25407
  "metadata": {
23979
- "templateKey": "operations-daily-client-briefing",
25408
+ "templateKey": "operations-crm-handoff-pickup",
23980
25409
  "requiresPodWideEntityQueryLens": true
23981
25410
  },
23982
25411
  "flow": {
@@ -23989,10 +25418,10 @@ const DATA = {
23989
25418
  "y": 0
23990
25419
  },
23991
25420
  "data": {
23992
- "label": "Daily at 08:00",
25421
+ "label": "Daily at 07:00",
23993
25422
  "triggerType": "cron",
23994
25423
  "config": {
23995
- "expression": "0 8 * * *"
25424
+ "expression": "0 7 * * *"
23996
25425
  }
23997
25426
  }
23998
25427
  },
@@ -24014,7 +25443,7 @@ const DATA = {
24014
25443
  }
24015
25444
  },
24016
25445
  {
24017
- "id": "clients-loop",
25446
+ "id": "handoff-loop",
24018
25447
  "type": "loop",
24019
25448
  "position": {
24020
25449
  "x": 0,
@@ -24027,175 +25456,223 @@ const DATA = {
24027
25456
  }
24028
25457
  },
24029
25458
  {
24030
- "id": "read-comms",
24031
- "type": "messages_query",
25459
+ "id": "read-client-facets",
25460
+ "type": "capability",
24032
25461
  "position": {
24033
25462
  "x": 0,
24034
25463
  "y": 360
24035
25464
  },
24036
25465
  "data": {
24037
- "label": "Read this client's recent comms",
24038
- "subjectEntityId": "{{loop.item.id}}",
24039
- "limit": 40
25466
+ "label": "Read this party's role facets",
25467
+ "verbId": "entity_facet.list",
25468
+ "inputMapping": {
25469
+ "entityId": "{{loop.item.id}}"
25470
+ }
24040
25471
  }
24041
25472
  },
24042
25473
  {
24043
- "id": "digest",
24044
- "type": "capability",
25474
+ "id": "client-handoff-status",
25475
+ "type": "transform",
24045
25476
  "position": {
24046
25477
  "x": 0,
24047
25478
  "y": 480
24048
25479
  },
24049
25480
  "data": {
24050
- "label": "Distil the real signal → JSON",
24051
- "verbId": "ai.generate",
24052
- "inputMapping": {
24053
- "json": "true",
24054
- "maxTokens": "1200",
24055
- "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.",
24056
- "prompt": "Digest the recent communications for the client \"{{loop.item.title}}\" into a concise internal briefing for OUR delivery team.\nPEOPLE 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.\nSurface 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.\nRespond with STRICT JSON and NOTHING else: {\n \"hasSignal\": \"true\" or \"false\" (a STRING),\n \"headline\": \"<one-line summary; empty string when hasSignal is 'false'>\",\n \"summary\": \"<a short markdown digest for the team; empty string when 'false'>\",\n \"risks\": \"<risks/blockers as short markdown, or empty string>\",\n \"nextActions\": \"<recommended next actions as short markdown, or empty string>\"\n} Set hasSignal to \"false\" when there is no notable activity (no messages, or only chatter).\nRecent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}"
24057
- }
25481
+ "label": "Read the client facet's handoff status",
25482
+ "expression": "{{steps.read-client-facets.output.facets}} | filter:item.profileSlug === client | map:{{item.properties.handoffStatus}}"
24058
25483
  }
24059
25484
  },
24060
25485
  {
24061
- "id": "signal-gate",
25486
+ "id": "handoff-gate",
24062
25487
  "type": "condition",
24063
25488
  "position": {
24064
25489
  "x": 0,
24065
25490
  "y": 600
24066
25491
  },
24067
25492
  "data": {
24068
- "label": "Only continue when there's real signal",
24069
- "expression": "steps.digest.output.hasSignal === true"
25493
+ "label": "Only clients the CRM marked ready or handed off",
25494
+ "expression": "steps.client-handoff-status.output.result contains-any ready,handed_off"
24070
25495
  }
24071
25496
  },
24072
25497
  {
24073
- "id": "write-briefing",
24074
- "type": "output",
25498
+ "id": "existing-engagements",
25499
+ "type": "query",
24075
25500
  "position": {
24076
25501
  "x": 0,
24077
25502
  "y": 720
24078
25503
  },
24079
25504
  "data": {
24080
- "label": "Save the client briefing",
25505
+ "label": "Does this client already have an engagement?",
25506
+ "profileSlug": "engagement",
25507
+ "filter": "{\"engagement-client\": \"{{loop.item.id}}\"}",
25508
+ "limit": 1
25509
+ }
25510
+ },
25511
+ {
25512
+ "id": "engagement-gate",
25513
+ "type": "condition",
25514
+ "position": {
25515
+ "x": 0,
25516
+ "y": 840
25517
+ },
25518
+ "data": {
25519
+ "label": "Only clients with no engagement yet",
25520
+ "expression": "steps.existing-engagements.output.count === 0"
25521
+ }
25522
+ },
25523
+ {
25524
+ "id": "handoff-time",
25525
+ "type": "compute",
25526
+ "position": {
25527
+ "x": 0,
25528
+ "y": 900
25529
+ },
25530
+ "data": {
25531
+ "label": "Record handoff time",
25532
+ "operation": "now"
25533
+ }
25534
+ },
25535
+ {
25536
+ "id": "create-engagement",
25537
+ "type": "output",
25538
+ "position": {
25539
+ "x": 0,
25540
+ "y": 960
25541
+ },
25542
+ "data": {
25543
+ "label": "Open a proposed engagement",
24081
25544
  "outputType": "entity_create",
24082
25545
  "config": {
24083
- "profileSlug": "client-briefing",
24084
- "title": "Briefing — {{loop.item.title}}",
24085
- "dedupeBy": "briefing-key",
25546
+ "profileSlug": "engagement",
25547
+ "title": "Engagement — {{loop.item.title}}",
25548
+ "dedupeBy": "engagement-client",
24086
25549
  "properties": {
24087
- "briefing-key": "{{loop.item.id}}::{{trigger.payload.scheduledAt}}",
24088
- "briefing-client": "{{loop.item.id}}",
24089
- "briefing-date": "{{trigger.payload.scheduledAt}}",
24090
- "briefing-headline": "{{steps.digest.output.headline}}",
24091
- "briefing-summary": "{{steps.digest.output.summary}}",
24092
- "briefing-risks": "{{steps.digest.output.risks}}",
24093
- "briefing-next-actions": "{{steps.digest.output.nextActions}}",
24094
- "briefing-message-count": "{{steps.read-comms.output.count}}"
25550
+ "engagement-status": "proposed",
25551
+ "engagement-client": "{{loop.item.id}}",
25552
+ "engagement-start": "{{steps.handoff-time.output.result}}",
25553
+ "engagement-notes": "Opened automatically from the CRM handoff. Confirm scope, dates, and owner before activating."
24095
25554
  }
24096
25555
  }
24097
25556
  }
24098
25557
  },
24099
25558
  {
24100
- "id": "link-briefing",
25559
+ "id": "link-engagement",
24101
25560
  "type": "output",
24102
25561
  "position": {
24103
25562
  "x": 0,
24104
- "y": 840
25563
+ "y": 1080
24105
25564
  },
24106
25565
  "data": {
24107
- "label": "Link briefing → client",
25566
+ "label": "Link engagement → client",
24108
25567
  "outputType": "relation_create",
24109
25568
  "config": {
24110
- "fromEntityId": "{{steps.write-briefing.output.entityId}}",
25569
+ "fromEntityId": "{{steps.create-engagement.output.entityId}}",
24111
25570
  "toEntityId": "{{loop.item.id}}",
24112
- "relationType": "briefing_about_client"
25571
+ "relationType": "engagement_for_client"
24113
25572
  }
24114
25573
  }
24115
25574
  },
24116
25575
  {
24117
- "id": "resolve-team",
24118
- "type": "capability",
25576
+ "id": "create-contract",
25577
+ "type": "output",
24119
25578
  "position": {
24120
25579
  "x": 0,
24121
- "y": 960
25580
+ "y": 1200
24122
25581
  },
24123
25582
  "data": {
24124
- "label": "Resolve the client's TEAM channel (FIREWALL)",
24125
- "verbId": "channel.resolve",
24126
- "inputMapping": {
24127
- "contextObjectType": "entity",
24128
- "contextObjectId": "{{loop.item.id}}",
24129
- "branchPurpose": "team"
25583
+ "label": "Open a draft contract",
25584
+ "outputType": "entity_create",
25585
+ "config": {
25586
+ "profileSlug": "contract",
25587
+ "title": "Contract — {{loop.item.title}}",
25588
+ "dedupeBy": "contract-engagement",
25589
+ "properties": {
25590
+ "contract-status": "draft",
25591
+ "contract-engagement": "{{steps.create-engagement.output.entityId}}",
25592
+ "start-date": "{{steps.handoff-time.output.result}}",
25593
+ "contract-notes": "Drafted automatically from the CRM handoff. Attach the signed document and set the term."
25594
+ }
24130
25595
  }
24131
25596
  }
24132
25597
  },
24133
25598
  {
24134
- "id": "post-recap",
25599
+ "id": "link-contract",
24135
25600
  "type": "output",
24136
25601
  "position": {
24137
25602
  "x": 0,
24138
- "y": 1080
25603
+ "y": 1320
24139
25604
  },
24140
25605
  "data": {
24141
- "label": "Post the recap to the TEAM channel (FIREWALL)",
24142
- "outputType": "channel_message",
25606
+ "label": "Link contract → engagement",
25607
+ "outputType": "relation_create",
24143
25608
  "config": {
24144
- "channelId": "{{steps.resolve-team.output.channelId}}",
24145
- "content": "**{{loop.item.title}} — daily briefing**\n{{steps.digest.output.headline}}\n{{steps.digest.output.summary}}\n**Risks:** {{steps.digest.output.risks}}\n**Next:** {{steps.digest.output.nextActions}}",
24146
- "metadata": {
24147
- "automationMessage": true,
24148
- "kind": "client-briefing"
24149
- }
25609
+ "fromEntityId": "{{steps.create-contract.output.entityId}}",
25610
+ "toEntityId": "{{steps.create-engagement.output.entityId}}",
25611
+ "relationType": "contract_for_engagement"
24150
25612
  }
24151
25613
  }
24152
25614
  }
24153
25615
  ],
24154
25616
  "edges": [
24155
25617
  {
24156
- "id": "e-trigger-list",
25618
+ "id": "h-trigger-list",
24157
25619
  "source": "trigger",
24158
25620
  "target": "list-clients"
24159
25621
  },
24160
25622
  {
24161
- "id": "e-list-loop",
25623
+ "id": "h-list-time",
24162
25624
  "source": "list-clients",
24163
- "target": "clients-loop"
25625
+ "target": "handoff-time"
24164
25626
  },
24165
25627
  {
24166
- "id": "e-loop-read",
24167
- "source": "clients-loop",
24168
- "target": "read-comms"
25628
+ "id": "h-time-loop",
25629
+ "source": "handoff-time",
25630
+ "target": "handoff-loop"
24169
25631
  },
24170
25632
  {
24171
- "id": "e-read-digest",
24172
- "source": "read-comms",
24173
- "target": "digest"
25633
+ "id": "h-loop-facets",
25634
+ "source": "handoff-loop",
25635
+ "target": "read-client-facets"
24174
25636
  },
24175
25637
  {
24176
- "id": "e-digest-gate",
24177
- "source": "digest",
24178
- "target": "signal-gate"
25638
+ "id": "h-facets-status",
25639
+ "source": "read-client-facets",
25640
+ "target": "client-handoff-status"
24179
25641
  },
24180
25642
  {
24181
- "id": "e-gate-write",
24182
- "source": "signal-gate",
24183
- "target": "write-briefing"
25643
+ "id": "h-status-gate",
25644
+ "source": "client-handoff-status",
25645
+ "target": "handoff-gate"
24184
25646
  },
24185
25647
  {
24186
- "id": "e-write-link",
24187
- "source": "write-briefing",
24188
- "target": "link-briefing"
25648
+ "id": "h-gate-existing",
25649
+ "source": "handoff-gate",
25650
+ "target": "existing-engagements"
24189
25651
  },
24190
25652
  {
24191
- "id": "e-link-resolve",
24192
- "source": "link-briefing",
24193
- "target": "resolve-team"
25653
+ "id": "h-existing-gate",
25654
+ "source": "existing-engagements",
25655
+ "target": "engagement-gate"
24194
25656
  },
24195
25657
  {
24196
- "id": "e-resolve-post",
24197
- "source": "resolve-team",
24198
- "target": "post-recap"
25658
+ "id": "h-gate-engagement",
25659
+ "source": "engagement-gate",
25660
+ "target": "create-engagement"
25661
+ },
25662
+ {
25663
+ "id": "h-engagement-link",
25664
+ "source": "create-engagement",
25665
+ "target": "link-engagement"
25666
+ },
25667
+ {
25668
+ "id": "h-link-contract",
25669
+ "source": "link-engagement",
25670
+ "target": "create-contract"
25671
+ },
25672
+ {
25673
+ "id": "h-contract-link",
25674
+ "source": "create-contract",
25675
+ "target": "link-contract"
24199
25676
  }
24200
25677
  ]
24201
25678
  }
@@ -24509,6 +25986,164 @@ const DATA = {
24509
25986
  "status": "active"
24510
25987
  }
24511
25988
  ],
25989
+ "automations": [
25990
+ {
25991
+ "key": "linkedin-conversation-ingest",
25992
+ "name": "LinkedIn conversation ingest",
25993
+ "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.",
25994
+ "triggerType": "cron",
25995
+ "triggerConfig": {
25996
+ "expression": "0 6 * * *"
25997
+ },
25998
+ "status": "draft",
25999
+ "flow": {
26000
+ "nodes": [
26001
+ {
26002
+ "id": "trigger",
26003
+ "type": "trigger",
26004
+ "position": {
26005
+ "x": 0,
26006
+ "y": 0
26007
+ },
26008
+ "data": {
26009
+ "label": "Daily 06:00 reconciliation (also fired manually for backfill)",
26010
+ "triggerType": "cron",
26011
+ "config": {
26012
+ "expression": "0 6 * * *"
26013
+ }
26014
+ }
26015
+ },
26016
+ {
26017
+ "id": "list-accounts",
26018
+ "type": "capability",
26019
+ "position": {
26020
+ "x": 0,
26021
+ "y": 120
26022
+ },
26023
+ "data": {
26024
+ "label": "Resolve the connected Unipile account",
26025
+ "verbId": "unipile_list_accounts"
26026
+ }
26027
+ },
26028
+ {
26029
+ "id": "filter-linkedin-account",
26030
+ "type": "transform",
26031
+ "position": {
26032
+ "x": 0,
26033
+ "y": 180
26034
+ },
26035
+ "data": {
26036
+ "label": "Keep only LinkedIn Unipile accounts",
26037
+ "expression": "{{steps.list-accounts.output.accounts}} | filter:item.type == 'LINKEDIN'"
26038
+ }
26039
+ },
26040
+ {
26041
+ "id": "list-conversations",
26042
+ "type": "capability",
26043
+ "position": {
26044
+ "x": 0,
26045
+ "y": 240
26046
+ },
26047
+ "data": {
26048
+ "label": "List LinkedIn conversations for the account",
26049
+ "verbId": "unipile_list_conversations",
26050
+ "inputMapping": {
26051
+ "accountId": "{{steps.filter-linkedin-account.output.result.0.id}}"
26052
+ }
26053
+ }
26054
+ },
26055
+ {
26056
+ "id": "conversations-loop",
26057
+ "type": "loop",
26058
+ "position": {
26059
+ "x": 0,
26060
+ "y": 360
26061
+ },
26062
+ "data": {
26063
+ "label": "For each conversation",
26064
+ "iteratorExpression": "steps.list-conversations.output.conversations",
26065
+ "itemVariable": "conversation"
26066
+ }
26067
+ },
26068
+ {
26069
+ "id": "list-messages",
26070
+ "type": "capability",
26071
+ "position": {
26072
+ "x": 0,
26073
+ "y": 480
26074
+ },
26075
+ "data": {
26076
+ "label": "List this thread's messages",
26077
+ "verbId": "unipile_list_messages",
26078
+ "inputMapping": {
26079
+ "threadId": "{{loop.item.id}}",
26080
+ "accountId": "{{steps.filter-linkedin-account.output.result.0.id}}"
26081
+ }
26082
+ }
26083
+ },
26084
+ {
26085
+ "id": "ingest",
26086
+ "type": "capability",
26087
+ "position": {
26088
+ "x": 0,
26089
+ "y": 600
26090
+ },
26091
+ "data": {
26092
+ "label": "Ingest thread + messages (idempotent, generic)",
26093
+ "verbId": "channel.ingest",
26094
+ "inputMapping": {
26095
+ "provider": "linkedin",
26096
+ "externalId": "{{loop.item.id}}",
26097
+ "title": "{{loop.item.name}}",
26098
+ "accountExternalId": "{{steps.filter-linkedin-account.output.result.0.id}}",
26099
+ "workspaceId": "499328b7-fd35-4143-b1bc-67b9e45d9b4b",
26100
+ "suppressSideEffects": true,
26101
+ "messages": "{{steps.list-messages.output.messages}}",
26102
+ "messageMap": {
26103
+ "text": "text",
26104
+ "id": "id",
26105
+ "sentAt": "timestamp",
26106
+ "isOutbound": "is_sender"
26107
+ }
26108
+ }
26109
+ }
26110
+ }
26111
+ ],
26112
+ "edges": [
26113
+ {
26114
+ "id": "e-trigger-accounts",
26115
+ "source": "trigger",
26116
+ "target": "list-accounts"
26117
+ },
26118
+ {
26119
+ "id": "e-accounts-filter",
26120
+ "source": "list-accounts",
26121
+ "target": "filter-linkedin-account"
26122
+ },
26123
+ {
26124
+ "id": "e-filter-convos",
26125
+ "source": "filter-linkedin-account",
26126
+ "target": "list-conversations"
26127
+ },
26128
+ {
26129
+ "id": "e-convos-loop",
26130
+ "source": "list-conversations",
26131
+ "target": "conversations-loop"
26132
+ },
26133
+ {
26134
+ "id": "e-loop-messages",
26135
+ "source": "conversations-loop",
26136
+ "target": "list-messages"
26137
+ },
26138
+ {
26139
+ "id": "e-messages-ingest",
26140
+ "source": "list-messages",
26141
+ "target": "ingest"
26142
+ }
26143
+ ]
26144
+ }
26145
+ }
26146
+ ],
24512
26147
  "integrations": [
24513
26148
  {
24514
26149
  "templateKey": "unipile-linkedin"
@@ -25074,7 +26709,7 @@ const DATA = {
25074
26709
  "icon": "user",
25075
26710
  "color": "#0891B2",
25076
26711
  "description": "Team member or stakeholder",
25077
- "scope": "WORKSPACE",
26712
+ "entityScope": "pod",
25078
26713
  "properties": [
25079
26714
  {
25080
26715
  "slug": "person-role",
@@ -28350,6 +29985,12 @@ const DATA = {
28350
29985
  "kind": "workspace",
28351
29986
  "relation": "require",
28352
29987
  "reason": "The engineering / ops dashboard — the build-side view of the company."
29988
+ },
29989
+ {
29990
+ "slug": "networking",
29991
+ "kind": "workspace",
29992
+ "relation": "require",
29993
+ "reason": "The partner-intro layer — need → intro-matcher → intro → billed_as → deal, with the A↔B confidentiality firewall; the Stellar Client-Sync emits needs that depend on it."
28353
29994
  }
28354
29995
  ],
28355
29996
  "workspace": {