A config-driven Salesforce app that surfaces stale, at-risk records across any standard object, with a colorful dashboard, a five-sub-agent Agentforce assistant, one-click triage, and a daily proactive monitor. Zero per-object code.
Ask Sentinel "what should I work on first?", open the Sentinel app for a live health radar, or let the daily monitor push you a digest. Adding a new object to watch is a metadata record, not a code change.
Three things to configure, the deploy will succeed without them, but the agent won't behave correctly until they're set:
- Agent user. Sentinel ships as an Employee Agent (internal users, no channel setup, no agent user required). If you switch it to a customer-facing Service Agent, edit
Sentinel.agent: setagent_type: "AgentforceServiceAgent"and add anaccess:block with a real Einstein Agent User, publishing with a missing/placeholder user fails with "Internal Error, try again later."- Permission set. Assign
Sentinel_Userto the agent's running user and to end users, otherwise actions return no data or are blocked.- Config records. Review the
Sentinel_Config.*metadata records so the watched objects, date fields, thresholds, and filters match your org. (The shippedDate_Field__cvalues must be queryable on that object, e.g.LastActivityDateexists on Opportunity/Lead but not everywhere.)The same checklist lives at the top of
Sentinel.agentandmanifest/package.xml.
Most record-hygiene solutions hard-code one query per object. Sentinel inverts that: all object-specific knowledge lives in a Custom Metadata Type, and a single Apex service reads that config to build every query at runtime.
| Property | How Sentinel achieves it |
|---|---|
| No per-object code | One SentinelService serves Case, Opportunity, Lead, Task, and anything else you configure. |
| No storage overhead | Config ships as Custom Metadata, deploys with the app, packageable, no records to seed. |
| Change without deploying Apex | Thresholds, date fields, sort, filters, and alert levels are metadata edits. |
| Reliable agent actions | The agent calls Apex through thin flows (flow://), the pattern Agentforce treats as most reliable. |
| Governor-safe & injection-safe | Queries are capped and ordered; every identifier from config is validated against an allowlist. |
Conversation ─ Agentforce "Sentinel" Employee Agent
└─ router → 5 sub-agents (see below)
Automation ── Daily SentinelMonitor → Chatter digest + threshold notifications
UI ────────── "Sentinel" Lightning app → 7 LWC tabs on one shared design system
Actions ───── 5 flow-fronted invocables: HealthCheck · Rollup · Reassign · FollowUp · Catalog
Logic ─────── SentinelService (shared engine) + focused controllers
Data ──────── Sentinel_Config__mdt (one record per watched object)
| Field | Purpose |
|---|---|
Object_API_Name__c |
Object to monitor (Case, Opportunity, Lead, Task). |
Date_Field__c |
Field measuring staleness (LastActivityDate, LastModifiedDate, ActivityDate). |
Stale_Days__c |
Days before a record counts as at-risk. |
Owner_Field__c |
Usually OwnerId; resolves Owner.Name automatically. |
Priority_Field__c |
Optional field to surface (Priority, StageName, Status). |
Name_Field__c |
Display-name field, Name, or CaseNumber / Subject. |
Filter_Criteria__c |
Optional extra WHERE fragment (IsClosed = false). |
Sort_Field__c / Sort_Direction__c |
Optional ordering (defaults to date, oldest first). |
Threshold_Alert__c |
Daily monitor raises a notification at/above this count. |
Is_Active__c |
Unchecked configs are skipped by rollups and the monitor. |
Label__c |
Friendly name shown to users (Active Pipeline). |
Four records ship: Active Pipeline (Opportunity), Open Cases (Case), Working Leads (Lead), Open Tasks (Task).
One engine builds each query from config and returns an enriched report per record: freshness band (Aging / Stale / Critical at 1×/2×/3× the threshold), deep link, resolved owner name, age in days, plus summaries grouped by owner, priority, and band. Every entry point reuses it.
A hub-and-spoke Employee Agent; the router picks a specialist per utterance:
| Sub-agent | Handles | Backing |
|---|---|---|
| Record Health | "stale opps?", "check my leads" | Check_Health, Check_All |
| Focus / Prioritize | "what should I work on first?", "morning briefing" | Check_All scoped to you → ranks → hands off to Triage |
| Triage | "reassign these", "create follow-ups" | Reassign_Records, Create_Follow_Up, confirms first |
| Catalog | "what can you check?", "how does staleness work?" | List_Watched_Objects |
| Coach | "how do I keep my pipeline fresh?" | best-practice advice, then offers real data |
Focus and Coach transition into other sub-agents, so one thread can flow check → prioritize → act.
Seven tabs share one "control room" design system: a deep violet header rail, a light data surface, and chart-forward panels.
| Tab | Component | Reads |
|---|---|---|
| Sentinel | sentinelDashboard |
Critical-first hero scoreboard, critical-share gauge, All/Mine scope, a KPI card per watched object (freshness bands, real 7-day trend, expandable oldest records) |
| Trends | sentinelTrends |
Inline-SVG trend lines per object, 7/14/30-day range, improving/worsening badge, expandable data table |
| Team Rollup | sentinelLeaderboard |
Ranked owner scoreboard with position treatment, share of total, search, sort, pagination |
| Snoozed | sentinelSnoozed |
Deferral register with wake-up countdown and row actions |
| Pipeline Value | sentinelPipelineValue |
Amount at risk, critical-share gauge, band split, breakdown by stage |
| Single-Threaded | sentinelSingleThreaded |
Relationship-risk register with severity pills and per-row "why" detail |
| Activity Gaps | sentinelActivityGaps |
Never contacted vs. went cold per object, with a never-worked share gauge |
Nine shared LWCs carry the system so nothing is duplicated: sentinelSectionHeader, sentinelStatCard, sentinelStatusBadge, sentinelBandBar, sentinelGauge, sentinelFilterBar, sentinelSkeleton, sentinelEmptyState, sentinelErrorState, plus the sentinelUtils service module. Every view has a skeleton loading state sized to its loaded content, a friendly error state with retry and expandable diagnostics, and an empty state that reads "all clear" as a win. Charts are hand-rolled inline SVG with role="img" labels and a text equivalent, no charting library.
A scheduled sweep that runs every active config, posts a Chatter digest, and raises a Sentinel Alert custom notification for any config at/above its Threshold_Alert__c.
sf org login web --alias my-org --set-default
# Deploy everything (uses the manifest):
sf project deploy start --manifest manifest/package.xml --json
# …or just: sf project deploy start --source-dir force-app --json
# Grant access, end users AND the agent's running user
sf org assign permset --name Sentinel_User
# Publish & activate the agent (see "Before you deploy" first)
sf agent validate authoring-bundle --api-name Sentinel --json
sf agent publish authoring-bundle --api-name Sentinel --json
sf agent activate --api-name Sentinel --json
# Schedule the daily monitor (anonymous Apex)
echo "System.schedule('Sentinel Daily Monitor','0 0 7 * * ?', new SentinelMonitor());" \
| sf apex runNote:
sf project deploy startdeploys the agent bundle as an editable draft;sf agent publishcompiles it into a running Bot. If the CLI's Einstein authoring endpoint is unreachable, you can Publish → Activate from Agentforce Studio in the browser instead.
- Dashboard (works immediately): App Launcher → Sentinel, or the Sentinel tab. Drop the
sentinelDashboardcomponent on any Home/App/Record page via Lightning App Builder. - Agent (after publish + activate): in Agentforce Studio → Sentinel → Connections, add the Salesforce (Employee) channel and enable it for
Sentinel_Userholders. It then appears as the Agentforce assistant panel (sparkle icon, top-right utility bar) on any Lightning page, and optionally in Slack.
scripts/seedData.apex creates ~60 at-risk records across Opportunity, Lead, and Task by backdating completed activities (which drive LastActivityDate) and setting past ActivityDate on open tasks. Re-run any time:
sf apex run --file scripts/seedData.apex- Ageing Cases depends on the org: Case needs a queryable staleness date. Where
LastActivityDateis absent and audit-field writes are disabled, seeded Cases can't be backdated, the Case board reads 0 until cases age naturally. Opportunity/Lead/Task populate fully. ownerScope=minefilters on the owner field equalling the running user; use it only where that field references a user.- Dynamic SOQL is assembled from metadata. Config is developer/admin-controlled and every identifier is allowlist-validated, but treat
Filter_Criteria__cas trusted developer input, never end-user input. - CLI publish endpoint. For some org types the CLI routes
sf agent publishtotest.api.salesforce.com; if that host is unreachable, publish from Agentforce Studio in the browser instead.
manifest/package.xml # deploy manifest + config checklist
scripts/seedData.apex # demo data
force-app/main/default/
├── objects/Sentinel_Config__mdt/ # Custom Metadata Type + 12 fields
├── customMetadata/ # one record per watched object
├── classes/ # SentinelService + 5 actions + controller + monitor + tests
├── flows/ # 5 flow-fronted action targets
├── notificationtypes/ # Sentinel_Alert custom notification
├── lwc/ # 7 page components + 9 shared components + sentinelUtils
├── flexipages/ tabs/ applications/ # Sentinel app page, tab, Lightning app
├── permissionsets/ # Sentinel_User
└── aiAuthoringBundles/Sentinel/ # the Sentinel agent (5 sub-agents)
Captured from a live org at a 1200px viewport.
MIT © 2026 VurtuoLabs



