sshvarzman-salesforce/claude-skills

sra-analytics

Deploy and manage the SRA Analytics Dashboard — fleet-level Agentforce analytics across sessions, channels, knowledge quality, and dynamic plans.

ソースを見る

使い方

  1. インストールコマンドをコピーし、自分の環境へ導入する前にソースを確認します。
  2. 必要なツール、認証情報、対応環境を原文で確認します。
  3. 入力と成果物を指定し、出力を資料と照合します。
npx skills add https://github.com/sshvarzman-salesforce/claude-skills --skill sra-analytics

開始前の確認

  • 導入にはモデル契約、API枠、ファイルへのアクセス許可は含まれません。
  • スター数や導入数は活動の指標であり、独立した品質・安全性試験ではありません。
リポジトリの原文

見出し、例、コード、表、リンク、参照画像を含む原文を表示しています。

sra-analytics — SRA Fleet Analytics Dashboard

Deploys, updates, and runs the Agentforce fleet-level analytics dashboard. Two deployment modes: Python web (local dev) and in-org (LWC + Apex via ConnectApi).

Prerequisites

RequirementDetails
sf CLIAuthenticated to target org (sf org login web --alias <ALIAS>)
Python3.9+ (for local dashboard mode only)
Data CloudEnabled on target org with STDM data flowing
Git accessgit.soma.salesforce.com/chad-goldsmith/agentforce-debug-tools
Org featuresConnectApi CDP Query enabled, Agentforce with session telemetry

Getting Started

Installation

bash
# 1. Clone the repo
git clone git.soma.salesforce.com/chad-goldsmith/agentforce-debug-tools.git
cd agentforce-debug-tools

# 2. Copy skill into your Claude skills directory
mkdir -p ~/.claude/skills/sra-analytics
cp skills/sra-analytics/SKILL.md ~/.claude/skills/sra-analytics/SKILL.md

# 3. Verify sf CLI auth
sf org list

Quick Start

bash
# Deploy the in-org dashboard to your target org
cd agentforce-debug-tools
sf project deploy start \
  --source-dir sra-analytics/force-app/main/default/classes/SRAAnalyticsController.cls \
  sra-analytics/force-app/main/default/classes/SRAAnalyticsController.cls-meta.xml \
  sra-analytics/force-app/main/default/lwc/sraAnalyticsDashboard \
  --target-org <ORG_ALIAS>

# Or run the local Python dashboard
python3 sra-analytics/scripts/analytics.py --org <ORG_ALIAS>

If the user hasn't given enough to proceed

When invoked without clear intent, print this verbatim:

What would you like to do with SRA Analytics? I can: - Deploy the in-org dashboard to a target org (LWC + Apex) - Run the local Python web dashboard against an org - Add a tile — new metric, chart, or table to the dashboard - Update filters — add/modify top-level filter options - Check data — query Data Cloud to verify what's available I need: - Org alias — the sf CLI alias (e.g. mySDO, MetaRLUAT) - Action — deploy, run, add tile, or explore data

When to use

Trigger this skill when the user:

  • Asks to "deploy / run / update the analytics dashboard"
  • Wants to see fleet-level metrics for Agentforce (session volume, token usage, etc.)
  • Asks about knowledge quality scores, feedback data, channel breakdown
  • Wants to add a new tile or filter to the dashboard
  • Asks "how are my agents performing" or "show me agent analytics"
  • References PRD-264, dynamic plan reporting, or fleet KPIs
  • Wants to filter data by channel (messaging, voice, case) or plan type (guidance vs dynamic)

Architecture

Source Locations

ComponentPath (relative to cloned repo)Purpose
Apex Controllersra-analytics/apex/SRAAnalyticsController.clsCanonical source
Python Dashboardsra-analytics/scripts/analytics.pyLocal web dashboard
In-Org LWCsra-analytics/force-app/main/default/lwc/sraAnalyticsDashboard/Deployable LWC
In-Org Apexsra-analytics/force-app/main/default/classes/SRAAnalyticsController.clsDeployable Apex

Data Sources

SourceTable/ObjectWhat it provides
Data Cloud STDMAiAgentSession__dllSessions, channels, end types, timestamps
Data Cloud STDMAiAgentInteraction__dllInteractions, topic routing
Data Cloud STDMAiAgentInteractionStep__dllSteps, types, duration
Data Cloud STDMAiAgentSessionParticipant__dllAgent identity, API names
Data CloudGenAIGatewayRequest__dllToken usage, prompt templates, models
Data CloudGenAIFeedback__dllDynamic plan feedback (thumbs up/down, shown/accepted)
Data CloudAIRetrieverRequest__dll / AIRetrieverResponse__dllKnowledge retrieval quality scores
Data CloudObservabilitySpan__dllPer-operation latency
Core SOQLGenOpPlanPlan outcomes, intents, topic routing
Core SOQLRecActorActionFeedAction feed volume

Key Technical Notes

  • Data Cloud tables end in __dll (NOT __dlm), no ssot__ prefix
  • Fields are camelCase with __c suffix (e.g. startTimestamp__c, aiAgentChannelTypeId__c)
  • Use ConnectApi.CdpQuery.queryAnsiSqlV2() for in-org queries (supports JOINs, caching)
  • Chart.js static resource in org is chartjs_v280 (not chartjs or ChartJS)
  • sf org display returns 54-char session IDs that don't work for REST API — use in-org ConnectApi instead

⚠️ RecActorActionFeed & GenOpPlan — Verified Field Reference (in-org, mySDO, 2026-07-27)

