afv-library/skills/customizing-pre-work-brief/SKILL.md
Nitasha Walia cc7c475b01 feat: add Pre-Work Brief skills (base + customization)
Adds two complementary skills for setting up and customizing the
Field Service Mobile Pre-Work Brief feature.

skills/setting-up-pre-work-brief/

End-to-end setup of Pre-Work Brief on a Salesforce org with the
Einstein for Field Service add-on. Detects provisioning state, enables
Lightning Data Service, deploys the prompt template, assigns permission
sets, adds the PreWorkBriefPromptTemplate field to the Work Order
layout, and creates a fresh test Work Order + Service Appointment so
the admin can validate on-device immediately. Verified end-to-end
against an Einstein for Field Service trial org, May 2026.

skills/customizing-pre-work-brief/

Generator-pattern skill for vertical-specific Pre-Work Briefs deployable
as code. The admin provides industry, website, and a sentence about
what their technicians do; the skill produces custom objects, an
autolaunched PromptFlow, a sibling prompt template, a permission set,
and a test Work Order. Loads vertical-specific seed templates from
templates/<industry>.md.

Five seeds ship:
  - hvac.md (verified end-to-end on a trial org, May 2026)
  - banking.md, telecom.md, healthcare.md, retail-merchandising.md
    (scaffolds; ready for the next admin in those verticals to verify)

Each seed contributes recommended custom objects, a section structure
for the prompt template, vertical-specific rules, and a cadence example.
For verticals not in the library, --industry other synthesizes a draft
seed at runtime and writes it back to templates/ for next time.

The two skills compose: base setup gets PWB working at all on the
managed flow; customizing builds a parallel customer-specific bundle.
2026-05-20 09:53:01 -07:00

27 KiB

name description allowed-tools license metadata
customizing-pre-work-brief Generate a vertical-specific Pre-Work Brief on Field Service Mobile, deployable as code. The admin provides industry, website, and a sentence describing what their technicians do; this skill produces custom objects, an autolaunched flow, a prompt template, a permission set, and a test record. TRIGGER when: user wants Pre-Work Brief tailored to their company or industry; user wants the brief to reference custom objects or fields specific to their business; user mentions website-grounded or vertical-specific briefs; user wants a second 'customized' brief alongside the default; user asks to clone or extend the managed Field Service flow; user references a specific vertical (HVAC, banking, telecom, healthcare, retail merchandising). DO NOT TRIGGER when: org has not yet completed base setup (use setting-up-pre-work-brief first); user wants Voice to Form; user wants Post-Work Summary; user wants a brand-new prompt template type from scratch (use Prompt Builder docs). Bash Read Write Edit Glob Grep WebFetch Apache-2.0
version last_updated argument-hint compatibility
0.2.0 2026-05-20 <org-alias> --company <name> --industry <hvac|banking|telecom|healthcare|retail-merchandising|other> --website <url> [--description <text>] [--service-resource <id>] claude-code

Customizing Pre-Work Brief

Generate a vertical-specific Pre-Work Brief that grounds on the customer's business and the data model their technicians actually work with. The skill produces a deployable bundle — custom objects, autolaunched flow, prompt template, permission set, plus a test Work Order — so an admin goes from "the default brief works but feels generic" to "the brief reads like someone who knows our business wrote it" in one run.

This skill is the second half of a two-step adoption journey. Step one — setting-up-pre-work-brief — gets PWB working at all on the managed flow. Step two — this skill — replaces the managed flow with a customer-specific bundle deployable entirely from metadata. No Save-As click in Flow Builder is required; the only manual step is one click to activate the prompt template, consistent with the base setup pattern.

The skill is vertical-agnostic by design. It ships with seed templates for common Field Service archetypes (HVAC, banking, telecom, healthcare, retail merchandising) at templates/<industry>.md. The seed contributes a section structure, recommended custom objects, and grounding patterns. For verticals not in the library, the skill synthesizes from the website + admin description and adds the result back into templates/ for next time.


Prerequisites

The base setup must be complete. Run setting-up-pre-work-brief against the same org first if any of the checks below fail:

ORG_ALIAS="${1:-}"
[ -z "$ORG_ALIAS" ] && { echo "Usage: customizing-pre-work-brief <org-alias> --company <name> --industry <vertical> --website <url>"; exit 1; }

echo "Checking base setup is in place..."

sf org list metadata --metadata-type GenAiPromptTemplate --target-org "$ORG_ALIAS" --json | \
  jq -r '[.result[]? | select(.fullName == "Pre_Work_Brief")] | length as $n |
    if $n > 0 then "✓ Default Pre_Work_Brief template deployed" else "✗ Default template missing — run setting-up-pre-work-brief first" end'

sf data query --target-org "$ORG_ALIAS" \
  --query "SELECT DeveloperName FROM PermissionSetLicense WHERE DeveloperName = 'EinsteinFieldServicePsl' AND TotalLicenses > 0" --json | \
  jq -r '.result.totalSize as $n | if $n > 0 then "✓ Einstein for Field Service PSL present" else "✗ PSL missing — run setting-up-pre-work-brief first" end'

sf sobject describe --sobject WorkOrder --target-org "$ORG_ALIAS" --json | \
  jq -r '[.result.fields[]? | select(.name == "PreWorkBriefPromptTemplate")] | length as $n |
    if $n > 0 then "✓ PreWorkBriefPromptTemplate field present" else "✗ Field missing — run setting-up-pre-work-brief first" end'

All three must report before continuing.


Args

Arg Required Default Purpose
<org-alias> yes Target org.
--company <name> yes Company name. Used in the prompt template label and in business context generation.
--industry <vertical> yes One of: hvac, banking, telecom, healthcare, retail-merchandising, or other. Picks the seed template at templates/<industry>.md. If other, the skill synthesizes a template at runtime.
--website <url> yes Public website URL. The skill fetches the homepage to derive technician context.
--description <text> no (admin prompted) 1-2 sentence description of what the company's technicians actually do on-site. If omitted, the skill asks.
--service-resource <id> no (admin prompted) ServiceResource Id to assign the test Service Appointment to.
--customized-template-name <name> no PreWorkBrief_<Industry>_<Company> Developer name of the new prompt template. Increment if running multiple times for the same org.

Parse args:

COMPANY=""
INDUSTRY=""
WEBSITE=""
DESCRIPTION=""
SERVICE_RESOURCE_ID=""
TEMPLATE_NAME=""

while [ $# -gt 0 ]; do
  case "$1" in
    --company) COMPANY="$2"; shift 2 ;;
    --industry) INDUSTRY="$2"; shift 2 ;;
    --website) WEBSITE="$2"; shift 2 ;;
    --description) DESCRIPTION="$2"; shift 2 ;;
    --service-resource) SERVICE_RESOURCE_ID="$2"; shift 2 ;;
    --customized-template-name) TEMPLATE_NAME="$2"; shift 2 ;;
    *) shift ;;
  esac
done

[ -z "$COMPANY" ] && { echo "--company is required"; exit 1; }
[ -z "$INDUSTRY" ] && { echo "--industry is required (hvac|banking|telecom|healthcare|retail-merchandising|other)"; exit 1; }
[ -z "$WEBSITE" ] && { echo "--website is required"; exit 1; }

# Default template name
if [ -z "$TEMPLATE_NAME" ]; then
  CLEAN_COMPANY=$(echo "$COMPANY" | tr -cd '[:alnum:]_')
  CLEAN_INDUSTRY=$(echo "$INDUSTRY" | tr '[:lower:]' '[:upper:]' | head -c 1)$(echo "$INDUSTRY" | tail -c +2)
  TEMPLATE_NAME="PreWorkBrief_${CLEAN_INDUSTRY}_${CLEAN_COMPANY}"
fi

Step 1: Build the business context

Combine the company website with the admin's description into a short technician-facing brief.

1a. Fetch the website.

Use Claude's WebFetch tool against $WEBSITE with the prompt:

Read this company's homepage and any obvious sub-pages. Return a 5-bullet summary of (1) what the company does, (2) the products or services they sell, (3) who their typical customer is, (4) what kind of work a field technician would perform on-site for them, (5) any specialized equipment, certifications, or terminology a technician would need to know. Keep it factual and pulled directly from the page. Do not invent details.

Capture the response as $WEBSITE_SUMMARY.

1b. Get the admin's description.

If --description was passed, use it directly. Otherwise ask:

"In 1-2 sentences, what do your technicians actually do on-site for this business?"

Capture as $ADMIN_DESCRIPTION.

1c. Synthesize.

Combine $WEBSITE_SUMMARY and $ADMIN_DESCRIPTION into a single 3-5 sentence paragraph saved as $BUSINESS_CONTEXT. Show the result to the admin and ask for sign-off before continuing.


Step 2: Load the vertical seed template

SEED_PATH="templates/${INDUSTRY}.md"

if [ -f "$SEED_PATH" ]; then
    echo "✓ Found seed template at $SEED_PATH"
    SEED=$(cat "$SEED_PATH")
else
    echo "No seed for industry '$INDUSTRY'. Synthesizing one from business context."
    INDUSTRY="other"
    SEED=""
fi

The seed contributes:

  • Section structure for the prompt template (e.g., HVAC = Mission and contact / Customer and SLA / Site access and certifications / Equipment and refrigerant context).
  • Recommended custom objects with the fields each typically carries.
  • Standard objects + fields to query in the flow.
  • Cadence example to anchor the model's tone.

If the seed doesn't exist, the skill writes one at the end of the run so the next admin in the same vertical gets it.


Step 3: Audit the org's existing schema

Some of what the seed proposes may already exist in the org. Audit before creating duplicates.

GROUNDING_OBJECTS="Account Asset Contact Case WorkOrder WorkOrderLineItem ServiceAppointment WorkPlan WorkStep"
EXCLUDE_NAMESPACES="FSL__,FSSK__,SDO__,FSLDemoTools__"

for obj in $GROUNDING_OBJECTS; do
    echo ""
    echo "Custom fields on $obj:"
    sf sobject describe --sobject "$obj" --target-org "$ORG_ALIAS" --json 2>/dev/null | \
      jq -r --arg obj "$obj" --arg exclude "$EXCLUDE_NAMESPACES" '
        ($exclude | split(",")) as $ns_list |
        .result.fields[]?
        | select(.name | endswith("__c"))
        | . as $f
        | select(any($ns_list[]; . as $ns | $f.name | startswith($ns)) | not)
        | "  \($obj).\(.name) | type=\(.type) | label=\(.label)"
      '
done > /tmp/pwb-existing-customs.txt

# Check whether the seed's recommended custom objects already exist
sf sobject list --target-org "$ORG_ALIAS" --sobject-type custom --json | \
  jq -r '.result[]?' > /tmp/pwb-existing-objects.txt

cat /tmp/pwb-existing-customs.txt
echo ""
echo "Existing custom objects: $(wc -l < /tmp/pwb-existing-objects.txt)"

Cross-reference against the seed's recommended objects. For each recommended object:

  • Already exists → skip object creation, audit its fields.
  • Doesn't exist → propose to admin for creation.

Show the diff to the admin and ask for confirmation before deploying anything.


Step 4: Generate and deploy custom objects

