Salesforce workflow orchestration
The unit of work is a story: one user-visible behaviour change, delivered through five waves. Waves 1, 2 and 4 run several agents at the same time; waves 0 and 3 are serial because they produce a decision the others depend on.
Nothing in this skill is advisory decoration: every wave boundary is a machine-checked gate
(vf-check), and the file-collision rules are enforced by the pre-edit-guard hook, not by
politeness.
When to use
| Situation | Use this skill |
|---|---|
| Story touches Apex + LWC + metadata | Yes: full five-wave run via /vf-story |
| Single-file typo fix, one metadata attribute | No: fix it, run vf-check static --changed |
| Bug with unknown root cause | Wave 0 only first (sf-scout), then decide |
| Production incident | Waves 3-4 only, with a human in the loop on every step |
| Refactor with no behaviour change | Waves 1-2, plus a diff review through /vf-review |
| Package release | Waves 3-4 plus skill sf-packaging-release |
Wave model
wave 0 scope sf-scout (read-only) -> impact map
design sf-technical-architect (read-only, when the story has a real choice)
-> .vibeforce/state/architecture.md
decide orchestrator -> .vibeforce/state/contract.md
wave 1 build sf-apex-engineer \
sf-lwc-engineer | one parallel batch, disjoint owned paths
sf-metadata-engineer |
sf-integration-engineer/
wave 2 check sf-test-engineer \
sf-quality-gate | one parallel batch, read-mostly
sf-security-reviewer /
gate vf-check local -> must pass
wave 3 deploy sf-deploy-engineer (serial)
gate vf-check deploy-validate -> deploy-quick
wave 4 verify sf-org-verifier \ one parallel batch
sf-test-engineer /
gate vf-check verify -> verdict
Rules that make the parallelism safe:
- One batch per wave. All agents of a wave are dispatched in a single parallel batch. Dispatching them one at a time is a defect, not a style choice: it serialises the wave and doubles the wall clock.
- Disjoint ownership. Each wave-1 agent owns metadata paths no sibling may write. The
pre-edit-guardhook denies a cross-slice write and names the owning agent. - Contracts before code. Anything two agents both depend on (Apex method signature, field
API name, event payload, permission set name) is decided in wave 0 and written to
.vibeforce/state/contract.md. Agents read it; they never negotiate mid-wave. - No mid-wave validation. Wave-1 agents do not run the project-wide gate; they would block on each other's half-finished edits. Checks belong to wave 2.
- Gates are blocking. A wave starts only after the previous wave's gate returned exit 0.
Wave 0: scope, design, contract
Serial, read-only, cheap relative to what a wrong design costs in wave 1. Each step feeds the next.
# what the story touches, before any edit
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" static --changed # baseline: is the tree already clean?
git log --oneline -10 -- force-app/main/default/classes # recent churn in the area
Step 1 - sf-scout returns an impact map: metadata files in scope, existing tests covering
them, triggers and flows already on the affected objects, integration touch points, and risks.
Step 2 - sf-technical-architect, only when the story has more than one defensible design.
It gets the story and the scout map, and returns decisions with the rejected alternative and the
binding limit for each. Run it when the story:
| Trigger | Example |
|---|---|
| changes the data model | a new object, a relationship, a field that carries volume |
| changes who sees what | OWD, sharing rules, Apex sharing, restriction rules |
| crosses a system boundary | a callout, an inbound API, an event, middleware, identity |
| states a non-functional number | row counts, latency, retention, availability |
| forces a declarative-versus-code call | Flow or trigger, rollup or Apex, External Services or hand-written callout |
| changes packaging or environments | a new package, a changed promotion path |
Skip it for a typo, a single-file fix, or a story whose design the contract already fixed. An architecture run on a story with one defensible design returns one line and costs a turn.
Step 3 - the orchestrator applies the minimal change ladder (skill sf-minimal-change) and
records the outcome in the contract file: which rung each requirement landed on, which alternative
was rejected and why. Scope that does not survive the ladder never reaches wave 1. The architect's
Contract inputs go in verbatim; its full record is persisted to
.vibeforce/state/architecture.md so wave 2 can review against the decision, not against taste.
Contract file shape (full template in references/contract-file.md):
## Story
As a service agent I need the case escalation reason on the case list.
## Decisions
- Escalation reason: existing field Case.Escalation_Reason__c reused (rung 2), no new field.
- List UI: lightning-datatable inside existing caseList LWC (rung 4), no custom table.
- Validation: validation rule Case.Escalation_Reason_Required (rung 3), no Apex.
## Contracts
- Apex: CaseEscalationService.escalate(List<Id> caseIds, String reasonCode) -> List<EscalationResult>
- Field API: Case.Escalation_Reason__c (picklist, values: Sla_Breach, Customer_Request)
- Permission set: Case_Escalation_Access (field read/edit + Apex class access)
## Ownership
- sf-apex-engineer: classes/CaseEscalationService*.cls, classes/CaseEscalationServiceTest.cls
- sf-lwc-engineer: lwc/caseList/**
- sf-metadata-engineer: objects/Case/**, permissionsets/Case_Escalation_Access.permissionset-meta.xml
Wave 1: parallel build
Dispatch every needed engineer in one batch. Each gets: the story, the contract file, its owned paths, and its non-goals. Typical split:
| Agent | Owns | Never touches |
|---|---|---|
sf-apex-engineer |
**/classes/**, **/triggers/** |
classes/integration/**, LWC, object metadata |
sf-lwc-engineer |
**/lwc/**, **/aura/**, **/staticresources/** |
Apex, metadata XML |
sf-metadata-engineer |
**/objects/**, **/permissionsets/**, **/flows/**, **/layouts/**, **/flexipages/**, **/labels/**, Agentforce and Data Cloud definitions |
Apex, LWC, named credentials |
sf-integration-engineer |
**/namedCredentials/**, **/externalCredentials/**, **/externalServiceRegistrations/**, **/externalClientApps/**, **/platformEventChannels/**, classes/integration/** |
everything else |
Each agent writes its own tests as part of the build (*Test.cls, __tests__/*.test.js) and
runs only the narrow check for its own files, for example:
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" jest --files force-app/main/default/lwc/caseList
Hook support during this wave:
post-edit-checkclaims every written path for the editing agent and records it in the session touch list.pre-edit-guarddenies a write to a path another live agent claimed, and denies cross-slice writes with the owner's name in the reason.subagent-stop-releasefrees the claims when an agent finishes, so wave 2 is unobstructed.
Wave 2: parallel checks
Three agents, one batch, over the same tree:
| Agent | Runs | Fails the wave when |
|---|---|---|
sf-test-engineer |
vf-check jest, vf-check apex --target-org <dev> |
coverage below apexOrgCoverageMin/apexClassCoverageMin/jestCoverageMin, or any test fails |
sf-quality-gate |
vf-check static |
a finding at or above analyzerFailSeverity |
sf-security-reviewer |
read-only review against skill sf-security-model |
missing CRUD/FLS or sharing enforcement, secret in source, injection path |
The wave gate is the composite local check:
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" local --changed
local = format + lint + analyzer + jest. It needs no org, which is why it is the gate
that runs on every turn: the stop-local-gate hook re-runs the configured gate over the files
touched in the session and refuses to end the turn while it fails.
Fix rule: failures are fixed at the source by the owning wave-1 agent. Thresholds are never lowered, assertions are never weakened, and findings are never suppressed to pass a gate.
Wave 3: serial deploy
One agent, one org at a time, always validate before deploy.
# 1. check-only validation, runs the tests in the target org without committing anything
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" deploy-validate --target-org acme-uat
# 2. deploy the already-validated job, no re-run of tests
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" deploy-quick --target-org acme-uat
deploy-validate records the job id in .vibeforce/state/deploy-jobs.json; deploy-quick
refuses to run without a matching validated job for that org. For production aliases the
pre-bash-guard hook denies sf project deploy start outright and forces the
validate + quick-deploy path; the direct command becomes available only with an explicit human
decision (VF_ALLOW_PROD=1). Details in skill sf-deployment-strategies.
Promotion order is fixed: dev -> integration -> uat -> prod. A wave-3 failure never gets
"fixed forward" in the target org by hand; it goes back to wave 1.
Wave 4: parallel post-deploy verification
The deploy succeeding is not the story working. Two agents, one batch:
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" smoke --target-org acme-uat
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" apex --target-org acme-uat --tests CaseEscalationServiceTest
sf-org-verifier runs the smoke probes (deploy report, anonymous-Apex probes, verification
queries, limits, error-log scan) and collects diagnostics into .vibeforce/reports/.
sf-test-engineer runs the targeted org test set. The verdict table and the forward-fix versus
rollback decision are covered in skill sf-post-deploy-verification and in
references/failure-and-rollback.md.
Gate and hook map
| Gate | Command | Enforced by | Blocks |
|---|---|---|---|
| Baseline clean | vf-check static --changed |
wave 0 | starting on a dirty tree |
| Local gate | vf-check local --changed |
stop-local-gate hook, wave 2 |
ending a turn with failing source |
| Org test gate | vf-check apex --target-org <alias> |
wave 2/4 | deploying untested behaviour |
| Validation gate | vf-check deploy-validate |
wave 3, pre-bash-guard |
unvalidated production deploys |
| Verification gate | vf-check verify |
wave 4 | declaring done without org evidence |
| Ownership | claim file in .vibeforce/state/ownership.json |
pre-edit-guard, post-edit-check |
two agents writing one file |
| Secret guard | content patterns | pre-edit-guard |
credentials entering the repo |
| Destructive guard | command patterns | pre-bash-guard |
prod data/metadata deletion |
Hook modes: off, minimal (deny-only guards), standard (default: guards + advisory notes +
stop gate), strict (also denies deploying without a passing local gate). Set
hooks.mode in .vibeforce/config.json or VF_HOOK_MODE per shell.
Anti-patterns
| Anti-pattern | Why it breaks | Instead |
|---|---|---|
| Dispatching wave-1 agents one by one | no parallelism, doubles wall clock, invites drift between slices | one batch, disjoint paths |
| Letting two agents share a file "carefully" | last write wins, silent loss; the hook denies it anyway | split the file or serialise that edit through one owner |
Running vf-check local inside wave 1 |
fails on a sibling's half-written file, agents block each other | check in wave 2 |
| Negotiating a method signature mid-wave | both sides stall or diverge | decide it in wave 0, write it to the contract |
sf project deploy start to production |
no validated job, no quick deploy, full test run in the release window | validate + quick deploy |
| Treating deploy success as done | metadata deploys fine and the feature still fails on permissions or data | wave 4 |
| Lowering a gate threshold to finish | the gate stops meaning anything | fix the source, or get an explicit human decision |
| Skipping wave 0 for a "small" story | the small story turns out to touch a trigger, a flow and a permission set | scout first; it is cheap |
Verification
# hook engine behaves as specified (runs in this repository)
node --test tests/
# the gate the Stop hook runs, on the current change set
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" local --changed
# state the orchestrator relies on
cat .vibeforce/state/contract.md
jq . .vibeforce/state/ownership.json
jq '.jobs[-1]' .vibeforce/state/deploy-jobs.json
ls -1 .vibeforce/reports | tail -5
A wave is complete when: its gate returned exit 0, every owned path is released, and the hand-off report lists changed files, published contracts, check results and residual risk.
References
references/wave-playbook.md- per-wave dispatch briefs, inputs, hand-off formats.references/ownership-matrix.md- path-to-agent map, claim lifecycle, conflict resolution.references/gates-and-hooks.md- every hook, every mode, exit codes, escape hatches.references/contract-file.md- contract template, decision log, worked examples.references/failure-and-rollback.md- abort conditions per wave, forward-fix vs rollback.references/external-skills.md- where Salesforce-authored plugins fit in the waves, who owns generated metadata, DevOps Center versus CLI promotion.
Related skills: sf-minimal-change, sf-deployment-strategies, sf-post-deploy-verification,
sf-apex-testing, sf-lwc-jest-testing, sf-code-analyzer-quality, sf-security-model,
sf-cli-operations, sf-project-structure, sf-agentforce-development, sf-data-cloud.