Empirically confirmed while building the Employee Interaction Tracking feature. Older query examples further down in this doc use pre-verification field names (Body, Type, ParentId, InsertedById, Parent.AiAgentSession__c) — prefer the verified names below.

RecActorActionFeed real fields:

UseCorrect fieldNOT
Parent record (Case/MessagingSession)RelatedRecordId~~ParentId~~
The human userCreatedById / CreatedBy.Name~~InsertedById~~
The skill/agent feature (not a user!)SenderId → RecActorFeatureDef—
Action contentContent~~Body~~
StatusStatus—
(there is no Type field)—~~Type~~

Query-context limitations (why GenOpPlan is the primary analytics source):

  • RecActorActionFeed throws Invalid type: RecActorActionFeed in static SOQL → must use dynamic Database.query() + @SuppressWarnings('PMD').
  • Not supported in @AuraEnabled(cacheable=true) or Execute Anonymous ("sObject type ... is not supported in this context") → use cacheable=false, and even then treat it as a try/catch fallback only.
  • No GROUP BY aggregates → fetch rows and count into a Map in Apex.
  • No LIKE on Id fields (ParentId LIKE → "invalid operator on id field") → filter with String.startsWith() after the query.
  • `GenOpPlan` is reliable in all contexts (incl. Execute Anonymous): GenOpPlan.CreatedById = user who ran the plan; GenOpPlan.ParentId = Case/MessagingSession. Make it the primary source; use RecActorActionFeed only for extra action detail.
  • `GenOpPlan.FeedbackKey` does NOT join to `GenAIFeedback` (tested: no matching rows). GenAIFeedback only populates on explicit user feedback → unreliable for confidence scoring. Use AIRetrieverResponse__dll quality/relevance scores instead.

Record-type detection via ID prefix: 500=Case, 0MW/0Mw=MessagingSession, 0LQ=VoiceCall, ka0=Knowledge, 005=User, 003=Contact, 001=Account.

LWC deploy gotcha: the bundle folder must contain only .css/.html/.js/.js-meta.xml. Stray temp files (*-employee.js, *.bak, temp.js) cause Leading decorators must be attached to a class declaration / Missing semicolon deploy errors.

⚠️ Stale path in Quick Start above: the actual layout is sra-analytics/apex/SRAAnalyticsController.cls (canonical) and a separate sra-analytics-deploy/main/default/{classes,lwc}/ deploy folder — not sra-analytics/force-app/main/default/.... Reconcile LWC edits from the deploy folder back to canonical sra-analytics/lwc/ before committing.

Dashboard Tiles

Current tiles (20 total):

Summary KPIs (always visible)

  • Total Sessions, Total Steps, Avg Steps/Session, Error Rate, Prompt/Completion/Total Tokens

Guidance Plan Tiles (filtered by Plan Type = Guidance or All)

  • Session Volume Over Time (line chart)
  • Step Type Breakdown (doughnut chart)
  • Session End Types (pie chart)
  • Channel Breakdown (pie chart)
  • Intent Distribution (table)
  • Plan Outcomes by Topic (table)
  • Prompt Template Usage (table)
  • Action Usage (table + bar chart)
  • Topic Breakdown (table)
  • Step Performance / Duration (table)
  • Inter-Step Latency (table — avg gap between consecutive steps)
  • Observability Spans (table)
  • Case Session Summary (table)
  • Feed Volume (line chart)
  • Token Usage Over Time (line chart)

Dynamic Plan Tiles (filtered by Plan Type = Dynamic or All)

  • Dynamic Plan Feedback banner (thumbs up/down, positive rate, shown/accepted)
  • Plan Feedback by User (table with resolved user names)

Shared Tiles (always visible)

  • Knowledge Retrieval Quality KPIs (green banner)
  • Knowledge Quality Scores per Article (table with color-coded quality)

Filters

FilterFieldValues
Date RangestartTimestamp__cStart/End date pickers
Agentp.aiAgentApiName__cDynamic from org data
Channels.aiAgentChannelTypeId__cAll, Messaging, Voice, Case (Web/Core), Builder (Test)
Plan TypeClient-side visibility toggleAll, Guidance (Topic-Routed), Dynamic (ServicePlans)

Commands

Deploy In-Org Dashboard

bash
# From the cloned agentforce-debug-tools repo root:
sf project deploy start \
  --source-dir sra-analytics/force-app/main/default/classes/SRAAnalyticsController.cls \
  sra-analytics/force-app/main/default/classes/SRAAnalyticsController.cls-meta.xml \
  sra-analytics/force-app/main/default/lwc/sraAnalyticsDashboard \
  --target-org <ORG_ALIAS>

Run Local Python Dashboard

bash
# From the cloned agentforce-debug-tools repo root:
python3 sra-analytics/scripts/analytics.py --org <ORG_ALIAS>
# Opens http://127.0.0.1:8090

Note: Python dashboard has auth issues on some orgs (54-char session token). In-org deployment is preferred.

Query Data Cloud Directly (Explore)

apex
ConnectApi.CdpQueryInput input = new ConnectApi.CdpQueryInput();
input.sql = '<YOUR SQL>';
ConnectApi.CdpQueryOutputV2 output = ConnectApi.CdpQuery.queryAnsiSqlV2(input);

Adding a New Tile

  1. Add data class in SRAAnalyticsController.cls:
apex
   public class MyNewData {
       @AuraEnabled public String fieldA;
       @AuraEnabled public Integer fieldB;
   }
  1. Add @AuraEnabled method with standard signature:
apex
   @AuraEnabled(cacheable=true)
   public static List<MyNewData> getMyNewData(String startDate, String endDate, String agentName, String channelType) {
       String dateFilter = buildDateFilter(startDate, endDate, 's.startTimestamp__c');
       String agentFilter = buildAgentFilter(agentName);
       // Query uses: dateFilter + agentFilter + buildChannelFilter(channelType)
   }
  1. Import in LWC JS and add to Promise.allSettled:
javascript
   import getMyNewData from '@salesforce/apex/SRAAnalyticsController.getMyNewData';
   // Add @track property, add to Promise.allSettled array, add result handler
  1. Add HTML section with table/chart markup
  1. Deploy to target org

Troubleshooting

IssueCauseFix
Chart.js not foundWrong resource nameUse chartjs_v280 (no namespace)
401 on Python dashboardsf CLI 54-char session tokenUse in-org deployment instead
"table does not exist"Wrong naming (ssot__ prefix or __dlm suffix)Use bare name + __dll suffix
Empty tile dataSharing model on Apex classEnsure without sharing if needed
Channel filter no resultsChannel values differ by orgQuery SELECT DISTINCT s.aiAgentChannelTypeId__c FROM AiAgentSession__dll s to check
Knowledge quality emptyNo AIRetrieverResponse dataVerify Data Library is configured with quality monitoring

Querying Session & Actor History for Customer Engineers

Use Case

Customer engineers need to see what happened in a specific SRA session: what actions fired, what CLTs rendered, what the agent said, and what the final outcome was.

Data Sources for Session History

SourceWhat It ShowsAccess Method
AiAgentSession__dllSession metadata: start/end time, channel, end type, agent nameData Cloud query (ConnectApi or SOQL on __dll)
AiAgentInteraction__dllInteractions within session: topic, intent, routingData Cloud query
AiAgentInteractionStep__dllIndividual steps: type, output, duration, orderData Cloud query
RecActorActionFeedSRA-specific: CLT rendering, action feed items, actor outputCore SOQL (NOT Data Cloud)
GenOpPlanPlan metadata: outcome, intent, topicCore SOQL
GenAIGatewayRequest__dllToken usage, prompt templates, model callsData Cloud query
ObservabilitySpan__dllPer-operation latency, parent/child spansData Cloud query

Query 1: Get Session Overview (Data Cloud)

Option A: Find sessions by parent record (Case/Messaging/Voice)

sql
SELECT aiAgentSessionId__c, startTimestamp__c, endTimestamp__c,
       aiAgentChannelTypeId__c, aiAgentSessionEndTypeId__c
FROM AiAgentSession__dll
WHERE relatedRecordId__c = '<CASE_ID or MESSAGING_SESSION_ID or VOICECALL_ID>'
ORDER BY startTimestamp__c DESC

Option B: Find session by session ID

apex
// Via ConnectApi in Apex
ConnectApi.CdpQueryInput input = new ConnectApi.CdpQueryInput();
input.sql = @
SELECT 
    s.aiAgentSessionId__c AS sessionId,
    s.startTimestamp__c,
    s.endTimestamp__c,
    s.aiAgentChannelTypeId__c AS channel,
    s.aiAgentSessionEndTypeId__c AS endType,
    s.relatedRecordId__c AS parentRecordId,
    p.aiAgentApiName__c AS agentName
FROM AiAgentSession__dll s
LEFT JOIN AiAgentSessionParticipant__dll p 
    ON s.aiAgentSessionId__c = p.aiAgentSessionId__c
WHERE s.aiAgentSessionId__c = '<SESSION_ID>'
@;
ConnectApi.CdpQueryOutputV2 output = ConnectApi.CdpQuery.queryAnsiSqlV2(input);
System.debug(output.data);

Via sf CLI:

bash
sf data query \
  --query "SELECT aiAgentSessionId__c, startTimestamp__c, endTimestamp__c, aiAgentChannelTypeId__c, relatedRecordId__c FROM AiAgentSession__dll WHERE relatedRecordId__c = '<CASE_ID>'" \
  --target-org <ALIAS> \
  --api-version 61.0

Key Field: relatedRecordId__c = the Case, MessagingSession, or VoiceCall ID (this is the parent record)

Query 2: Get All Steps in Session (Data Cloud)

The Relationship Hierarchy:

AiAgentSession__dll (session)
    └─ AiAgentInteraction__dll (interaction) 
           └─ AiAgentInteractionStep__dll (steps)

Join Keys:

  • AiAgentInteraction__dll.aiAgentSessionId__c → AiAgentSession__dll.id__c
  • AiAgentInteractionStep__dll.aiAgentInteractionId__c → AiAgentInteraction__dll.id__c

Query steps via interaction:

sql
-- Get all steps for a session (via interaction join)
SELECT 
    step.name__c AS stepName,
    step.aiAgentInteractionStepTypeId__c AS stepType,
    step.startTimestamp__c,
    step.endTimestamp__c,
    step.inputValueText__c AS input,
    step.outputValueText__c AS output,
    step.subType__c
FROM AiAgentInteractionStep__dll step
JOIN AiAgentInteraction__dll interaction 
    ON step.aiAgentInteractionId__c = interaction.id__c
WHERE interaction.aiAgentSessionId__c = '<SESSION_ID>'
ORDER BY step.startTimestamp__c

If you have the interaction ID directly:

sql
SELECT name__c, aiAgentInteractionStepTypeId__c, 
       startTimestamp__c, endTimestamp__c,
       inputValueText__c, outputValueText__c
FROM AiAgentInteractionStep__dll
WHERE aiAgentInteractionId__c = '<INTERACTION_ID>'
ORDER BY startTimestamp__c

Via ConnectApi in Apex:

apex
ConnectApi.CdpQueryInput input = new ConnectApi.CdpQueryInput();
input.sql = @
SELECT 
    step.stepType__c AS stepType,
    step.stepName__c AS stepName,
    step.topicName__c AS topic,
    step.startTimestamp__c,
    step.endTimestamp__c,
    step.statusCode__c AS status,
    step.input__c,
    step.output__c
FROM AiAgentInteractionStep__dll step
WHERE step.aiAgentSessionId__c = '<SESSION_ID>'
ORDER BY step.startTimestamp__c ASC
@;
ConnectApi.CdpQueryOutputV2 output = ConnectApi.CdpQuery.queryAnsiSqlV2(input);

Step Types:

  • GenAiPromptRequest — Agent thinking/planning
  • SearchRelatedRecord — Knowledge grounding
  • GetStepSummary — Plan step rendering
  • ExecuteAction — Action execution
  • GetModelResponse — LLM call

Status Codes:

  • 200 — Success
  • 400 — Bad request
  • 500 — Internal error

Query 2b: Get Conversation Messages (Data Cloud)

Join Keys for Messages:

  • AiAgentInteractionMessage__dll.aiAgentSessionId__c → AiAgentSession__dll.id__c (direct)
  • AiAgentInteractionMessage__dll.aiAgentInteractionId__c → AiAgentInteraction__dll.id__c (via interaction)

Option A: Get messages directly by session ID:

sql
SELECT aiAgentInteractionMessageTypeId__c AS messageType,
       contentText__c AS content,
       startTimestamp__c AS timestamp
FROM AiAgentInteractionMessage__dll
WHERE aiAgentSessionId__c = '<SESSION_ID>'
ORDER BY startTimestamp__c

Option B: Get messages via interaction ID:

sql
SELECT aiAgentInteractionMessageTypeId__c AS messageType,
       contentText__c AS content,
       startTimestamp__c AS timestamp
FROM AiAgentInteractionMessage__dll
WHERE aiAgentInteractionId__c = '<INTERACTION_ID>'
ORDER BY startTimestamp__c

Message Types:

  • User — Customer message
  • Agent — AI agent response
  • System — System notifications

Query 3: Get RecActorActionFeed (Core SOQL)

This is SRA-specific and shows CLT rendering, action confirmations, and actor output.

apex
// In Apex
List<RecActorActionFeed> feed = [
    SELECT Id, ParentId, Type, Body, CreatedDate, 
           InsertedBy.Name, CommentCount, LikeCount
    FROM RecActorActionFeed
    WHERE Parent.AiAgentSession__c = '<SESSION_ID>'
    ORDER BY CreatedDate ASC
];

Via sf CLI:

bash
sf data query \
  --query "SELECT Id, Type, Body, CreatedDate FROM RecActorActionFeed WHERE Parent.AiAgentSession__c = '<SESSION_ID>' ORDER BY CreatedDate" \
  --target-org <ALIAS>

Feed Types:

  • TrackedChange — Status updates ("Plan started", "Action completed")
  • TextPost — Agent messages to rep
  • ContentPost — CLT rendering (Complex Data Type)
  • CallLogPost — Voice-specific call logs

Query 4: Get Plan Outcome (Core SOQL)

apex
List<GenOpPlan> plans = [
    SELECT Id, AiAgentSession__c, Outcome, Intent, Topic, 
           ChannelType, CreatedDate
    FROM GenOpPlan
    WHERE AiAgentSession__c = '<SESSION_ID>'
];

Via sf CLI:

bash
sf data query \
  --query "SELECT Id, Outcome, Intent, Topic FROM GenOpPlan WHERE AiAgentSession__c = '<SESSION_ID>'" \
  --target-org <ALIAS>

Outcomes:

  • Resolved — Plan completed successfully
  • Unresolved — Plan did not complete
  • Escalated — Handed off to human

Query 5: Get Token Usage for Session (Data Cloud)

apex
ConnectApi.CdpQueryInput input = new ConnectApi.CdpQueryInput();
input.sql = @
SELECT 
    g.aiAgentSessionId__c AS sessionId,
    g.aiPromptTemplateApiName__c AS promptTemplate,
    g.aiModelName__c AS model,
    SUM(g.inputTokens__c) AS totalInputTokens,
    SUM(g.outputTokens__c) AS totalOutputTokens
FROM GenAIGatewayRequest__dll g
WHERE g.aiAgentSessionId__c = '<SESSION_ID>'
GROUP BY g.aiAgentSessionId__c, g.aiPromptTemplateApiName__c, g.aiModelName__c
@;
ConnectApi.CdpQueryOutputV2 output = ConnectApi.CdpQuery.queryAnsiSqlV2(input);

Query 6: Get Knowledge Retrieval Quality (Data Cloud)

apex
ConnectApi.CdpQueryInput input = new ConnectApi.CdpQueryInput();
input.sql = @
SELECT 
    resp.aiRetrieverRequestId__c AS requestId,
    resp.retrievedRecordName__c AS articleTitle,
    resp.retrievedRecordId__c AS articleId,
    resp.qualityScore__c AS quality,
    resp.relevanceScore__c AS relevance,
    resp.orderNumber__c AS rank
FROM AIRetrieverResponse__dll resp
JOIN AIRetrieverRequest__dll req 
    ON resp.aiRetrieverRequestId__c = req.id__c
WHERE req.aiAgentSessionId__c = '<SESSION_ID>'
ORDER BY resp.orderNumber__c ASC
@;
ConnectApi.CdpQueryOutputV2 output = ConnectApi.CdpQuery.queryAnsiSqlV2(input);

Query 7: Get Observability Spans (Latency Breakdown)