For each approved custom object, write metadata files. The general shape (using HVAC's Refrigerant_Log__c as an illustration; the actual fields come from the seed):

<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
    <deploymentStatus>Deployed</deploymentStatus>
    <description>{seed-supplied description}</description>
    <enableActivities>true</enableActivities>
    <enableHistory>true</enableHistory>
    <enableReports>true</enableReports>
    <enableSearch>true</enableSearch>
    <label>{seed-supplied label}</label>
    <nameField>
        <label>{Object} Number</label>
        <type>AutoNumber</type>
        <displayFormat>{prefix}-{0000}</displayFormat>
        <startingNumber>1</startingNumber>
    </nameField>
    <pluralLabel>{seed-supplied plural label}</pluralLabel>
    <sharingModel>ReadWrite</sharingModel>
</CustomObject>

Lookup field rules to avoid the "must specify either cascade delete or restrict delete" deploy error:

<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Asset__c</fullName>
    <deleteConstraint>SetNull</deleteConstraint>
    <label>Asset</label>
    <referenceTo>Asset</referenceTo>
    <relationshipLabel>Refrigerant Logs</relationshipLabel>
    <relationshipName>Refrigerant_Logs</relationshipName>
    <type>Lookup</type>
</CustomField>

Note: do not set <required>true</required> on lookups — pair it with <deleteConstraint> or the deploy fails. For the customs the brief queries, optional lookups are fine.

If any new lookup goes onto WorkOrder (e.g., WorkOrder.Maintenance_Contract__c), write that field too — the flow will reference it via $Input.WorkOrder.<New_Field>__c.

Deploy:

mkdir -p /tmp/pwb-custom-build/force-app/main/default
cat > /tmp/pwb-custom-build/sfdx-project.json <<'EOF'
{"packageDirectories":[{"path":"force-app","default":true}],"namespace":"","sourceApiVersion":"65.0"}
EOF

# (Write all object + field XMLs into /tmp/pwb-custom-build/force-app/main/default/objects/...)

cd /tmp/pwb-custom-build
sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app --wait 30 --api-version 65.0

Step 5: Generate the from-scratch flow

Write a PromptFlow flow that grounds on the relevant standard objects + the new customs. The pattern (verified working against the afvuser trial org, 2026-05-20):

  • <processType>PromptFlow</processType>
  • <apiVersion>65.0</apiVersion> (the managed flow's getCustomerSignalsInsights action requires v65+; from-scratch flows that don't use it can stay lower, but 65 is the safe default)
  • <start> block has <triggerType>Capability</triggerType> and a <capabilityTypes> element declaring PromptTemplateType://einstein_gpt__fieldServicePreWorkBrief with a WorkOrder SObject input
  • A chain of <recordLookups> elements with getFirstRecordOnly=true (single-record references resolve cleanly; collections require Loops which complicate the demo)
  • An <assignments> element with <elementSubtype>AddPromptInstructions</elementSubtype> that builds $Output.Prompt from grounded references

Skeleton:

<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>65.0</apiVersion>
    <description>{Industry}-specific Pre-Work Brief grounding flow for {Company}.</description>
    <interviewLabel>Pre-Work Brief {Industry} {!$Flow.CurrentDateTime}</interviewLabel>
    <label>Pre-Work Brief {Industry} Custom</label>
    <processType>PromptFlow</processType>
    <status>Active</status>
    <start>
        <locationX>0</locationX>
        <locationY>0</locationY>
        <capabilityTypes>
            <name>PromptTemplateType://einstein_gpt__fieldServicePreWorkBrief</name>
            <capabilityName>PromptTemplateType://einstein_gpt__fieldServicePreWorkBrief</capabilityName>
            <inputs>
                <name>WorkOrder</name>
                <capabilityInputName>WorkOrder</capabilityInputName>
                <dataType>SOBJECT://WorkOrder</dataType>
                <isCollection>false</isCollection>
            </inputs>
        </capabilityTypes>
        <connector>
            <targetReference>{first lookup}</targetReference>
        </connector>
        <triggerType>Capability</triggerType>
    </start>
    <!-- recordLookups, then a single assignments that builds $Output.Prompt -->
</Flow>

The seed at templates/<industry>.md enumerates which lookups to chain, what each queries, and how $Output.Prompt is structured.

Deploy:

cd /tmp/pwb-custom-build
sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app/main/default/flows --wait 30 --api-version 65.0

Activate the flow (deploying with <status>Active</status> is necessary but not sufficient — Salesforce treats it as draft until explicitly activated):

FD_ID=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \
  --query "SELECT Id FROM FlowDefinition WHERE DeveloperName = '${TEMPLATE_NAME}_Flow'" --json | \
  jq -r '.result.records[0].Id')

