Add voice modality support across ADLC skills - #39
Conversation
- New reference: voice-modality-reference.md with full `modality voice:` block syntax, TTS/STT properties, pronunciation dict, speak-up config, and voice-specific authoring guidance - New example: voice-service-agent.agent template with modality block, connection voice escalation, and telephony-optimized instructions - agentforce-generate: detect voice intent during Design, include modality block during code gen, voice Quick Link added - agentforce-test: voice UX checks (response length, no visual formatting, confirmation patterns, escalation channel) - agentforce-observe: voice-specific anti-patterns in session analysis - Core language ref: modality voice added to block ordering - CLAUDE.md: "voice agent", "phone agent" triggers added
…oice - Add VoiceCallId linked variable (@VoiceCall.Id) to voice agents - Remove fabricated connection voice: surface type; keep connection messaging: for escalation and add connection customer_web_client: with adaptive_response_allowed (matches Agent Builder UI output) - Correct Atlas__VoiceAgent claim: it is a runtime planner_type, not an authored bundle field; voice agents keep AgentforceServiceAgent - Start from the platform default voice (UgBBYS2sOqTuMpoF3BR0, 1/0.65/0.75) instead of prompting for a voice_id; point users to Agent Builder > Connections > Voice to customize - Propagate corrections to test/observe voice checks and CHANGELOG
Voice support corrected against UI ground truthValidated the generated voice output against a voice agent created through the Agent Builder UI (
Corrections propagated to Local validationBuilt a voice pizza-ordering agent in a fresh sfdx project on the |
- Pair voice with knowledge grounding: /agentforce-generate now proactively asks the Knowledge Grounding question when it detects a voice agent, and starts from a combined template when the Spec has both sections. - Add assets/agents/voice-knowledge-grounded.agent (modality voice: + voice wiring + knowledge: block + AnswerQuestionsWithKnowledge, with spoken-answer anti-hallucination guards). - Document the known limitation that voice-channel deploy is UI-only (bundle publish deploys metadata; telephony wiring is Agent Builder only) -- a tracked Steel Thread 2 gap -- plus a Steel Thread alignment note. - CHANGELOG updated.
Aligned voice support with Project Codey Steel Thread 2Per the steel thread definition in
This commit (
Directly serves the 8/31 "validated in Claude" milestone. |
Eval results — Enterprise Use Cases suiteRan the Overall score: 84.6% — 3/3 tests PARTIAL. Assertions: 77 passed / 5 partial / 12 failed (94 total).
Executive summaryThree enterprise multi-topic agents were authored, discovered, scaffolded, deployed, previewed, and analyzed. Authoring quality was excellent across the board (5.0/5 on FSM architecture, action quality, safety compliance, and instruction quality): correct hub-and-spoke FSM, router-only start agents, complete action inventories with The single systemic defect is downstream in scaffold/deploy, not authoring. Failing labels (all traceable to one root cause)
Key findings
Recommended actions
|
Resolve conflicts from main (optimization task domain, #36) against the voice-support work: - CHANGELOG: keep both the voice entries and the optimization entries. - CLAUDE.md: merge the /agentforce-generate trigger list (voice + optimize). Also fix the systemic authoring defect surfaced by the enterprise-use-cases eval (run-20260720-205045): the authoring step emitted multiple apex:// targets sharing one class (apex://ClassName.method), which cannot compile because Salesforce allows only one InvocableMethod per class -- cascading into failed deploy/publish and no grounded action calls. - agent-design-and-spec-creation.md: make the one-class-per-action rule explicit with WRONG/RIGHT examples. - agent-validator.py: new _check_apex_target_shared_class -- warns on the method-suffix form and on two apex:// targets resolving to the same class. - tests/test_hooks.py: cover method-suffix, shared-class, and the distinct-classes (no-false-positive) cases.
| | Ask the customer what they'd like help with. You can check order status, process a return, or connect them with a representative. If unclear, ask one clarifying question. | ||
| ``` | ||
|
|
||
| ### Connection Blocks |
There was a problem hiding this comment.
For Voice agent, it need either the ECv2 or Telephony as the connection?
connection customer_web_client:
adaptive_response_allowed: True
connection telephony:
adaptive_response_allowed: True
How this skill guide the user to pick which connection to use for voice agent?
In the Agent Builder, when user add the ECv2 or Telephony connection, the voice settings is added automatically. please help to clarify the dependency of the modality and connection block..
There was a problem hiding this comment.
Good question — clarified in 54bdb75. A voice agent needs both a modality voice: block (voice behavior) and a voice-capable connection surface. The surface is connection customer_web_client: (Enhanced Chat v2 / ECv2). connection messaging: is additive — only needed when the agent escalates to a human via @utils.escalate. So to your question: it's ECv2 (or Telephony) for the voice surface; messaging isn't required unless there's a human handoff.
On the UI auto-adding voice settings: right — in Agent Builder, adding either an ECv2 or Telephony connection auto-enables Voice Settings. ADLC authors customer_web_client because that's the surface reliably created via the DSL/CLI today and the one Agent Builder Preview requires; Telephony (SCV) channel attachment is UI-only. I rewrote the 'Connection Blocks' section to spell out the modality↔connection dependency and the ECv2-vs-Telephony choice.
| jq -r '.plan[] | select(.type == "VariableUpdateStep") | .data.variable_updates[] | "\(.variable_name): \(.variable_past_value) -> \(.variable_new_value) (\(.variable_change_reason))"' "$TRACE" | ||
| ``` | ||
|
|
||
| ### Voice Agent Testing |
There was a problem hiding this comment.
Testing Center supports these voice specific validations?
There was a problem hiding this comment.
Fair challenge — no, Testing Center does not natively validate voice. These are heuristic checks on the text-preview transcript, not audio/TTS/STT validation (true voice test-case generation depends on the NGT API integration, which is out of war-room scope). I reframed the section in 54bdb75 to say exactly that: it inspects the text responses and the .agent config as a proxy for voice-readiness, and labels final voice QA as requiring the Agent Builder voice preview / a live channel.
There was a problem hiding this comment.
Two items I'd like to see addressed before merge, both about internal consistency (the code/docs disagreeing with other code/docs in the same repo):
connection customer_web_client:vs the repo's own "Supported Channels" table — see inline onvoice-modality-reference.md.actions-reference.md(untouched by this PR) still lists onlymessagingandtelephony;customer_web_clientappears nowhere else in the repo.- The new
apex://validator warns on the repo's own files — including this PR's own template — see inline onvoice-service-agent.agent. I ran the new check locally: it flagsvoice-service-agent.agent:89, plus 4 pre-existing occurrences (action-callbacks.agent:63,lifecycle-events.agent:53/75,examples.md:126).
The rule itself (one @InvocableMethod per Apex class) is correct — the issue is shipping the check without fixing the files it flags. Minor notes inline too (unverified runtime claim; regex matches apex:// on comment lines). Details below.
| actions: | ||
| get_order_status: | ||
| description: "Retrieves order status and estimated delivery date" | ||
| target: "apex://OrderService.getOrderStatus" |
There was a problem hiding this comment.
This PR's own template trips the apex:// validator this same PR introduces. _check_apex_target_shared_class() warns on any apex://Class.method suffix form — I ran it against this file and it flags line 89:
apex:// target 'OrderService.getOrderStatus' uses a method suffix — the target names the CLASS, not a method (use 'apex://OrderService')
SKILL.md explicitly tells users to copy-modify this template, so they'd get warned by the very rule this PR adds. Suggest changing to a bare class name, e.g. target: "apex://OrderStatusService".
Separately, the check also flags 4 pre-existing files (assets/patterns/action-callbacks.agent:63, assets/patterns/lifecycle-events.agent:53 & :75, references/examples.md:126). Either fix those in this PR so the repo is self-consistent, or file a fast follow — but at minimum the PR-authored template shouldn't fail the PR's own validator.
There was a problem hiding this comment.
You're right, and thanks for running the check locally. Fixed in 54bdb75: voice-service-agent.agent:89 is now apex://OrderStatusService (bare class), plus the 4 pre-existing files you flagged — action-callbacks.agent:63 → AuditEventService, lifecycle-events.agent:53/75 → CurrentTimestampService/TurnAnalyticsService, examples.md:126 → ITKnowledgeSearchArticles. I verified the whole skills/ tree is now free of apex://Class.method (except the intentional WRONG example in the rule blockquote) and re-ran the validator against the fixed template — clean.
|
|
||
| ### Connection Blocks | ||
|
|
||
| `connection` blocks are separate from `modality voice:` — they define the surface/channel the agent is wired to, while `modality` defines voice behavior. The only valid connection surface types are **messaging** and **customer_web_client** — there is **no `connection voice:`**. |
There was a problem hiding this comment.
This says the only valid connection surfaces are messaging and customer_web_client, but actions-reference.md:542-547 (not touched by this PR) still lists the Supported Channels as messaging and telephony — and customer_web_client appears nowhere else in the repo. A reader consulting the two references gets two different answers.
Since both new templates depend on connection customer_web_client:, could you either (a) update the actions-reference.md Supported Channels table in this PR to add customer_web_client (and clarify how it relates to the existing telephony row), or (b) note where the surface type is sourced from (e.g. an Agent Builder export / schema)? Right now the repo contradicts itself on what connection surfaces exist.
There was a problem hiding this comment.
Fixed the self-contradiction in 54bdb75 (chose your option (a)): actions-reference.md's 'Supported Channels' table now lists customer_web_client (ECv2) alongside messaging and telephony, with a note on how they relate. I also removed the over-claim that 'only messaging and customer_web_client exist' — telephony is a real surface too; the point is that ECv2 (customer_web_client) is what ADLC authors for voice + preview, while Telephony/SCV channel attachment is UI-only. The two references now agree. Source of the surface name: the Agent Builder export for a voice-enabled AgentforceServiceAgent (validated against Vehicle_Offer_Agent and the pizza voice agent I published to storm).
|
|
||
| Wire with: `target: "apex://ClassName"` | ||
|
|
||
| > **One `@InvocableMethod` per Apex class — one class per action.** Salesforce permits **only one** `@InvocableMethod` in a given Apex class. The `apex://` target therefore names the **class**, not a method: use `apex://ClassName` — never `apex://ClassName.methodName`. Each distinct Apex-backed action MUST point at its **own** class. |
There was a problem hiding this comment.
Minor: the constraint (one @InvocableMethod per class) is real, but the claim that the .method suffix in the apex:// target string specifically "won't compile/deploy/publish" is asserted rather than shown. Worth confirming whether the suffix actually breaks resolution vs. is simply ignored by the runtime — a one-line hedge or a cited repro would make this airtight.
There was a problem hiding this comment.
Good catch, and you're right to separate the two claims. Hedged in 54bdb75. The verified failure is the shared class: multiple @InvocableMethods in one Apex class fail compilation with Only one method per type can be defined with: InvocableMethod (observed directly in the enterprise-use-cases eval). Whether the .method suffix in the target string itself breaks resolution vs. is silently ignored by the runtime I have not independently confirmed — so the doc now says exactly that, and frames apex://ClassName (no suffix) as the rule because authoring the suffix invites the shared-class pattern that does fail.
| if next_line and not next_line.startswith("#"): | ||
| break | ||
|
|
||
| def _check_apex_target_shared_class(self): |
There was a problem hiding this comment.
Minor: _check_apex_target_shared_class() scans every line with re.search(r'apex://...') and doesn't skip comment lines, so a comment like # see apex://Foo.bar would emit a false method suffix warning. The sibling checks in this file already skip # lines — worth matching that here.
There was a problem hiding this comment.
Fixed in 54bdb75 — _check_apex_target_shared_class() now skips lines whose first non-whitespace char is #, matching the sibling checks. Added a regression test (test_apex_target_in_comment_ignored) covering # see apex://Foo.bar + a real apex://RealService target in the same file; verified no false method suffix/reused warning fires.
Voice/connection guidance corrected against UI ground truth and made self-consistent across the repo: - customer_web_client (ECv2) is the voice-capable connection surface; connection messaging is additive (only when the agent escalates to a human). Docs/templates previously implied messaging was always required. - actions-reference.md "Supported Channels" table now includes customer_web_client, resolving a repo self-contradiction (the surface appeared only in the voice reference). - Clarified the modality-vs-connection relationship and ECv2 vs Telephony. Templates trimmed to minimum per Sky's "more than minimum" note: - modality voice: block reduced to voice_id + speed/stability/similarity; filler/speak-up/endpointing moved to opt-in guidance in the reference. - Spoken-delivery instructions trimmed to the high-value guards (read back critical data; never speak URLs/citations/formatting) instead of tone the planner already handles. Validator + self-consistency: - Fixed apex://Class.method method-suffix targets in the repo's own files that tripped the PR's own new validator: voice-service-agent.agent, examples.md, lifecycle-events.agent (x2), action-callbacks.agent. - _check_apex_target_shared_class() now skips # comment lines (no false method-suffix warning on a `# see apex://Foo.bar` note). Added test. - Softened the apex:// "won't compile" claim: the verified failure is the shared class (one @InvocableMethod per class); whether the .method suffix string itself breaks resolution is not independently confirmed. Testing: - /agentforce-test voice section reframed as heuristic text-preview proxy checks, not native voice validation (no audio/TTS/STT in the CLI; true voice test generation depends on the out-of-scope NGT API).
Addressed review feedback (
|
There was a problem hiding this comment.
Please update the version of the skill
There was a problem hiding this comment.
Done — bumped agentforce-generate to version: "0.10" (and the plugin to 0.10.0 in both plugin.json and marketplace.json). Pushed in aa0a7d6.
There was a problem hiding this comment.
Please update the version of the skill.
There was a problem hiding this comment.
Done — bumped agentforce-test to version: "0.7" (also bumped agentforce-observe 0.6→0.7 since it received voice changes too, and the plugin to 0.10.0). Pushed in aa0a7d6.
Addresses Sky Chen's PR #39 version-number comments and Slack question. Version bumps (per CLAUDE.md — plugin.json and marketplace.json must stay in sync): - agentforce-generate SKILL.md: 0.9 -> 0.10 - agentforce-test SKILL.md: 0.6 -> 0.7 - agentforce-observe SKILL.md: 0.6 -> 0.7 - plugin.json / marketplace.json: 0.9.0 -> 0.10.0 Known-issue #18 marked RESOLVED: the `connection customer_web_client:` DSL block (underscores) compiles a CustomerWebClient plannerSurface directly — no post-publish patch needed. Verified against storm: the published GenAiPlannerBundle contains both Messaging and CustomerWebClient surfaces auto-generated from the DSL. The original failure used the non-existent `customerwebclient` spelling. Patch workflow retained as historical fallback.
Summary
modality voice:block support to the ADLC skill suite, aligning with the Agent Script DSL schema (confirmed inagent_dsl.v2.schema.VoiceConfiguration) and the A2 authoring agent's awareness of voice modalityChanges
agentforce-generate:
modality voice:block and voice-optimized instructions when spec has voice configvoice-modality-reference.md(syntax, properties, pronunciation dict, advanced configs)voice-service-agent.agent(complete voice agent example)agentforce-test:
agentforce-observe:
Core language reference:
modality voice:added to canonical block orderingTest plan
/agentforce-generate— verify modality block is included.agentfile withmodality voice:block compiles viasf agent validateagent_dsl.v2.schema.VoiceConfiguration