apex
ConnectApi.CdpQueryInput input = new ConnectApi.CdpQueryInput();
input.sql = @
SELECT 
    span.name__c AS operation,
    span.startTimestamp__c,
    span.endTimestamp__c,
    span.durationInMilliseconds__c AS duration,
    span.parentSpanId__c AS parentSpan
FROM ObservabilitySpan__dll span
WHERE span.aiAgentSessionId__c = '<SESSION_ID>'
ORDER BY span.startTimestamp__c ASC
@;
ConnectApi.CdpQueryOutputV2 output = ConnectApi.CdpQuery.queryAnsiSqlV2(input);

Common Operations:

  • service_plan_generation — Plan generation latency
  • knowledge_grounding — Knowledge retrieval latency
  • action_execution — Action execution latency
  • llm_call — Model inference latency

Example: Full Session Trace for Customer Engineer

apex
// Step 1: Get session metadata
String sessionId = '<SESSION_ID>';
ConnectApi.CdpQueryInput input = new ConnectApi.CdpQueryInput();

// Session overview
input.sql = 'SELECT s.id__c, s.startTimestamp__c, s.endTimestamp__c, s.aiAgentChannelTypeId__c, p.aiAgentApiName__c FROM AiAgentSession__dll s LEFT JOIN AiAgentSessionParticipant__dll p ON s.id__c = p.aiAgentSessionId__c WHERE s.id__c = \'' + sessionId + '\'';
ConnectApi.CdpQueryOutputV2 sessionData = ConnectApi.CdpQuery.queryAnsiSqlV2(input);
System.debug('Session: ' + sessionData.data);

// All steps
input.sql = 'SELECT step.id__c, step.aiAgentInteractionStepTypeId__c, step.orderNumber__c, step.durationInMilliseconds__c FROM AiAgentInteractionStep__dll step JOIN AiAgentInteraction__dll interaction ON step.aiAgentInteractionId__c = interaction.id__c WHERE interaction.aiAgentSessionId__c = \'' + sessionId + '\' ORDER BY step.orderNumber__c ASC';
ConnectApi.CdpQueryOutputV2 stepsData = ConnectApi.CdpQuery.queryAnsiSqlV2(input);
System.debug('Steps: ' + stepsData.data);

// Step 2: Get RecActorActionFeed (core SOQL)
List<RecActorActionFeed> feed = [SELECT Id, Type, Body, CreatedDate FROM RecActorActionFeed WHERE Parent.AiAgentSession__c = :sessionId ORDER BY CreatedDate];
System.debug('Feed: ' + feed);

// Step 3: Get plan outcome
List<GenOpPlan> plans = [SELECT Id, Outcome, Intent, Topic FROM GenOpPlan WHERE AiAgentSession__c = :sessionId];
System.debug('Plan: ' + plans);

// Step 4: Token usage
input.sql = 'SELECT SUM(g.inputTokens__c) AS inputTokens, SUM(g.outputTokens__c) AS outputTokens FROM GenAIGatewayRequest__dll g WHERE g.aiAgentSessionId__c = \'' + sessionId + '\'';
ConnectApi.CdpQueryOutputV2 tokenData = ConnectApi.CdpQuery.queryAnsiSqlV2(input);
System.debug('Tokens: ' + tokenData.data);

How to Share with Customer Engineers

Option 1: Anonymous Apex (Quick Check)

  1. Copy one of the queries above
  2. Developer Console → Debug → Open Execute Anonymous Window
  3. Paste query, replace <SESSION_ID> with actual session ID
  4. Click Execute, check Debug Logs

Option 2: Workbench (SQL Interface)

  1. Go to Workbench (workbench.developerforce.com)
  2. Login with org credentials
  3. Queries → SOQL Query (for core objects like RecActorActionFeed, GenOpPlan)
  4. Utilities → REST Explorer → POST to /services/data/v61.0/connect/cdp/query (for Data Cloud tables)

Option 3: REST API (Programmatic)

bash
# Get session ID from recent sessions
sf data query --query "SELECT Id, Name FROM Case WHERE Subject LIKE '%<CUSTOMER_NAME>%' ORDER BY CreatedDate DESC LIMIT 1" --target-org <ALIAS>

# Use session ID to query history
sf data query --query "SELECT Id, Type, Body FROM RecActorActionFeed WHERE Parent.AiAgentSession__c = '<SESSION_ID>'" --target-org <ALIAS>

Option 4: In-Org LWC (Customer Self-Service) Deploy a custom LWC that queries these tables and renders a timeline view. Customer engineers can access via a custom Lightning page.

Common Customer Engineer Questions

Q: "Which employee ran this session?" (Human rep, not the AI agent)

The human employee is found by resolving from the parent record (Case, MessagingSession, or VoiceCall):

apex
// Step 1: Get the parent record ID from GenOpPlan
List<GenOpPlan> plans = [
    SELECT Id, ParentId, CreatedBy.Name, ChannelType 
    FROM GenOpPlan 
    WHERE AiAgentSession__c = :sessionId
];

String parentId = plans[0].ParentId; // Case, MessagingSession, or VoiceCall Id
String channelType = plans[0].ChannelType;

// Step 2: Query parent record based on channel
if (channelType == 'Case') {
    // Case channel - get case owner
    List<Case> cases = [
        SELECT Id, Owner.Name, Owner.Email, OwnerId 
        FROM Case 
        WHERE Id = :parentId
    ];
    System.debug('Employee: ' + cases[0].Owner.Name);
    
} else if (channelType == 'Messaging') {
    // Messaging channel - get agent from MessagingSession
    List<MessagingSession> sessions = [
        SELECT Id, AgentId, Agent.Name, Agent.Email 
        FROM MessagingSession 
        WHERE Id = :parentId
    ];
    System.debug('Employee: ' + sessions[0].Agent.Name);
    
} else if (channelType == 'Voice') {
    // Voice channel - get agent from VoiceCall
    List<VoiceCall> calls = [
        SELECT Id, UserId, User.Name, User.Email 
        FROM VoiceCall 
        WHERE Id = :parentId
    ];
    System.debug('Employee: ' + calls[0].User.Name);
}