LATEST_VERSION=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \
  --query "SELECT VersionNumber FROM Flow WHERE Definition.DeveloperName = '${TEMPLATE_NAME}_Flow' ORDER BY VersionNumber DESC LIMIT 1" --json | \
  jq -r '.result.records[0].VersionNumber')

cat > /tmp/activate-flow.json <<EOF
{"Metadata": {"activeVersionNumber": $LATEST_VERSION}}
EOF
sf api request rest --target-org "$ORG_ALIAS" --method PATCH \
  "/services/data/v65.0/tooling/sobjects/FlowDefinition/$FD_ID" \
  --body @/tmp/activate-flow.json

Step 6: Generate the prompt template

Build a GenAiPromptTemplate with three sections from the seed: business-context paragraph, flow reference, instruction block (sections + rules + cadence example). The flow reference is the single line:

{!$Flow:<flow-developer-name>.Prompt}

Skeleton:

<?xml version="1.0" encoding="UTF-8"?>
<GenAiPromptTemplate xmlns="http://soap.sforce.com/2006/04/metadata">
    <developerName>{TEMPLATE_NAME}</developerName>
    <masterLabel>Pre-Work Brief — {Company} ({Industry})</masterLabel>
    <templateVersions>
        <content>You are briefing a {Industry} field technician on the job they are about to perform on-site. Address the technician directly, in the second person.

Business context (do not contradict):
{BUSINESS_CONTEXT}

Specific job grounding:
{!$Flow:{flow-developer-name}.Prompt}

{INSTRUCTION_BLOCK from seed — sections + rules + cadence example}
</content>
        <inputs>
            <apiName>WorkOrder</apiName>
            <definition>SOBJECT://WorkOrder</definition>
            <referenceName>Input:WorkOrder</referenceName>
            <required>true</required>
        </inputs>
        <primaryModel>sfdc_ai__DefaultOpenAIGPT4OmniMini</primaryModel>
        <status>Published</status>
        <templateDataProviders>
            <definition>flow://{flow-developer-name}</definition>
            <parameters>
                <definition>SOBJECT://WorkOrder</definition>
                <isRequired>true</isRequired>
                <parameterName>WorkOrder</parameterName>
                <valueExpression>{!$Input:WorkOrder}</valueExpression>
            </parameters>
            <referenceName>Flow:{flow-developer-name}</referenceName>
        </templateDataProviders>
    </templateVersions>
    <type>einstein_gpt__fieldServicePreWorkBrief</type>
    <visibility>Global</visibility>
</GenAiPromptTemplate>

Deploy and surface the activation deeplink:

cd /tmp/pwb-custom-build
sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app/main/default/genAiPromptTemplates --wait 30 --api-version 65.0

