Scribe is a sales employee agent that turns a rep's call recap into real CRM work, updated Opportunity fields, follow-up Tasks, logged call notes, and new Contact records for stakeholders who came up on the call but aren't in Salesforce yet. The rep pastes or dictates what happened; Scribe does the 10–15 minutes of post-call data entry that reps otherwise skip or half-do.
Every change Scribe ever makes is written to a dedicated audit trail, reviewable through its own app, tabs, LWCs, and list views. Nothing Scribe does is invisible.
- Why Scribe
- Design goals
- The three surfaces
- Architecture and data flow
- The five agent topics
- Data model
- Component inventory
- Guardrails & safety
- Install & deploy
- Configuration reference
- Testing
- Project structure
- Status & known environment notes
- License
Reps hate post-call admin, so it rots: Opportunity fields go stale, verbal commitments ("I'll send pricing," "we'll loop in their CFO") never become Tasks and quietly get forgotten, new stakeholders mentioned in passing never get added as Contacts, and deal notes live in the rep's head instead of the record.
None of that is laziness, turning a conversation into structured CRM fields is tedious work that adds value to the org's data, not to the rep. Scribe's job is to make "give me a two-sentence recap" produce the same CRM outcome as a rep spending fifteen minutes doing it by hand, and to make every automated change fully visible afterward.
| Goal | How Scribe honors it |
|---|---|
| Never invent data | Every field update, task, or contact traces to something literally in the recap. The deterministic extraction engine (Scribe_ExtractionService) is the only place text is interpreted, and it flags lowConfidence instead of guessing. |
| Confirm before writing | Opportunity updates and new Contacts use a two-phase propose → apply flow; the agent only calls the apply action after the rep explicitly says yes. The two risky actions also carry require_user_confirmation. |
| One extraction, many outputs | The recap is parsed once by Log This Call and cached on Scribe_Call_Log__c.Extraction_JSON__c. Every other topic reuses that single parse via the call-log id, no re-pasting, no inconsistent re-parsing. |
| No new habits | Extraction works on bullets, stream-of-consciousness paragraphs, or a pasted transcript. No required format. |
| Fit the deal cycle | Flag Missing Info compares the deal against per-stage requirements and creates reminder tasks for what's still missing, actively catching deal risk, not just recording history. |
| Full transparency | Every write, no exceptions, funnels through Scribe_ChangeLogService and produces a Scribe_Change_Log__c row with before/after values and a link back to the source recap. |
- The employee agent (conversational),
Scribe_Agent, an AgentforceAiAuthoringBundlewith five topics. The rep talks to it; it does the CRM work. - The automation engine (headless), five autolaunched Flows, each wrapping an invocable Apex action that performs the write and logs the audit row as its final step.
- The Scribe app (review & audit), a Lightning app where reps and admins see everything Scribe has ever done, by object, rep, date, or change type.
Rep recap ──▶ Scribe agent (Agentforce)
│
▼
Scribe_ExtractionService ── one structured parse ──▶ cached on Scribe_Call_Log__c
│
┌─────────────┬───┴────────┬──────────────┬─────────────────┐
▼ ▼ ▼ ▼ ▼
Log call Update Opp Create Tasks Flag gaps Detect Contacts
(additive) (confirm) (additive) (additive) (confirm)
│ │ │ │ │
└─────────────┴────────────┴──────────────┴─────────────────┘
│
▼
Scribe_Change_Log__c ← every action writes exactly one audit row
The change log is the single source of truth for "everything Scribe has ever done," independent of whether someone views it via the app, a list view, or the record-page badge.
| # | Topic | Risk | What it does |
|---|---|---|---|
| 1 | Log This Call | Additive | Extracts stage-relevant facts, objections, and sentiment; logs a Scribe_Call_Log__c; caches the one parse every other topic reuses. |
| 2 | Update Opportunity | Confirm | Proposes Stage / Close Date / Amount / Next Step changes field-by-field; writes only after explicit confirmation; audits each field with before/after. |
| 3 | Create Follow-Up Tasks | Additive | Turns each commitment ("I'll…", "we will…") into a Task with an inferred owner and due date. |
| 4 | Flag Missing Info | Additive | Compares the deal (this recap + all prior call logs) against Scribe_Stage_Requirement__mdt for the current stage and creates reminder Tasks for the gaps. |
| 5 | Multi-Contact Detection | Confirm | Drafts Contacts for stakeholders mentioned but not yet on the deal; creates them (linked via OpportunityContactRole) only after confirmation. |
Custom objects
Scribe_Call_Log__c, the raw recap/transcript, a link to the Opportunity, extracted sentiment/objections, andExtraction_JSON__c(the cached single parse). The source every change-log row traces back to.Scribe_Change_Log__c, one row per action Scribe takes.Change_Type__c(Call Logged / Field Update / Task Created / Stage Gap Task / Contact Created),Related_Record_Id__c+Object_API_Name__c,Field_Name__c/Old_Value__c/New_Value__c(field updates only),Source_Call_Log__c, andCreated_By_Rep__c(the human behind the conversation, distinct from the running user).
Custom metadata
Scribe_Stage_Requirement__mdt, what "complete" means per Opportunity stage (economic buyer, budget, timeline, …), with detection keywords. Tunable per sales process without a code change. Ships seeded for Qualification, Needs Analysis, Value Proposition, Proposal/Price Quote, and Negotiation/Review.
Standard-object additions
Task.Scribe_Generated__c,Contact.Scribe_Generated__c, mark agent-created records so the "Created by Scribe" list views are trivially reliable (more robust than filtering on the integration user'sCreatedById, and deployable without knowing that user's Id).Task.Scribe_Source_Call_Log__c,Contact.Scribe_Source_Call_Log__c, trace each generated record back to its recap.
| Type | Components |
|---|---|
| Agent | Scribe_Agent (AiAuthoringBundle, 5 topics) |
| Apex, core | Scribe_ExtractionService, Scribe_CallParse, Scribe_ChangeLogService, Scribe_Constants, Scribe_Util |
| Apex, invocables | Scribe_LogCallInvocable, Scribe_UpdateOpportunityInvocable, Scribe_CreateTasksInvocable, Scribe_CheckStageRequirementsInvocable, Scribe_DetectNewContactsInvocable |
| Apex, controllers | Scribe_ChangeLogConsoleController, Scribe_HomeDashboardController |
| Apex, tests | Scribe_ExtractionService_Test, Scribe_Invocables_Test, Scribe_Controllers_Test, Scribe_TestUtil |
| Flows | Scribe_Log_Call, Scribe_Update_Opportunity, Scribe_Create_Followup_Tasks, Scribe_Check_Stage_Requirements, Scribe_Detect_New_Contacts |
| LWCs | scribeChangeLogConsole, scribeHomeDashboard, scribeRecordHistoryBadge |
| App / tabs | Scribe app; tabs: Scribe Home, Change Log, Change Log records, Call Logs (+ standard Task/Contact) |
| List views | Task & Contact "Created by Scribe"; Change Log "This Week / By Object / Field Updates Only" |
| Permission sets | Scribe_Agent_User (write, for the running/integration user), Scribe_Reviewer (read-only review access) |
- Confirmation is enforced two ways: the propose→apply action split (the agent only calls
applyafter an explicit yes) andrequire_user_confirmation: Trueon the two write actions. - Graceful degradation: when a recap is too vague,
Scribe_ExtractionServicereturnslowConfidenceand the agent asks the rep to clarify rather than writing a low-confidence value. - The audit row is mandatory, not configurable. Every write path calls
Scribe_ChangeLogService; there is no code path that writes without auditing. - Apex runs
with sharing; controllers are read-only and use bound variables (no SOQL injection).
Requires an Agentforce-enabled org for the agent bundle. The data model, Apex, Flows, app, and LWCs deploy to any org.
# 1. Set your target org
sf config set target-org <alias>
# 2. Deploy everything
sf project deploy start --source-dir force-app
# 3. Assign permission sets
sf org assign permset --name Scribe_Agent_User # running/integration user
sf org assign permset --name Scribe_Reviewer # reps & managers who review
# 4. Validate, publish and activate the agent (Agentforce org)
sf agent validate authoring-bundle --api-name Scribe_Agent
sf agent publish authoring-bundle --api-name Scribe_Agent
sf agent activate --api-name Scribe_AgentThen add the Scribe Record History badge to the Opportunity Lightning record page in App Builder.
Every object with a tab in the Scribe app ships an unfiltered All list view (filterScope: Everything), and that is what each tab should default to - the narrower views ("Created by Scribe", "This Week", "Field Updates Only") are filters a reviewer chooses deliberately, never the landing state.
Tune Flag Missing Info entirely through Scribe_Stage_Requirement__mdt, no code change:
| Field | Purpose |
|---|---|
Stage_Name__c |
The Opportunity StageName this requirement applies to. |
Requirement_Key__c / Requirement_Label__c |
Machine key + human label (used in task subjects). |
Detection_Keywords__c |
Comma-separated, case-insensitive phrases. If any appears across the deal's recaps, the requirement is "covered." |
Reminder_Task_Subject__c / Reminder_Due_In_Days__c |
Subject and due window for the gap reminder Task. |
Sort_Order__c / Is_Active__c |
Ordering and on/off. |
Apex tests cover the extraction engine, all five invocables (including propose→apply, dedup, and bad-input paths), both controllers, and the utilities:
sf apex run test --tests Scribe_ExtractionService_Test --tests Scribe_Invocables_Test \
--tests Scribe_Controllers_Test --result-format human --code-coverageStatic analysis (SLDS/ESLint) is clean:
sf code-analyzer run --target force-appforce-app/main/default/
├── aiAuthoringBundles/Scribe_Agent/ # the employee agent (5 topics)
├── applications/ # Scribe Lightning app
├── classes/ # extraction, invocables, controllers, tests
├── customMetadata/ # seeded stage requirements
├── flexipages/ # Scribe Home, Change Log app pages
├── flows/ # 5 autolaunched action flows
├── lwc/ # console, dashboard, record badge
├── objects/ # Call Log, Change Log, Stage Requirement + Task/Contact fields
├── permissionsets/ # Agent User, Reviewer
└── tabs/ # Scribe Home, Change Log, Call Logs
Deployed and verified to a Developer Edition org: all objects, the stage-requirement type and its 13 seed records, 16 Apex classes, 5 Flows, 3 LWCs, 2 flexipages, 4 tabs, the Scribe app, both permission sets (assigned), and the Scribe_Agent AiAuthoringBundle. All 20 Apex tests pass at 94% coverage of the exercised classes.
Two org-specific deployment notes, already handled in this repo:
- Task fields live on
Activity.Scribe_Generated__candScribe_Source_Call_Log__care defined underobjects/Activity/(Task is a sub-type of Activity, so a field placed directly on Task is rejected). They still surface on Task asTask.Scribe_Generated__cetc. - Seed CMT records deploy via the Apex Metadata API. This org returns an
UNKNOWN_EXCEPTIONgack when deploying custom-metadata records through the Metadata API (reproducible even with a single minimal record, an org/platform issue, not the files). The 13 rows were loaded withMetadata.Operations.enqueueDeploymentinstead; on a healthy orgsf project deploy starthandles them normally.
Remaining, environment-limited:
- Apex PMD/SFGE static analysis was not run here because no Java runtime is installed (
sf code-analyzerPMD/SFGE engines require Java 11). SLDS/ESLint analysis passes. Runsf code-analyzer run --target force-appon a machine with Java to exercise the Apex engines. - Agent publish/activate: the bundle deploys cleanly, but
sf agent validate/preview(theafscript/v2/parseandcompilereasoner endpoint) returned 422 across every available org, a service-side issue independent of the bundle. Re-runsf agent validate authoring-bundle --api-name Scribe_Agentwhen that service is reachable, thensf agent publish/sf agent activate.
Captured from a live org at a 1200px viewport.
MIT © 2026 VurtuoLabs