Quick version via sf CLI:

bash
# Step 1: Get parent record ID
sf data query --query "SELECT ParentId, ChannelType FROM GenOpPlan WHERE AiAgentSession__c = '<SESSION_ID>'" --target-org <ALIAS>

# Step 2: Query based on channel type
# For Case:
sf data query --query "SELECT Owner.Name, Owner.Email FROM Case WHERE Id = '<PARENT_ID>'" --target-org <ALIAS>

# For Messaging:
sf data query --query "SELECT Agent.Name, Agent.Email FROM MessagingSession WHERE Id = '<PARENT_ID>'" --target-org <ALIAS>

# For Voice:
sf data query --query "SELECT User.Name, User.Email FROM VoiceCall WHERE Id = '<PARENT_ID>'" --target-org <ALIAS>

Alternative: Query RecActorActionFeed InsertedById The human rep who interacted with the feed:

apex
List<RecActorActionFeed> feed = [
    SELECT InsertedBy.Name, InsertedBy.Email, Type, CreatedDate
    FROM RecActorActionFeed 
    WHERE Parent.AiAgentSession__c = :sessionId
    ORDER BY CreatedDate ASC
    LIMIT 1
];
System.debug('Employee: ' + feed[0].InsertedBy.Name);

Q: "What actions fired in this session?"

apex
// Query AiAgentInteractionStep__dll WHERE stepType = 'ExecuteAction'
input.sql = 'SELECT step.aiAgentInteractionStepOutput__c FROM AiAgentInteractionStep__dll step JOIN AiAgentInteraction__dll interaction ON step.aiAgentInteractionId__c = interaction.id__c WHERE interaction.aiAgentSessionId__c = \'' + sessionId + '\' AND step.aiAgentInteractionStepTypeId__c = \'ExecuteAction\'';

Q: "What knowledge articles were grounded?"

apex
// Query AIRetrieverResponse__dll with article names
input.sql = 'SELECT resp.retrievedRecordName__c, resp.qualityScore__c FROM AIRetrieverResponse__dll resp JOIN AIRetrieverRequest__dll req ON resp.aiRetrieverRequestId__c = req.id__c WHERE req.aiAgentSessionId__c = \'' + sessionId + '\'';

Q: "Why did the plan fail?"

apex
// Query GenOpPlan for outcome + last RecActorActionFeed for error message
List<GenOpPlan> plans = [SELECT Outcome FROM GenOpPlan WHERE AiAgentSession__c = :sessionId];
List<RecActorActionFeed> lastFeed = [SELECT Body FROM RecActorActionFeed WHERE Parent.AiAgentSession__c = :sessionId ORDER BY CreatedDate DESC LIMIT 1];

Q: "How long did each step take?"

apex
// Query AiAgentInteractionStep__dll with duration
input.sql = 'SELECT step.aiAgentInteractionStepTypeId__c, step.durationInMilliseconds__c FROM AiAgentInteractionStep__dll step JOIN AiAgentInteraction__dll interaction ON step.aiAgentInteractionId__c = interaction.id__c WHERE interaction.aiAgentSessionId__c = \'' + sessionId + '\' ORDER BY step.orderNumber__c';

User Interaction Tracking (For SRA Tracer UI)

Use Case: Case → Users → User Actions Drill-Down

Goal: In the SRA tracer UI, user selects a Case ID from dropdown, sees which employees interacted with it, filters by employee, then sees every action that employee took (questions asked, actions approved/rejected, messages sent).

Step 1: Get All Sessions for a Case

apex
// Find all sessions on a Case
String caseId = '<CASE_ID>';
ConnectApi.CdpQueryInput input = new ConnectApi.CdpQueryInput();

// Option A: For Case channel (uses GenOpPlan.ParentId)
List<GenOpPlan> plans = [
    SELECT Id, AiAgentSession__c, CreatedDate, CreatedBy.Name
    FROM GenOpPlan 
    WHERE ParentId = :caseId
    ORDER BY CreatedDate DESC
];

// Get unique session IDs
Set<String> sessionIds = new Set<String>();
for (GenOpPlan p : plans) {
    if (p.AiAgentSession__c != null) {
        sessionIds.add(p.AiAgentSession__c);
    }
}

// Option B: For Messaging/Voice (uses AiAgentSession__dll.relatedRecordId__c)
// (Only if Case was ALSO a MessagingSession or VoiceCall parent)
input.sql = 'SELECT id__c, startTimestamp__c, endTimestamp__c FROM AiAgentSession__dll WHERE relatedRecordId__c = \'' + caseId + '\'';
ConnectApi.CdpQueryOutputV2 sessions = ConnectApi.CdpQuery.queryAnsiSqlV2(input);

For Tracer UI: Populate dropdown with Case IDs. On selection, run this query to get sessionIds list.


Step 2: Get All Users Who Interacted with Those Sessions

Source: RecActorActionFeed (SRA-specific feed) shows which users interacted with the case during SRA sessions.

apex
// Get all unique users from RecActorActionFeed for these sessions
List<RecActorActionFeed> allFeed = [
    SELECT Id, InsertedById, InsertedBy.Name, InsertedBy.Email, 
           Type, CreatedDate, Body
    FROM RecActorActionFeed
    WHERE ParentId = :caseId
    ORDER BY CreatedDate ASC
];