# Resolve the template Id
TEMPLATE_ID=$(sf api request rest "/services/data/v62.0/einstein/prompt-templates?pageSize=200" --target-org "$ORG_ALIAS" 2>/dev/null | \
  python3 -c "
import sys, json, os
d = json.loads(sys.stdin.read())
name = os.environ.get('TEMPLATE_NAME')
for t in d.get('promptRecords', []):
    if t.get('fields', {}).get('DeveloperName', {}).get('value') == name:
        print(t.get('fields', {}).get('Id', {}).get('value'))
        break")

# Fall back to deploy report if runtime catalog hasn't picked it up yet
if [ -z "$TEMPLATE_ID" ]; then
  for did in $(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \
      --query "SELECT Id FROM DeployRequest WHERE NumberComponentsDeployed > 0 ORDER BY CompletedDate DESC LIMIT 10" --json | \
      jq -r '.result.records[]?.Id'); do
    cand=$(sf project deploy report --target-org "$ORG_ALIAS" --job-id "$did" --json 2>/dev/null | \
      jq -r ".result.details.componentSuccesses[]? | select(.componentType == \"GenAiPromptTemplate\" and .fullName == \"$TEMPLATE_NAME\") | .id" | head -1)
    [ -n "$cand" ] && TEMPLATE_ID="$cand" && break
  done
fi

echo ""
echo "  ⚠ Action required: click Activate in Prompt Builder."
echo "  The template is deployed and Published, but until activated, it"
echo "  won't appear in the runtime catalog. The mobile app will fail with"
echo "  'We hit a snag' until activation is complete."
echo ""
echo "  Generating sign-in link to the template (valid ~15 minutes):"
sf org open --target-org "$ORG_ALIAS" \
  --path "/lightning/setup/EinsteinPromptStudio/$TEMPLATE_ID/edit" \
  --url-only 2>&1 | grep -oE 'https://[^[:cntrl:][:space:]]+frontdoor[^[:cntrl:][:space:]]+' | head -1 | sed 's/\x1b\[[0-9;]*m//g'

Pause until the admin confirms activation. Verify:

sf api request rest "/services/data/v62.0/einstein/prompt-templates?pageSize=200" --target-org "$ORG_ALIAS" 2>/dev/null | \
  python3 -c "
import sys, json, os
d = json.loads(sys.stdin.read())
name = os.environ.get('TEMPLATE_NAME')
hit = [t for t in d.get('promptRecords', []) if t.get('fields', {}).get('DeveloperName', {}).get('value') == name]
print('✓ Active in runtime catalog' if hit else '✗ Not yet active — click Activate in Prompt Builder')"

Why a click is still required. Salesforce does not currently expose a programmatic activation path for GenAiPromptTemplate. We tried Tooling REST PATCH on IsActive, Connect API /activate endpoints across v62-v66, Apex ConnectApi.EinsteinLLM methods, and metadata activeVersionNumber — none work for GenAiPromptTemplate (the activeVersionNumber PATCH does work for Flow, which is why Step 5 can activate the flow programmatically). The skill follows the same one-click activation pattern the base setting-up-pre-work-brief skill uses for consistency.


Step 7: Generate the permission set

Bundle CRUD on the new objects, FLS on the new fields (including any new WorkOrder lookups), and assign to admin + technician:

<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
    <description>Read/Edit on {Industry} custom objects + new fields used by the {Industry} Pre-Work Brief flow for {Company}.</description>
    <hasActivationRequired>false</hasActivationRequired>
    <label>Pre-Work Brief {Industry} Access ({Company})</label>
    <objectPermissions>
        <allowCreate>true</allowCreate>
        <allowDelete>true</allowDelete>
        <allowEdit>true</allowEdit>
        <allowRead>true</allowRead>
        <modifyAllRecords>false</modifyAllRecords>
        <object>{Custom_Object__c}</object>
        <viewAllRecords>true</viewAllRecords>
    </objectPermissions>
    <!-- Repeat objectPermissions for each new custom object -->
    <fieldPermissions><editable>true</editable><field>{Object}.{Field__c}</field><readable>true</readable></fieldPermissions>
    <!-- Repeat fieldPermissions for each new field -->
</PermissionSet>

Deploy + assign:

cd /tmp/pwb-custom-build
sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app/main/default/permissionsets --wait 30 --api-version 65.0

PERMSET_NAME="PreWorkBrief_${INDUSTRY}_Access"
sf org assign permset --name "$PERMSET_NAME" --target-org "$ORG_ALIAS"
sf org assign permset --name "$PERMSET_NAME" --on-behalf-of "$TECH_USERNAME" --target-org "$ORG_ALIAS"

Asset.ProductDescription gotcha. Verified during the HVAC dry-run on afvuser (2026-05-19): even with full Asset CRUD granted, ProductDescription is read-only for non-admin profiles in some org configurations. If the seed proposes populating it, fall back to writing the same content into Asset.Description instead.


Step 8: Create a fresh test Work Order with seed data

Mirrors the setting-up-pre-work-brief Step 8-fresh pattern. Resolve Service Resource + Service Territory dynamically; create Account → Asset → custom-object records → Contact → WO → SA → AssignedResource as a chain so the brief has real data to ground on.

The seed contributes example values for the test record (e.g., HVAC seed populates a 10-ton rooftop unit, Platinum SLA contract, R-410A refrigerant log at 14.2 lbs).

After creation, hand off to the admin:

The {Industry} Pre-Work Brief is wired up. Open the Field Service mobile app, sign in as $TECH_USERNAME, navigate to today's schedule. The new Work Order $WO_NUMBER should appear. Open it; the Pre-Work Brief should render in the Overview tab and reference {Company}'s business context plus the custom-object data this skill just created.


Adding a new vertical seed

If you ran with --industry other and want to contribute the synthesized template back, the skill writes a draft to templates/<your-industry>.md at the end of the run. Review the draft, edit as needed, and commit. The next admin in your industry skips the synthesis step.

A seed should contain:

Section What it specifies
Business archetype One-sentence description of the vertical's typical customer + technician work
Recommended custom objects 2-4 objects with field lists. Include why each matters (compliance, SLA, asset history).
Standard objects + fields to query Which standard fields the flow's Get-record elements should pull (e.g., ServiceAppointment.ArrivalWindowStartTime, Contact.Email)
Prompt template section structure The 3-5 sections the brief should contain (e.g., HVAC = Mission and contact / Customer and SLA / Site access and certifications / Equipment and refrigerant context)
Rules Vertical-specific rules (e.g., HVAC = "if last refrigerant service was >12 months ago, call it out")
Cadence example Tone-anchoring example paragraph using the section structure
Test data sample Realistic values for the test WO + seed records the skill creates in Step 8

See templates/hvac.md as the canonical example.


Idempotency

Re-running this skill against the same org with the same --customized-template-name:

  • Re-fetches the website and re-prompts for description (cheap; admins re-confirm).
  • Re-audits org schema; new customs may have appeared since last run.
  • Skips object creation if Step 4 finds the customs already exist.
  • Re-deploys the flow as a new version (Salesforce treats identical metadata as no-op).
  • Re-deploys the prompt template (same; identical metadata is a no-op).
  • Creates a new test WO + SA every run.

For a second vertical in the same org, pass a different --industry and --customized-template-name. The skill produces a parallel pipeline (objects + flow + permset + template + test WO) without touching the first.


  • setting-up-pre-work-brief — required prerequisite. Runs the base setup that this skill builds on.
  • generating-flow — useful when the from-scratch flow needs deeper edits than this skill performs (e.g., complex Loops over collections, Decision elements). Open the deployed flow in Flow Builder for visual review.
  • generating-permission-set — for orgs that already have a permission set pattern to extend rather than create anew.

References

  • Pre-Work Brief setup: https://help.salesforce.com/s/articleView?id=service.mfs_einstein_pre_work_brief.htm
  • Pre-Work Brief data model: https://help.salesforce.com/s/articleView?id=service.mfs_einstein_pre_work_brief_data.htm
  • Prompt Builder: https://help.salesforce.com/s/articleView?id=ai.prompt_builder_about.htm
  • Flow Reference (PromptFlow processType): https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_visual_workflow.htm

Known Limitations

  • Programmatic activation of GenAiPromptTemplate is not exposed in the API today. Step 6's deeplink is the supported path until Salesforce ships an Activate REST endpoint. The skill follows the same one-click pattern as setting-up-pre-work-brief for consistency.
  • Flows with multi-record record lookups (getFirstRecordOnly=false) require a Loop element to reference individual records in the Output.Prompt assignment; the from-scratch pattern in Step 5 uses getFirstRecordOnly=true to keep the demo simple. For verticals where a Work Order has multiple Refrigerant Logs / Compliance Checks / etc., the seed should specify a Loop pattern.
  • WebFetch results depend on the company's homepage being public and not paywalled or JS-only. Pages rendered entirely by client-side JS may return little usable content; in that case fall back to admin description only.
  • AssignedResource create may fail in orgs that require a Service Territory on the Service Appointment. Step 8 resolves Service Territory from the chosen Service Resource's ServiceTerritoryMember and includes it on create. If the resource has no territory membership, ask the admin to pick a different resource or assign a territory in Setup.
  • Asset.ProductDescription is read-only for non-admin profiles in some org configurations (verified on afvuser 2026-05-19). Seeds should write descriptive text to Asset.Description instead.