// Dedupe users
Map<Id, String> userMap = new Map<Id, String>();
for (RecActorActionFeed f : allFeed) {
    if (!userMap.containsKey(f.InsertedById)) {
        userMap.put(f.InsertedById, f.InsertedBy.Name + ' (' + f.InsertedBy.Email + ')');
    }
}

System.debug('Users who interacted: ' + userMap.values());

For Tracer UI: Show list of users (names + emails) who interacted with this Case. Make it filterable/clickable.

Note: InsertedById = the human rep user. ParentId = Case ID.


Step 3: Filter by User — Get All Actions for That User

Once user selects a specific employee, show everything they did:

apex
// Filter feed items by selected user
String selectedUserId = '<USER_ID>'; // From dropdown selection

List<RecActorActionFeed> userActions = [
    SELECT Id, Type, Body, CreatedDate, 
           Status, CommentCount, LikeCount,
           RelatedRecordId // The specific action/CLT/step this relates to
    FROM RecActorActionFeed
    WHERE ParentId = :caseId
      AND InsertedById = :selectedUserId
    ORDER BY CreatedDate ASC
];

// Parse each action type
for (RecActorActionFeed action : userActions) {
    if (action.Type == 'TextPost') {
        System.debug('User asked: ' + action.Body);
    } else if (action.Type == 'ContentPost') {
        System.debug('User received CLT: ' + action.Body); // JSON rendering data
    } else if (action.Type == 'TrackedChange') {
        System.debug('Status update: ' + action.Body);
    } else if (action.Type == 'ActionConfirm') {
        System.debug('User approved action: ' + action.RelatedRecordId);
    }
}

For Tracer UI: Display as timeline:

  • Timestamp (CreatedDate)
  • Action Type (TextPost = question, ContentPost = CLT card shown, ActionConfirm = approved/rejected)
  • Body (message text or JSON data)
  • Status (if available — approval status, completion status)

Action Types Reference:

TypeWhat It MeansHow to Parse
TextPostUser sent a message/question to SRABody = text content
ContentPostSRA rendered a CLT cardBody = JSON with complex_data_type_name, rendering_data
TrackedChangeStatus update (plan started, action completed)Body = status text
CallLogPostVoice-specific call logBody = call metadata
ActionConfirmUser approved/rejected an actionRelatedRecordId = action ID, Status = approved/rejected

Step 4: Get Detailed Context for Each Action

To show WHAT the user was responding to (e.g., "User approved action 'Update Case Status'"), you need to join with:

For Action Confirmations:

apex
// Get action details from GenAiFunction (action definition)
String actionId = action.RelatedRecordId;
List<GenAiFunction> actions = [
    SELECT Id, DeveloperName, Description 
    FROM GenAiFunction 
    WHERE Id = :actionId
];
System.debug('User approved: ' + actions[0].DeveloperName);

For CLT Rendering:

apex
// Parse ContentPost body (JSON format)
if (action.Type == 'ContentPost') {
    Map<String, Object> cltData = (Map<String, Object>) JSON.deserializeUntyped(action.Body);
    String cltType = (String) cltData.get('complex_data_type_name');
    System.debug('CLT Type: ' + cltType); // e.g., "solutionCard", "orderSummary"
}

For Messages (TextPost):

apex
// Body is plain text
System.debug('User said: ' + action.Body);

Step 5: Get Session-Level Context (What Plan Was Running)

To show "User approved action during Plan X on Session Y":

apex
// Get session metadata for these actions
List<GenOpPlan> planContext = [
    SELECT Id, Topic, Intent, Outcome, AiAgentSession__c
    FROM GenOpPlan
    WHERE ParentId = :caseId
];

// Map session IDs to topics
Map<String, String> sessionToTopic = new Map<String, String>();
for (GenOpPlan p : planContext) {
    sessionToTopic.put(p.AiAgentSession__c, p.Topic);
}

// Now annotate user actions with plan context
for (RecActorActionFeed action : userActions) {
    // Find which session this action belongs to (infer from CreatedDate vs session start/end)
    // Or: use GenOpPlan.CreatedDate range to bucket actions into sessions
}

Example UI Flow (Tracer)

Dropdown 1: Select Case

Case-00001234 — Customer Refund Request
Case-00001235 — Product Issue Escalation
Case-00001236 — Billing Dispute

Dropdown 2: Select User (after Case selected)

John Smith (john.smith@example.com) — 12 interactions
Jane Doe (jane.doe@example.com) — 7 interactions
System Agent (system@salesforce.com) — 23 interactions [filter out?]

Timeline View (after User selected)

2026-07-23 10:15:32 — User asked: "Can we issue a refund?"
2026-07-23 10:15:45 — SRA showed CLT: Solution Card (Refund Policy)
2026-07-23 10:16:02 — User approved action: "Create Refund Case"
2026-07-23 10:16:10 — Status update: "Action completed successfully"
2026-07-23 10:17:22 — User asked: "What's the refund timeline?"

Complete Query for Tracer UI Backend

apex
// Input: caseId (String), selectedUserId (String, optional)

// Step 1: Get all sessions for Case
List<GenOpPlan> plans = [
    SELECT Id, AiAgentSession__c, Topic, CreatedDate
    FROM GenOpPlan 
    WHERE ParentId = :caseId
    ORDER BY CreatedDate DESC
];

// Step 2: Get all users who interacted
List<RecActorActionFeed> allFeed = [
    SELECT InsertedById, InsertedBy.Name, InsertedBy.Email, 
           COUNT(Id) interactionCount
    FROM RecActorActionFeed
    WHERE ParentId = :caseId
    GROUP BY InsertedById, InsertedBy.Name, InsertedBy.Email
];

// Step 3: If user selected, get their actions
if (selectedUserId != null) {
    List<RecActorActionFeed> userActions = [
        SELECT Id, Type, Body, CreatedDate, Status, RelatedRecordId
        FROM RecActorActionFeed
        WHERE ParentId = :caseId
          AND InsertedById = :selectedUserId
        ORDER BY CreatedDate ASC
    ];
    
    // Step 4: Enrich with action metadata
    Set<Id> actionIds = new Set<Id>();
    for (RecActorActionFeed a : userActions) {
        if (a.RelatedRecordId != null && a.Type == 'ActionConfirm') {
            actionIds.add(a.RelatedRecordId);
        }
    }
    
    Map<Id, GenAiFunction> actionMap = new Map<Id, GenAiFunction>([
        SELECT Id, DeveloperName, Description
        FROM GenAiFunction
        WHERE Id IN :actionIds
    ]);
    
    // Return enriched data
    for (RecActorActionFeed a : userActions) {
        if (actionMap.containsKey(a.RelatedRecordId)) {
            System.debug(a.CreatedDate + ' — User ' + a.Type + ': ' + actionMap.get(a.RelatedRecordId).DeveloperName);
        } else {
            System.debug(a.CreatedDate + ' — ' + a.Type + ': ' + a.Body);
        }
    }
}

Additional Filters for Tracer UI

Filter by Interaction Type:

  • All interactions
  • Questions only (Type = 'TextPost')
  • Action approvals only (Type = 'ActionConfirm')
  • CLT cards shown only (Type = 'ContentPost')
  • Status updates only (Type = 'TrackedChange')

Filter by Time Range:

apex
WHERE ParentId = :caseId
  AND InsertedById = :selectedUserId
  AND CreatedDate >= :startDate
  AND CreatedDate <= :endDate

Filter by Session:

apex
// First get session date ranges from GenOpPlan
List<GenOpPlan> sessionPlans = [
    SELECT AiAgentSession__c, CreatedDate
    FROM GenOpPlan
    WHERE ParentId = :caseId
];

// Then filter feed items by date range of selected session
// (Since RecActorActionFeed doesn't directly store session ID)

Data Model Summary for UI Developers

FieldSourcePurpose
Case IDUser dropdown selectionParent record filter
Session IDGenOpPlan.AiAgentSession__cSession context (optional grouping)
User IDRecActorActionFeed.InsertedByIdFilter by employee
User NameRecActorActionFeed.InsertedBy.NameDisplay name
TimestampRecActorActionFeed.CreatedDateTimeline ordering
Action TypeRecActorActionFeed.TypeIcon + label in UI
Action ContentRecActorActionFeed.BodyMessage text or JSON data
Action StatusRecActorActionFeed.StatusApproved/rejected/completed
Related ActionRecActorActionFeed.RelatedRecordIdLink to action definition
Plan TopicGenOpPlan.TopicSession context label

Related

  • SRA Debugger skill (sra-agent-debugger): Per-session trace and analysis
  • PRD-264: AE Dynamic Plan Reporting
  • Git repo: git.soma.salesforce.com/chad-goldsmith/agentforce-debug-tools

タスクに関連する項目

同じリポジトリから

関連する Skills

すべての Skills
sshvarzman-salesforce
コミュニティ

activating-datacloud

Salesforce Data Cloud Act phase. Use this skill when the user manages activations, activation targets, data actions, or downstream delivery of Data Cloud audiences and data. TRIGGER when: user manages activations, activation targets, data actions, or downstream delivery of Data Cloud audiences and data. DO NOT TRIGGER when: the task is segment creation (use segmenting-datacloud), data retrieval/search work (use retrieving-datacloud), or STDM/session tracing (use observing-agentforce).

導入数
1
GitHub Stars
0
更新日
10月15日
sshvarzman-salesforce
コミュニティ

agentforce-skills-research

Research Agentforce configuration patterns — topics, actions, instructions, CLTs, knowledge, planner behavior. Searches Salesforce docs, Trailhead, internal repos (git.soma), Slack channels, and Google Docs to find working examples, known issues, and best practices.

導入数
1
GitHub Stars
0
更新日
10月15日
sshvarzman-salesforce
コミュニティ

harmonizing-datacloud

Salesforce Data Cloud Harmonize phase. Use this skill when the user works with DMOs, mappings, relationships, identity resolution, unified profiles, data graphs, or universal IDs. TRIGGER when: user works with DMOs, mappings, relationships, identity resolution, unified profiles, data graphs, or universal IDs. DO NOT TRIGGER when: the task is only about streams/DLOs (use preparing-datacloud), segments/insights (use segmenting-datacloud), retrieval/search (use retrieving-datacloud), or STDM/sessio

導入数
1
GitHub Stars
0
更新日
10月15日
sshvarzman-salesforce
コミュニティ

preparing-datacloud

Salesforce Data Cloud Prepare phase. Use this skill when the user creates or manages Data Cloud data streams, DLOs, transforms, or Document AI configurations. TRIGGER when: user creates or manages Data Cloud data streams, DLOs, transforms, or Document AI configurations, or asks about ingestion into Data Cloud. DO NOT TRIGGER when: the task is connection setup only (use connecting-datacloud), DMOs and identity resolution (use harmonizing-datacloud), or query/search work (use retrieving-datacloud)

導入数
1
GitHub Stars
0
更新日
10月15日