mirror of
https://github.com/Jaganpro/sf-skills.git
synced 2026-09-19 07:52:00 +08:00
2052 lines
91 KiB
HTML
2052 lines
91 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>From Days to Minutes — Agentic Automation of the Agentforce Agent Development Lifecycle</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,400;0,600;0,700;0,800;1,400&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<style>
|
||
/* ═══════════════════════ Custom Properties ═══════════════════════ */
|
||
:root {
|
||
--font-display: 'Crimson Pro', Georgia, 'Times New Roman', serif;
|
||
--font-body: 'IBM Plex Sans', system-ui, sans-serif;
|
||
--font-mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
|
||
--bg: #faf8f5;
|
||
--surface: #ffffff;
|
||
--surface-elevated: #ffffff;
|
||
--border: rgba(0,0,0,0.08);
|
||
--border-bright: rgba(0,0,0,0.15);
|
||
--text: #1a1f36;
|
||
--text-dim: #6b7280;
|
||
--text-bright: #0f1729;
|
||
--accent: #1e3a5f;
|
||
--accent-dim: rgba(30,58,95,0.08);
|
||
--gold: #d4a73a;
|
||
--gold-dim: rgba(212,167,58,0.08);
|
||
--teal: #0d7377;
|
||
--teal-dim: rgba(13,115,119,0.08);
|
||
--amber: #b45309;
|
||
--amber-dim: rgba(180,83,9,0.08);
|
||
--green: #2a6041;
|
||
--green-dim: rgba(42,96,65,0.08);
|
||
--red: #8b2c3c;
|
||
--red-dim: rgba(139,44,60,0.08);
|
||
--code-bg: #edeae5;
|
||
}
|
||
@media (prefers-color-scheme: dark) {
|
||
:root {
|
||
--bg: #0d1117;
|
||
--surface: #161b22;
|
||
--surface-elevated: #1c2333;
|
||
--border: rgba(255,255,255,0.06);
|
||
--border-bright: rgba(255,255,255,0.12);
|
||
--text: #e6edf3;
|
||
--text-dim: #8b949e;
|
||
--text-bright: #f0f3f9;
|
||
--accent: #5b9bd5;
|
||
--accent-dim: rgba(91,155,213,0.10);
|
||
--gold: #e8b84a;
|
||
--gold-dim: rgba(232,184,74,0.10);
|
||
--teal: #2dd4bf;
|
||
--teal-dim: rgba(45,212,191,0.10);
|
||
--amber: #fbbf24;
|
||
--amber-dim: rgba(251,191,36,0.10);
|
||
--green: #34d399;
|
||
--green-dim: rgba(52,211,153,0.10);
|
||
--red: #f87171;
|
||
--red-dim: rgba(248,113,113,0.10);
|
||
--code-bg: #1c2128;
|
||
}
|
||
}
|
||
|
||
/* ═══════════════════════ Reset & Base ═══════════════════════ */
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
font-family: var(--font-body);
|
||
font-size: 16px;
|
||
line-height: 1.7;
|
||
color: var(--text);
|
||
background: var(--bg);
|
||
background-image:
|
||
radial-gradient(ellipse at 20% 0%, rgba(30,58,95,0.04) 0%, transparent 60%),
|
||
radial-gradient(ellipse at 80% 100%, rgba(212,167,58,0.03) 0%, transparent 50%);
|
||
background-attachment: fixed;
|
||
padding: 32px 40px;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
|
||
a:hover { color: var(--gold); }
|
||
|
||
/* ═══════════════════════ Grid Layout ═══════════════════════ */
|
||
.wrap {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
display: grid;
|
||
grid-template-columns: 180px 1fr;
|
||
gap: 0 40px;
|
||
}
|
||
.main { min-width: 0; }
|
||
|
||
/* ═══════════════════════ TOC Sidebar ═══════════════════════ */
|
||
.toc {
|
||
position: sticky;
|
||
top: 24px;
|
||
align-self: start;
|
||
padding: 14px 0;
|
||
grid-row: 1 / -1;
|
||
max-height: calc(100dvh - 48px);
|
||
overflow-y: auto;
|
||
}
|
||
.toc::-webkit-scrollbar { width: 3px; }
|
||
.toc::-webkit-scrollbar-thumb { background: var(--surface-elevated); border-radius: 2px; }
|
||
.toc-title {
|
||
font-family: var(--font-mono);
|
||
font-size: 9px;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 2px;
|
||
color: var(--text-dim);
|
||
padding: 0 0 10px;
|
||
margin-bottom: 8px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.toc a {
|
||
display: block;
|
||
font-size: 11px;
|
||
color: var(--text-dim);
|
||
text-decoration: none;
|
||
padding: 4px 8px;
|
||
border-radius: 5px;
|
||
border-left: 2px solid transparent;
|
||
transition: all 0.15s;
|
||
line-height: 1.4;
|
||
margin-bottom: 1px;
|
||
}
|
||
.toc a:hover { color: var(--text); background: var(--accent-dim); }
|
||
.toc a.active { color: var(--text); border-left-color: var(--gold); font-weight: 600; }
|
||
|
||
/* ═══════════════════════ Typography ═══════════════════════ */
|
||
h1, h2, h3, h4 { font-family: var(--font-display); color: var(--text-bright); }
|
||
h1 { font-size: clamp(32px, 5vw, 42px); font-weight: 800; line-height: 1.15; letter-spacing: -0.5px; margin-bottom: 16px; }
|
||
h2 { font-size: 28px; font-weight: 700; line-height: 1.2; margin: 56px 0 20px; }
|
||
h3 { font-size: 22px; font-weight: 700; line-height: 1.3; margin: 40px 0 14px; }
|
||
h4 { font-size: 18px; font-weight: 700; line-height: 1.3; margin: 28px 0 10px; }
|
||
p { margin-bottom: 1.2em; }
|
||
strong { font-weight: 600; }
|
||
ul, ol { padding-left: 1.5em; margin-bottom: 1.2em; }
|
||
li { margin-bottom: 0.4em; }
|
||
li code, p code {
|
||
font-family: var(--font-mono);
|
||
font-size: 0.88em;
|
||
background: var(--accent-dim);
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
}
|
||
hr { border: none; height: 1px; background: var(--border); margin: 48px 0; }
|
||
|
||
/* ═══════════════════════ Section Headers ═══════════════════════ */
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
margin: 56px 0 20px;
|
||
scroll-margin-top: 24px;
|
||
animation: fadeUp 0.4s ease-out both;
|
||
animation-delay: calc(var(--i, 0) * 0.06s);
|
||
}
|
||
.section__num {
|
||
flex-shrink: 0;
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 50%;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
font-family: var(--font-mono);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
.section-header h2 { margin: 0; }
|
||
|
||
/* ═══════════════════════ Hero ═══════════════════════ */
|
||
.hero {
|
||
padding: 40px 0 32px;
|
||
animation: fadeUp 0.5s ease-out both;
|
||
}
|
||
.hero__subtitle {
|
||
font-family: var(--font-display);
|
||
font-size: 20px;
|
||
font-style: italic;
|
||
color: var(--text-dim);
|
||
margin-bottom: 24px;
|
||
}
|
||
.hero__meta {
|
||
font-size: 14px;
|
||
color: var(--text-dim);
|
||
line-height: 1.6;
|
||
}
|
||
.hero__meta strong { color: var(--text); }
|
||
.hero__meta a { font-size: 14px; }
|
||
|
||
/* ═══════════════════════ Lead & Pull Quote ═══════════════════════ */
|
||
.lead {
|
||
font-size: 19px;
|
||
line-height: 1.6;
|
||
color: var(--text-bright);
|
||
margin-bottom: 32px;
|
||
}
|
||
.pullquote {
|
||
margin: 48px 0;
|
||
padding-left: 24px;
|
||
border-left: 3px solid var(--gold);
|
||
}
|
||
.pullquote p {
|
||
font-family: var(--font-display);
|
||
font-size: 22px;
|
||
font-style: italic;
|
||
line-height: 1.4;
|
||
color: var(--text-bright);
|
||
margin: 0;
|
||
}
|
||
|
||
/* ═══════════════════════ Callout ═══════════════════════ */
|
||
.callout {
|
||
padding: 16px 20px;
|
||
border-radius: 8px;
|
||
border-left: 4px solid var(--callout-border);
|
||
background: var(--callout-bg);
|
||
margin: 20px 0;
|
||
font-size: 15px;
|
||
}
|
||
.callout--info { --callout-border: var(--accent); --callout-bg: var(--accent-dim); }
|
||
.callout--warning { --callout-border: var(--amber); --callout-bg: var(--amber-dim); }
|
||
.callout--success { --callout-border: var(--green); --callout-bg: var(--green-dim); }
|
||
.callout p { margin-bottom: 0.6em; }
|
||
.callout p:last-child { margin-bottom: 0; }
|
||
|
||
/* ═══════════════════════ Code Blocks ═══════════════════════ */
|
||
.code-block {
|
||
font-family: var(--font-mono);
|
||
font-size: 13px;
|
||
line-height: 1.55;
|
||
background: var(--code-bg);
|
||
border: 1px solid var(--border-bright);
|
||
border-radius: 8px;
|
||
padding: 20px 24px;
|
||
overflow-x: visible;
|
||
white-space: pre-wrap;
|
||
overflow-wrap: break-word;
|
||
margin: 16px 0 20px;
|
||
}
|
||
.code-block--scroll { max-height: 500px; overflow-y: auto; }
|
||
.code-label {
|
||
display: inline-block;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
color: var(--text-dim);
|
||
margin-bottom: 6px;
|
||
}
|
||
.code-file {
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
margin: 16px 0 20px;
|
||
}
|
||
.code-file__header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 10px 16px;
|
||
background: var(--surface);
|
||
border-bottom: 1px solid var(--border);
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
}
|
||
.code-file__body {
|
||
font-family: var(--font-mono);
|
||
font-size: 13px;
|
||
line-height: 1.55;
|
||
padding: 16px;
|
||
background: var(--surface-elevated);
|
||
white-space: pre-wrap;
|
||
overflow-wrap: break-word;
|
||
max-height: 600px;
|
||
overflow-y: auto;
|
||
margin: 0;
|
||
}
|
||
|
||
/* ═══════════════════════ Data Tables ═══════════════════════ */
|
||
.table-wrap { overflow-x: auto; margin: 20px 0; border-radius: 8px; border: 1px solid var(--border); }
|
||
.data-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
.data-table th {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
color: var(--text-dim);
|
||
text-align: left;
|
||
padding: 12px 16px;
|
||
border-bottom: 2px solid var(--border-bright);
|
||
background: var(--surface);
|
||
position: sticky; top: 0; z-index: 1;
|
||
}
|
||
.data-table td {
|
||
padding: 10px 16px;
|
||
border-bottom: 1px solid var(--border);
|
||
vertical-align: top;
|
||
}
|
||
.data-table tbody tr:nth-child(even) td {
|
||
background: color-mix(in srgb, var(--surface) 95%, var(--accent) 5%);
|
||
}
|
||
.data-table tbody tr:hover td { background: var(--accent-dim); }
|
||
.data-table tfoot td {
|
||
font-weight: 600;
|
||
border-top: 2px solid var(--border-bright);
|
||
border-bottom: none;
|
||
background: var(--surface);
|
||
padding: 12px 16px;
|
||
}
|
||
.data-table code {
|
||
font-family: var(--font-mono);
|
||
font-size: 0.85em;
|
||
background: var(--accent-dim);
|
||
padding: 2px 5px;
|
||
border-radius: 3px;
|
||
}
|
||
.data-table .cell-desc {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
margin-top: 2px;
|
||
}
|
||
/* Color-coded table accents */
|
||
.table-wrap[data-accent="accent"] { border-left: 3px solid var(--accent); }
|
||
.table-wrap[data-accent="red"] { border-left: 3px solid var(--red); }
|
||
.table-wrap[data-accent="teal"] { border-left: 3px solid var(--teal); }
|
||
.table-wrap[data-accent="green"] { border-left: 3px solid var(--green); }
|
||
.table-wrap[data-accent="gold"] { border-left: 3px solid var(--gold); }
|
||
.table-wrap[data-accent="amber"] { border-left: 3px solid var(--amber); }
|
||
|
||
.table-wrap[data-accent="accent"] .data-table th { color: var(--accent); }
|
||
.table-wrap[data-accent="red"] .data-table th { color: var(--red); }
|
||
.table-wrap[data-accent="teal"] .data-table th { color: var(--teal); }
|
||
.table-wrap[data-accent="green"] .data-table th { color: var(--green); }
|
||
.table-wrap[data-accent="gold"] .data-table th { color: var(--gold); }
|
||
.table-wrap[data-accent="amber"] .data-table th { color: var(--amber); }
|
||
|
||
.badge { display: inline-block; font-family: var(--font-mono); font-size: 11px; font-weight: 500; padding: 2px 8px; border-radius: 4px; }
|
||
.badge--yes { background: var(--green-dim); color: var(--green); }
|
||
.badge--partial { background: var(--amber-dim); color: var(--amber); }
|
||
|
||
/* ═══════════════════════ ve-card ═══════════════════════ */
|
||
.ve-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 16px 20px;
|
||
margin: 16px 0;
|
||
animation: fadeUp 0.4s ease-out both;
|
||
animation-delay: calc(var(--i, 0) * 0.06s);
|
||
}
|
||
.ve-card--hero {
|
||
background: color-mix(in srgb, var(--surface) 92%, var(--accent) 8%);
|
||
box-shadow: 0 4px 20px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.04);
|
||
border-color: color-mix(in srgb, var(--border) 50%, var(--accent) 50%);
|
||
}
|
||
.ve-card--elevated {
|
||
background: var(--surface-elevated);
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
|
||
}
|
||
.ve-card--recessed {
|
||
background: color-mix(in srgb, var(--bg) 70%, var(--surface) 30%);
|
||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.06);
|
||
}
|
||
|
||
/* ═══════════════════════ Mermaid ═══════════════════════ */
|
||
.mermaid-wrap {
|
||
position: relative;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 32px 24px;
|
||
overflow: auto;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
min-height: 400px;
|
||
margin: 20px 0;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: var(--border) transparent;
|
||
cursor: grab;
|
||
}
|
||
.mermaid-wrap.is-panning { cursor: grabbing; user-select: none; }
|
||
.mermaid-wrap::-webkit-scrollbar { width: 6px; height: 6px; }
|
||
.mermaid-wrap::-webkit-scrollbar-track { background: transparent; }
|
||
.mermaid-wrap::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||
.mermaid-wrap--tall { min-height: 700px; }
|
||
.mermaid-wrap .mermaid { zoom: 1.4; }
|
||
.zoom-controls {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
display: flex;
|
||
gap: 2px;
|
||
z-index: 10;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
padding: 2px;
|
||
}
|
||
.zoom-controls button {
|
||
width: 28px;
|
||
height: 28px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-dim);
|
||
font-family: var(--font-mono);
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background 0.15s, color 0.15s;
|
||
}
|
||
.zoom-controls button:hover { background: var(--border); color: var(--text); }
|
||
.mermaid .nodeLabel { color: var(--text) !important; }
|
||
.mermaid .edgeLabel { color: var(--text-dim) !important; background-color: var(--bg) !important; }
|
||
.mermaid .edgeLabel rect { fill: var(--bg) !important; }
|
||
.mermaid .nodeLabel { font-family: var(--font-body) !important; font-size: 16px !important; }
|
||
.mermaid .edgeLabel { font-family: var(--font-mono) !important; font-size: 13px !important; }
|
||
|
||
/* ═══════════════════════ Fix Loop Visual ═══════════════════════ */
|
||
.fix-loop { display: flex; flex-direction: column; align-items: center; gap: 0; margin: 24px 0; }
|
||
.fix-loop__step {
|
||
padding: 10px 24px; border-radius: 8px; font-weight: 600; font-size: 15px;
|
||
font-family: var(--font-body); text-align: center; color: #fff; line-height: 1.4;
|
||
}
|
||
.fix-loop__arrow {
|
||
width: 2px; height: 28px; background: var(--border-bright); position: relative;
|
||
}
|
||
.fix-loop__arrow::after {
|
||
content: ''; position: absolute; bottom: -4px; left: 50%; transform: translateX(-50%);
|
||
border-left: 5px solid transparent; border-right: 5px solid transparent;
|
||
border-top: 6px solid var(--border-bright);
|
||
}
|
||
.fix-loop__grid {
|
||
display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
|
||
width: 100%; max-width: 720px;
|
||
}
|
||
.fix-loop__col-label {
|
||
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
|
||
color: var(--text-dim); padding: 0 0 4px 6px; grid-column: span 1;
|
||
}
|
||
.fix-loop__cat {
|
||
border-left: 3px solid var(--accent); padding: 10px 14px;
|
||
background: var(--surface); border-radius: 6px; font-size: 13px;
|
||
font-family: var(--font-body); line-height: 1.4;
|
||
}
|
||
.fix-loop__cat--teal { border-left-color: var(--teal); }
|
||
.fix-loop__cat code { font-family: var(--font-mono); font-size: 12px; font-weight: 600; }
|
||
.fix-loop__paths {
|
||
width: 100%; max-width: 720px; background: var(--surface);
|
||
border: 1px solid var(--border); border-radius: 8px; padding: 16px 20px;
|
||
font-size: 13px; line-height: 1.6; font-family: var(--font-body);
|
||
}
|
||
.fix-loop__paths strong { font-weight: 600; }
|
||
.fix-loop__decision {
|
||
width: 100%; max-width: 720px; background: var(--surface);
|
||
border: 2px dashed var(--border-bright); border-radius: 8px; padding: 16px 20px;
|
||
font-size: 13px; line-height: 1.7; font-family: var(--font-body); text-align: center;
|
||
}
|
||
|
||
/* ═══════════════════════ Pipeline Visual ═══════════════════════ */
|
||
.pipeline { display: flex; align-items: center; justify-content: center; gap: 0; margin: 20px 0; flex-wrap: wrap; }
|
||
.pipeline__stage {
|
||
padding: 14px 24px; border-radius: 8px; text-align: center;
|
||
background: var(--surface); border: 2px solid var(--border-bright);
|
||
font-family: var(--font-body); line-height: 1.4; min-width: 140px;
|
||
}
|
||
.pipeline__stage strong { display: block; font-size: 15px; font-weight: 600; }
|
||
.pipeline__stage span { font-size: 12px; color: var(--text-dim); }
|
||
.pipeline__arrow {
|
||
font-size: 20px; color: var(--border-bright); padding: 0 12px;
|
||
font-family: var(--font-mono); line-height: 1;
|
||
}
|
||
|
||
/* ═══════════════════════ Prism.js Overrides ═══════════════════════ */
|
||
code[class*="language-"], pre[class*="language-"] {
|
||
text-shadow: none !important;
|
||
font-family: var(--font-mono) !important;
|
||
}
|
||
pre[class*="language-"] { background: var(--code-bg) !important; margin: 0 !important; padding: 0 !important; }
|
||
.code-block[class*="language-"] { padding: 20px 24px !important; }
|
||
.code-file__body[class*="language-"] { padding: 16px !important; }
|
||
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: var(--text-dim); }
|
||
.token.punctuation { color: var(--text); }
|
||
.token.property, .token.tag, .token.boolean, .token.number { color: var(--teal); }
|
||
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin { color: var(--green); }
|
||
.token.operator, .token.entity, .token.url { color: var(--amber); }
|
||
.token.atrule, .token.attr-value, .token.keyword { color: var(--accent); }
|
||
.token.function, .token.class-name { color: var(--red); }
|
||
.token.important, .token.bold { font-weight: bold; }
|
||
.token.italic { font-style: italic; }
|
||
|
||
/* ═══════════════════════ Animations ═══════════════════════ */
|
||
@keyframes fadeUp {
|
||
from { opacity: 0; transform: translateY(12px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*, *::before, *::after {
|
||
animation-duration: 0.01ms !important;
|
||
animation-delay: 0ms !important;
|
||
transition-duration: 0.01ms !important;
|
||
}
|
||
}
|
||
|
||
/* ═══════════════════════ Mobile ═══════════════════════ */
|
||
@media (max-width: 1000px) {
|
||
.wrap { grid-template-columns: 1fr; padding-top: 0; }
|
||
body { padding: 0 16px 32px; }
|
||
.toc {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 200;
|
||
max-height: none;
|
||
display: flex;
|
||
gap: 4px;
|
||
align-items: center;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
background: var(--bg);
|
||
border-bottom: 1px solid var(--border);
|
||
padding: 10px 0;
|
||
margin: 0 -16px;
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
grid-row: auto;
|
||
}
|
||
.toc::-webkit-scrollbar { display: none; }
|
||
.toc-title { display: none; }
|
||
.toc a {
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
border-left: none;
|
||
border-bottom: 2px solid transparent;
|
||
border-radius: 4px 4px 0 0;
|
||
padding: 6px 10px;
|
||
font-size: 10px;
|
||
}
|
||
.toc a.active { border-left: none; border-bottom-color: var(--gold); background: var(--surface); }
|
||
.main { padding-top: 20px; }
|
||
.section-header, h2, h3 { scroll-margin-top: 52px; }
|
||
.code-block, .code-file__body { font-size: 12px; }
|
||
.mermaid-wrap .mermaid { zoom: 1; }
|
||
}
|
||
|
||
/* ═══════════════════════ Print ═══════════════════════ */
|
||
@media print {
|
||
/* ── Force light-mode CSS variables ── */
|
||
:root {
|
||
--bg: #ffffff;
|
||
--surface: #ffffff;
|
||
--surface-elevated: #ffffff;
|
||
--border: rgba(0,0,0,0.08);
|
||
--border-bright: rgba(0,0,0,0.15);
|
||
--text: #1a1f36;
|
||
--text-dim: #6b7280;
|
||
--text-bright: #0f1729;
|
||
--accent: #1e3a5f;
|
||
--accent-dim: rgba(30,58,95,0.08);
|
||
--gold: #d4a73a;
|
||
--gold-dim: rgba(212,167,58,0.08);
|
||
--teal: #0d7377;
|
||
--teal-dim: rgba(13,115,119,0.08);
|
||
--amber: #b45309;
|
||
--amber-dim: rgba(180,83,9,0.08);
|
||
--green: #2a6041;
|
||
--green-dim: rgba(42,96,65,0.08);
|
||
--red: #8b2c3c;
|
||
--red-dim: rgba(139,44,60,0.08);
|
||
--code-bg: #f5f3f0;
|
||
}
|
||
|
||
/* ── Layout ── */
|
||
body {
|
||
background: #fff;
|
||
background-image: none;
|
||
color: var(--text);
|
||
padding: 0;
|
||
font-size: 11.5pt;
|
||
line-height: 1.65;
|
||
}
|
||
.wrap { display: block; }
|
||
.toc, .zoom-controls { display: none; }
|
||
|
||
/* ── Typography scale ── */
|
||
h1 { font-size: 26pt; }
|
||
h2 { font-size: 20pt; }
|
||
h3 { font-size: 16pt; }
|
||
h4 { font-size: 13pt; }
|
||
.lead { font-size: 13pt; }
|
||
.pullquote { font-size: 15pt; }
|
||
.callout { font-size: 10.5pt; }
|
||
|
||
/* ── Code blocks — visually subordinate to body text ── */
|
||
.code-block, .code-file__body {
|
||
font-size: 10pt;
|
||
padding: 14px 18px;
|
||
line-height: 1.5;
|
||
}
|
||
.code-file__header {
|
||
font-size: 8.5pt;
|
||
padding: 6px 18px;
|
||
}
|
||
|
||
/* ── Tables ── */
|
||
.data-table td { font-size: 10pt; border-color: #ccc; }
|
||
.data-table th { font-size: 8pt; background: #f5f5f5; color: #333; border-color: #999; }
|
||
.data-table tbody tr:nth-child(even) td { background: #fafafa; }
|
||
.data-table tbody tr:hover td { background: inherit; }
|
||
.data-table tfoot td { background: #f5f5f5; border-color: #999; }
|
||
.table-wrap { border-color: #ccc; }
|
||
|
||
/* ── Mermaid diagrams — fill container width ── */
|
||
.mermaid-wrap { border: 1px solid #ccc; break-inside: avoid; min-height: auto; }
|
||
.mermaid-wrap .mermaid { zoom: 1; width: 100%; }
|
||
.mermaid-wrap .mermaid svg {
|
||
width: 100% !important;
|
||
max-width: 100% !important;
|
||
height: auto !important;
|
||
}
|
||
/* nodeLabel/edgeLabel: no override — SVG width:100% scales proportionally */
|
||
|
||
/* ── Cards & components ── */
|
||
.code-block, .code-file { break-inside: avoid; border-color: #ccc; }
|
||
.badge { border: 1px solid #999; }
|
||
.ve-card { break-inside: avoid; box-shadow: none; border-color: #ccc; }
|
||
.fix-loop { break-inside: avoid; }
|
||
.fix-loop__step { color: #fff !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||
.fix-loop__cat { border-color: #666; background: #f9f9f9; }
|
||
.fix-loop__paths, .fix-loop__decision { background: #f9f9f9; border-color: #ccc; }
|
||
.pipeline { break-inside: avoid; }
|
||
.pipeline__stage { background: #f9f9f9; border-color: #ccc; }
|
||
|
||
/* ── Page flow ── */
|
||
.section-header { break-after: avoid; }
|
||
h2, h3 { break-after: avoid; }
|
||
|
||
/* ── Links — prevent long URLs from overflowing ── */
|
||
a { color: #000; text-decoration: underline; }
|
||
a[href]::after { content: " (" attr(href) ")"; font-size: 0.75em; color: #666; word-break: break-all; }
|
||
.hero__meta a[href]::after, .callout a[href]::after { content: ""; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="wrap">
|
||
|
||
<!-- ═══════════════════════ TOC ═══════════════════════ -->
|
||
<nav class="toc" id="toc">
|
||
<div class="toc-title">Contents</div>
|
||
<a href="#abstract">Abstract</a>
|
||
<a href="#s1">1. The Problem</a>
|
||
<a href="#s2">2. The Solution</a>
|
||
<a href="#s3">3. Running Example</a>
|
||
<a href="#s4">4. Stage Deep Dive</a>
|
||
<a href="#s5">5. Time Comparison</a>
|
||
<a href="#s6">6. Architecture</a>
|
||
<a href="#s7">7. Conclusion</a>
|
||
</nav>
|
||
|
||
<!-- ═══════════════════════ MAIN ═══════════════════════ -->
|
||
<div class="main">
|
||
|
||
<!-- ─────────────── Hero ─────────────── -->
|
||
<div class="hero" style="--i:0">
|
||
<h1>From Days to Minutes: Agentic Automation of the Agentforce Agent Development Lifecycle</h1>
|
||
<p class="hero__subtitle">How Open-Source AI Skills Replace Manual Agent Builder Workflows</p>
|
||
<div class="hero__meta">
|
||
<strong>Author:</strong> Jag Valaiyapathy<br>
|
||
Senior Forward Deployed Engineer, Salesforce Certified Technical Architect (CTA)<br>
|
||
<a href="mailto:jvalaiyapathy@salesforce.com">jvalaiyapathy@salesforce.com</a><br><br>
|
||
<strong>Date:</strong> March 2026<br>
|
||
<strong>License:</strong> MIT — <a href="https://github.com/Jaganpro/sf-skills">github.com/Jaganpro/sf-skills</a>
|
||
</div>
|
||
</div>
|
||
|
||
<hr>
|
||
|
||
<!-- ─────────────── Abstract ─────────────── -->
|
||
<div id="abstract" class="section-header" style="--i:1">
|
||
<span class="section__num" style="background:var(--gold);color:#1a1f36;">A</span>
|
||
<h2>Abstract</h2>
|
||
</div>
|
||
|
||
<p class="lead">Building Agentforce agents today is a manual, UI-heavy process: configure in Agent Builder, click Publish, click Activate, open Preview, read trace waterfalls by eye, switch back to the canvas, edit, repeat. Each iteration takes 5–15 minutes. Multiply by dozens of iterations across topics, actions, and edge cases, and a single production-ready agent costs <strong>2–5 days</strong> of developer time.</p>
|
||
|
||
<p>The <strong>sf-skills</strong> open-source toolkit (19 skills, MIT license) automates this entire lifecycle using AI coding agents — Claude Code, Codex, Gemini CLI, and 40+ others — orchestrating the Salesforce CLI (<code>sf</code>) with <strong>zero MCP tool calls</strong>. From requirements generation through production deployment, the agentic loop compresses that 2–5 day cycle to <strong>1–3 hours</strong>: a <strong>90%+ reduction</strong> in development time.</p>
|
||
|
||
<p>This white paper walks through the complete 7-stage lifecycle using a single running example — the <code>OrderSupport</code> agent — with exact CLI commands, Mermaid flow diagrams, trace analysis recipes, and a defensible time comparison at every stage.</p>
|
||
|
||
<div class="pullquote">
|
||
<p>The agentic loop compresses the 2–5 day agent development cycle to 1–3 hours — a 90%+ reduction in development time, with zero MCP tool calls.</p>
|
||
</div>
|
||
|
||
<hr>
|
||
|
||
<!-- ═══════════════════════ §1 ═══════════════════════ -->
|
||
<div id="s1" class="section-header" style="--i:2">
|
||
<span class="section__num">1</span>
|
||
<h2>The Problem — Manual Agent Development Today</h2>
|
||
</div>
|
||
|
||
<p>Every Agentforce agent built today follows the same manual loop. The developer opens Agent Builder, makes changes in the UI canvas, publishes, activates, tests in Preview, reads traces visually, and goes back to fix issues. This loop repeats for every topic, every action, and every edge case.</p>
|
||
|
||
<h3>The 8 Manual Steps</h3>
|
||
|
||
<div class="table-wrap" data-accent="red">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>Step</th><th>Action</th><th>Where</th><th>Typical Time</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>1</td><td>Write agent in Agent Builder UI</td><td>Canvas / Script</td><td>30–120 min</td></tr>
|
||
<tr><td>2</td><td>Click "Publish" in the UI</td><td>Agent Builder</td><td>2–5 min (wait)</td></tr>
|
||
<tr><td>3</td><td>Click "Activate" in the UI</td><td>Agent Builder</td><td>1–2 min (wait)</td></tr>
|
||
<tr><td>4</td><td>Open Preview, type utterances</td><td>Preview Panel</td><td>5–10 min</td></tr>
|
||
<tr><td>5</td><td>Read trace waterfall visually</td><td>Trace Inspector</td><td>5–15 min</td></tr>
|
||
<tr><td>6</td><td>Identify routing/action/grounding issues by eye</td><td>Mental model</td><td>5–10 min</td></tr>
|
||
<tr><td>7</td><td>Switch back to canvas, make edits</td><td>Agent Builder</td><td>5–20 min</td></tr>
|
||
<tr><td>8</td><td>Repeat from Step 2</td><td>All of the above</td><td>5–15 min/iter</td></tr>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr><td></td><td>TOTAL (first pass + 5 iterations)</td><td></td><td>~2–8 hours</td></tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>The Pain Points</h3>
|
||
|
||
<p><strong>Context switching.</strong> The developer bounces between the Agent Builder canvas, the Preview panel, the Trace Inspector, and sometimes VS Code — each in a different browser tab or application window. Every switch costs cognitive overhead.</p>
|
||
|
||
<p><strong>No programmatic trace access.</strong> Traces are visual waterfalls in the UI. There is no API to query trace steps, no way to assert that a specific action was invoked, no way to batch-check 20 utterances. The developer reads each span by eye, clicks to expand, and mentally reconstructs what happened.</p>
|
||
|
||
<p><strong>Compounding cost.</strong> A single agent might need 5–10 iteration cycles before it routes topics correctly, invokes the right actions, and handles edge cases. Multiply by the number of agents in an org, and the cost is measured in <strong>days to weeks</strong> of developer time.</p>
|
||
|
||
<hr>
|
||
|
||
<!-- ═══════════════════════ §2 ═══════════════════════ -->
|
||
<div id="s2" class="section-header" style="--i:3">
|
||
<span class="section__num">2</span>
|
||
<h2>The Solution — Agentic Development Lifecycle</h2>
|
||
</div>
|
||
|
||
<p>The agentic development lifecycle replaces every manual step with a CLI command orchestrated by an AI coding agent. The agent reads requirements, writes the <code>.agent</code> file, validates it, runs smoke tests via programmatic preview, publishes, runs formal tests, analyzes observability data, and deploys to production — all from the terminal.</p>
|
||
|
||
<h3>Zero MCP Calls</h3>
|
||
|
||
<p>This entire lifecycle operates with <strong>zero MCP tool calls</strong>. Every stage uses the Salesforce CLI (<code>sf</code> commands) invoked via the AI agent's Bash tool. No MCP servers, no custom protocols, no middleware.</p>
|
||
|
||
<p><strong>Why this matters:</strong></p>
|
||
|
||
<ul>
|
||
<li><strong>Composability.</strong> CLI outputs are plain text or JSON — pipe through <code>jq</code>, chain commands with <code>&&</code>, redirect to files. AI agents already know how to use shells.</li>
|
||
<li><strong>Recovery.</strong> If a command fails, re-run it. No state machine to reset, no server to restart, no session to reconnect.</li>
|
||
<li><strong>Portability.</strong> Works with any AI coding agent that has shell access — Claude Code, Codex, Gemini CLI, OpenCode, Amp, Droid CLI — 40+ agents and counting. No agent-specific MCP adapters needed.</li>
|
||
<li><strong>Debuggability.</strong> Every command is visible in the terminal, every trace is a local JSON file. Copy-paste any command to debug manually.</li>
|
||
<li><strong>No infrastructure.</strong> No MCP server process running, no port binding, no WebSocket connections, no authentication handshakes beyond the standard <code>sf org login</code>.</li>
|
||
</ul>
|
||
|
||
<p>The <code>sf</code> CLI handles all Salesforce communication — REST API, Tooling API, Agent Runtime API — through well-tested, versioned endpoints. The AI agent's job is to orchestrate: call the right command at the right time, parse the output, and decide the next step. This is exactly what AI coding agents are optimized for.</p>
|
||
|
||
<h3>Master Lifecycle Pipeline</h3>
|
||
|
||
<p>The lifecycle consists of 7 stages, each handled by a specific skill in the sf-skills toolkit:</p>
|
||
|
||
<div class="mermaid-wrap">
|
||
<div class="zoom-controls">
|
||
<button onclick="zoomDiagram(this, 1.2)" title="Zoom in">+</button>
|
||
<button onclick="zoomDiagram(this, 0.8)" title="Zoom out">−</button>
|
||
<button onclick="resetZoom(this)" title="Reset zoom">↺</button>
|
||
</div>
|
||
<pre class="mermaid">
|
||
graph LR
|
||
S1["Stage 1<br/>Requirements<br/>& Design"]
|
||
S2["Stage 2<br/>Agent Script<br/>Authoring"]
|
||
S3["Stage 3<br/>LSP + CLI<br/>Validation"]
|
||
S35["Stage 3.5<br/>Preview<br/>Smoke Test"]
|
||
S4["Stage 4<br/>Publish<br/>& Activate"]
|
||
S5["Stage 5<br/>Formal<br/>Testing"]
|
||
S6["Stage 6<br/>Observability<br/>& STDM"]
|
||
S7["Stage 7<br/>Production<br/>Deployment"]
|
||
|
||
S1 --> S2
|
||
S2 --> S3
|
||
S3 --> S35
|
||
S35 --> S4
|
||
S4 --> S5
|
||
S5 --> S6
|
||
S6 --> S7
|
||
|
||
S35 -.->|fix loop| S2
|
||
S5 -.->|fix loop| S2
|
||
|
||
style S1 fill:#1e3a5f44,stroke:#1e3a5f,stroke-width:2px
|
||
style S2 fill:#0d737744,stroke:#0d7377,stroke-width:2px
|
||
style S3 fill:#2a604144,stroke:#2a6041,stroke-width:2px
|
||
style S35 fill:#d4a73a44,stroke:#d4a73a,stroke-width:2px
|
||
style S4 fill:#8b2c3c44,stroke:#8b2c3c,stroke-width:2px
|
||
style S5 fill:#3d5a8044,stroke:#3d5a80,stroke-width:2px
|
||
style S6 fill:#0d737744,stroke:#0d7377,stroke-width:2px
|
||
style S7 fill:#34495e44,stroke:#34495e,stroke-width:2px
|
||
</pre>
|
||
</div>
|
||
|
||
<div class="table-wrap" data-accent="accent">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>Stage</th><th>What Happens</th><th>Key Skill</th><th>Key CLI Command</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>1</td><td>Requirements → agent spec YAML</td><td>sf agent generate</td><td><code>sf agent generate agent-spec</code></td></tr>
|
||
<tr><td>2</td><td>AI writes the .agent file</td><td>sf-ai-agentscript</td><td>Write/Edit tools</td></tr>
|
||
<tr><td>3</td><td>LSP auto-validates + CLI check</td><td>sf-ai-agentscript (LSP)</td><td><code>sf agent validate authoring-bundle</code></td></tr>
|
||
<tr><td>3.5</td><td>Preview smoke test (inner loop)</td><td>sf-ai-agentscript</td><td><code>sf agent preview start/send/end</code></td></tr>
|
||
<tr><td>4</td><td>Publish + Activate</td><td>sf-deploy</td><td><code>sf agent publish authoring-bundle</code></td></tr>
|
||
<tr><td>5</td><td>Formal testing (dual-track)</td><td>sf-ai-agentforce-testing</td><td><code>sf agent test run</code> + API</td></tr>
|
||
<tr><td>6</td><td>Observability & STDM analysis</td><td>sf-ai-agentforce-observ.</td><td><code>python3 scripts/cli.py extract</code></td></tr>
|
||
<tr><td>7</td><td>Production deployment pipeline</td><td>sf-deploy</td><td><code>sf agent publish</code> + activate</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<hr>
|
||
|
||
<!-- ═══════════════════════ §3 ═══════════════════════ -->
|
||
<div id="s3" class="section-header" style="--i:4">
|
||
<span class="section__num">3</span>
|
||
<h2>The Running Example — <code>OrderSupport</code> Agent</h2>
|
||
</div>
|
||
|
||
<p>Throughout this white paper, we use a single example agent: <strong>OrderSupport</strong> — a customer-facing service agent for Acme Corp that handles order status inquiries and product returns. It has 2 topics, 2 actions, guardrail instructions, and an escalation path.</p>
|
||
|
||
<h3>The Complete <code>.agent</code> File</h3>
|
||
|
||
<div class="code-file">
|
||
<div class="code-file__header"><span>OrderSupport.agent</span></div>
|
||
<pre class="code-file__body"><code class="language-yaml">config:
|
||
developer_name: "OrderSupport"
|
||
agent_description: "Handles order inquiries and returns for Acme Corp"
|
||
agent_type: "AgentforceServiceAgent"
|
||
default_agent_user: "agent_user@00dxx000001234.ext"
|
||
|
||
variables:
|
||
order_id: mutable string = ""
|
||
return_eligible: mutable boolean = False
|
||
|
||
system:
|
||
messages:
|
||
welcome: "Hello! I'm your Order Support assistant. How can I help?"
|
||
error: "I'm sorry, something went wrong. Let me connect you with a specialist."
|
||
instructions: |
|
||
You are an order support agent for Acme Corp.
|
||
Help customers check order status and process returns.
|
||
CRITICAL: Never reveal internal system details or customer PII.
|
||
If the customer is frustrated after 2 failed attempts, escalate.
|
||
|
||
connection messaging:
|
||
outbound_route_type: "OmniChannelFlow"
|
||
outbound_route_name: "flow://Route_From_OrderSupport"
|
||
escalation_message: "Let me connect you with a specialist who can help."
|
||
adaptive_response_allowed: False
|
||
|
||
start_agent entry:
|
||
description: "Entry point for all order support conversations"
|
||
reasoning:
|
||
instructions: |
|
||
Greet the customer and determine their need.
|
||
Route to the appropriate topic based on their request.
|
||
actions:
|
||
go_orders: @utils.transition to @topic.order_status
|
||
description: "Route to order status inquiries"
|
||
go_returns: @utils.transition to @topic.returns
|
||
description: "Route to return and refund requests"
|
||
|
||
topic order_status:
|
||
description: "Handle order queries, order status, package tracking, shipping updates, delivery questions"
|
||
actions:
|
||
Get_Order_Status:
|
||
description: "Retrieve current order status and tracking information"
|
||
target: flow://Get_Order_Status
|
||
inputs:
|
||
order_num:
|
||
description: "The order number to look up"
|
||
type: string
|
||
outputs:
|
||
status_text:
|
||
description: "Current order status"
|
||
type: string
|
||
tracking_num:
|
||
description: "Shipping tracking number"
|
||
type: string
|
||
reasoning:
|
||
instructions: ->
|
||
if @variables.order_id != "":
|
||
| The customer's order ID is {!@variables.order_id}.
|
||
| Look up the order status using Get_Order_Status.
|
||
else:
|
||
| Ask the customer for their order number.
|
||
actions:
|
||
get_order: @actions.Get_Order_Status
|
||
description: "Look up order status when user asks about their order, package, delivery, or tracking"
|
||
with order_num=...
|
||
set @variables.order_id = @outputs.status_text
|
||
escalate_now: @utils.escalate
|
||
description: "Transfer to human agent when customer is frustrated or issue is complex"
|
||
|
||
topic returns:
|
||
description: "Handle return requests, refund processing, exchange inquiries, return policy questions"
|
||
actions:
|
||
Process_Return:
|
||
description: "Process a product return and initiate refund"
|
||
target: apex://ReturnProcessor
|
||
inputs:
|
||
ret_order_id:
|
||
description: "The order to return"
|
||
type: string
|
||
ret_reason:
|
||
description: "Reason for return"
|
||
type: string
|
||
outputs:
|
||
return_id:
|
||
description: "Generated return authorization ID"
|
||
type: string
|
||
refund_amt:
|
||
description: "Refund amount in dollars"
|
||
type: string
|
||
reasoning:
|
||
instructions: ->
|
||
if @variables.order_id != "" and @variables.return_eligible == True:
|
||
| The customer wants to return order {!@variables.order_id}.
|
||
| Process the return using Process_Return.
|
||
else:
|
||
| Help the customer with their return request.
|
||
| Ask for the order number if not already provided.
|
||
actions:
|
||
process_return: @actions.Process_Return
|
||
description: "Process a return when customer wants to return a product, get a refund, or exchange"
|
||
with ret_order_id=...
|
||
with ret_reason=...
|
||
escalate_now: @utils.escalate
|
||
description: "Transfer to human agent for complex return cases"</code></pre>
|
||
</div>
|
||
|
||
<h3>Key Design Decisions</h3>
|
||
|
||
<ul>
|
||
<li><strong><code>instructions: -></code></strong> (procedural mode) enables <code>if</code>/<code>else</code> conditionals that resolve <em>before</em> the LLM sees the prompt — deterministic routing, not suggestions.</li>
|
||
<li><strong>Two-level action system</strong>: Level 1 definitions (<code>actions:</code> under topic) declare <code>target:</code>, <code>inputs:</code>, <code>outputs:</code>. Level 2 invocations (<code>reasoning.actions:</code>) reference them via <code>@actions.Name</code> and specify <code>with</code>/<code>set</code> clauses.</li>
|
||
<li><strong><code>with param=...</code></strong> triggers LLM slot-filling — the planner extracts values from the conversation.</li>
|
||
<li><strong><code>@utils.escalate</code></strong> is a built-in utility action for human handoff — requires the <code>connection messaging:</code> block.</li>
|
||
</ul>
|
||
|
||
<hr>
|
||
|
||
<!-- ═══════════════════════ §4 ═══════════════════════ -->
|
||
<div id="s4" class="section-header" style="--i:5">
|
||
<span class="section__num">4</span>
|
||
<h2>Stage-by-Stage Deep Dive with CLI Commands</h2>
|
||
</div>
|
||
|
||
<!-- Stage 1 -->
|
||
<h3>Stage 1: Requirements & Design</h3>
|
||
|
||
<p><strong>What happens:</strong> The AI agent generates a structured agent specification from natural-language requirements. The spec defines the agent's role, audience, topics, and behavioral guidelines.</p>
|
||
|
||
<p><strong>CLI command:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">sf agent generate agent-spec \
|
||
--type customer \
|
||
--role "Order Support Representative" \
|
||
--company-name "Acme Corp" \
|
||
--company-description "E-commerce platform for consumer electronics" \
|
||
--tone formal \
|
||
--max-topics 3 \
|
||
--output-file specs/order-support.yaml</code></pre>
|
||
|
||
<p><strong>What this produces:</strong> A YAML specification file that feeds into Stage 2. The spec includes suggested topics, escalation paths, and behavioral guidelines — all derived from the role description and company context.</p>
|
||
|
||
<p><strong>Manual equivalent:</strong> 2–4 hours of requirements gathering, stakeholder interviews, and document drafting.</p>
|
||
|
||
<p><strong>Agentic time:</strong> 15–30 minutes — the AI reads existing documentation, generates the spec, and iterates with the developer.</p>
|
||
|
||
<!-- Stage 2 -->
|
||
<h3>Stage 2: Agent Script Authoring</h3>
|
||
|
||
<p><strong>What happens:</strong> The AI coding agent writes the complete <code>.agent</code> file (shown in Section 3 above) based on the spec from Stage 1. It creates the authoring bundle directory structure and metadata XML.</p>
|
||
|
||
<p><strong>File structure created:</strong></p>
|
||
|
||
<pre class="code-block"><code>force-app/main/default/aiAuthoringBundles/
|
||
└── OrderSupport/
|
||
├── OrderSupport.agent # Agent Script (shown above)
|
||
└── OrderSupport.bundle-meta.xml # Metadata XML</code></pre>
|
||
|
||
<p><strong>The metadata XML:</strong></p>
|
||
|
||
<span class="code-label">xml</span>
|
||
<pre class="code-block"><code class="language-xml"><?xml version="1.0" encoding="UTF-8"?>
|
||
<AiAuthoringBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
||
<bundleType>AGENT</bundleType>
|
||
</AiAuthoringBundle></code></pre>
|
||
|
||
<p><strong>Key skill:</strong> <code>sf-ai-agentscript</code> — knows Agent Script syntax, two-level action system, FSM patterns, and all deployment gotchas.</p>
|
||
|
||
<p><strong>Manual equivalent:</strong> 4–8 hours of writing and configuring in Agent Builder UI.</p>
|
||
|
||
<p><strong>Agentic time:</strong> 10–30 minutes — the AI writes the <code>.agent</code> file in a single pass, following validated syntax patterns from the skill's 100-point scoring rubric.</p>
|
||
|
||
<!-- Stage 3 -->
|
||
<h3>Stage 3: LSP + CLI Validation</h3>
|
||
|
||
<p><strong>What happens:</strong> The Agent Script LSP server validates the <code>.agent</code> file on every save — catching syntax errors, undefined topic references, mixed indentation, and invalid <code>default_agent_user</code> values. The CLI then performs server-side validation against the target org.</p>
|
||
|
||
<p><strong>Automatic LSP validation (on save):</strong></p>
|
||
|
||
<p>The LSP engine validates:</p>
|
||
<ul>
|
||
<li>Mixed tabs/spaces → <code>SyntaxError</code></li>
|
||
<li>Lowercase booleans (<code>true</code> instead of <code>True</code>) → <code>SyntaxError</code></li>
|
||
<li>Undefined topic references (<code>@topic.nonexistent</code>) → <code>ValidationError</code></li>
|
||
<li>Missing <code>start_agent</code> block → <code>StructureError</code></li>
|
||
<li>Reserved field names used as variables → <code>ParseError</code></li>
|
||
</ul>
|
||
|
||
<p><strong>CLI validation command:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">sf agent validate authoring-bundle \
|
||
--api-name OrderSupport \
|
||
-o dev \
|
||
--json</code></pre>
|
||
|
||
<p><strong>Expected output (success):</strong></p>
|
||
|
||
<span class="code-label">json</span>
|
||
<pre class="code-block"><code class="language-json">{
|
||
"status": 0,
|
||
"result": {
|
||
"valid": true,
|
||
"warnings": []
|
||
}
|
||
}</code></pre>
|
||
|
||
<p><strong>Manual equivalent:</strong> 30–60 minutes of clicking through the UI, reading error toasts, and fixing issues one at a time.</p>
|
||
|
||
<p><strong>Agentic time:</strong> ~5 seconds — LSP validates instantly on save, CLI validation is a single command.</p>
|
||
|
||
<!-- Stage 3.5 -->
|
||
<h3>Stage 3.5: Preview Smoke Test Loop (The Inner Loop)</h3>
|
||
|
||
<p>This is the <strong>star of the agentic lifecycle</strong> — the tight feedback loop that catches topic routing, action invocation, and grounding issues <em>before</em> the expensive publish-activate cycle.</p>
|
||
|
||
<p><strong>Why this matters:</strong> The <code>--authoring-bundle</code> flag compiles the <code>.agent</code> file server-side <strong>without publishing</strong> — no CustomerWebClient patch, no activation step. This enables <strong>~15-second iteration cycles</strong> (vs ~90 seconds for publish + activate), letting the AI coding agent fix issues in a tight inner loop.</p>
|
||
|
||
<h4>The Sequence Diagram</h4>
|
||
|
||
<div class="mermaid-wrap mermaid-wrap--tall">
|
||
<div class="zoom-controls">
|
||
<button onclick="zoomDiagram(this, 1.2)" title="Zoom in">+</button>
|
||
<button onclick="zoomDiagram(this, 0.8)" title="Zoom out">−</button>
|
||
<button onclick="resetZoom(this)" title="Reset zoom">↺</button>
|
||
</div>
|
||
<pre class="mermaid">
|
||
sequenceDiagram
|
||
participant CC as AI Coding Agent
|
||
participant CLI as sf CLI
|
||
participant ORG as Salesforce Org
|
||
|
||
Note over CC: Derive 4 utterances from .agent file
|
||
|
||
CC->>CLI: sf agent preview start --authoring-bundle OrderSupport
|
||
CLI->>ORG: POST /einstein/ai-agent/v1.1/preview/
|
||
ORG-->>CLI: sessionId: abc-123
|
||
CLI-->>CC: SESSION_ID=abc-123
|
||
|
||
loop For each utterance (4 total)
|
||
CC->>CLI: sf agent preview send --session-id abc-123 --utterance "..."
|
||
CLI->>ORG: POST /preview/send
|
||
ORG-->>CLI: messages, planId
|
||
CLI-->>CC: Response + Plan ID
|
||
end
|
||
|
||
CC->>CLI: sf agent preview end --session-id abc-123
|
||
CLI->>ORG: DELETE session
|
||
ORG-->>CLI: tracesPath
|
||
CLI-->>CC: Traces path
|
||
|
||
Note over CC: Read traces with jq (6 checks)
|
||
|
||
alt All checks pass
|
||
CC->>CC: Proceed to Stage 4 (Publish)
|
||
else Failures found
|
||
CC->>CC: Edit .agent file (fix)
|
||
CC->>CC: LSP auto-validates
|
||
Note over CC: Repeat (max 3 iterations)
|
||
end
|
||
</pre>
|
||
</div>
|
||
|
||
<h4>Full Bash Workflow</h4>
|
||
|
||
<p><strong>Step 1: Start preview session</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">SESSION_ID=$(sf agent preview start \
|
||
--authoring-bundle OrderSupport \
|
||
--target-org dev --json 2>/dev/null \
|
||
| jq -r '.result.sessionId')
|
||
|
||
echo "Session: $SESSION_ID"</code></pre>
|
||
|
||
<p><strong>Step 2: Send smoke test utterances</strong></p>
|
||
|
||
<p>The AI derives utterances from the <code>.agent</code> file itself — one per topic, one for action invocation, one off-topic (guardrail test):</p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">PLAN_IDS=()
|
||
|
||
for UTTERANCE in \
|
||
"Where is my order?" \
|
||
"I want to return this" \
|
||
"Tell me a joke" \
|
||
"My order #12345 is late"; do
|
||
|
||
RESP=$(sf agent preview send \
|
||
--session-id "$SESSION_ID" \
|
||
--utterance "$UTTERANCE" \
|
||
--target-org dev --json 2>/dev/null)
|
||
|
||
PID=$(echo "$RESP" | jq -r '.result.messages[-1].planId')
|
||
PLAN_IDS+=("$PID")
|
||
echo "[$PID] $UTTERANCE → $(echo "$RESP" | jq -r '.result.messages[-1].message' | head -c 80)"
|
||
done</code></pre>
|
||
|
||
<p><strong>Step 3: End session and get traces</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">TRACES_PATH=$(sf agent preview end \
|
||
--session-id "$SESSION_ID" \
|
||
--target-org dev --json 2>/dev/null \
|
||
| jq -r '.result.tracesPath')
|
||
|
||
echo "Traces at: $TRACES_PATH"</code></pre>
|
||
|
||
<h4>The 6 Trace Checks with <code>jq</code></h4>
|
||
|
||
<p>For each plan ID, read the trace file and run these 6 checks:</p>
|
||
|
||
<div class="table-wrap" data-accent="teal">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>#</th><th>What</th><th>jq Command</th><th>Expected</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>1</td><td>Topic Routing</td><td><code style="white-space:pre-wrap">jq '[.steps[] | select(.stepType=="TransitionStep") | .data.to]' "$TRACE"</code></td><td><code>["order_status"]</code></td></tr>
|
||
<tr><td>2</td><td>Action Invocation</td><td><code style="white-space:pre-wrap">jq '[.steps[] | select(.stepType=="FunctionStep") | .data.function]' "$TRACE"</code></td><td><code>["Get_Order_Status"]</code></td></tr>
|
||
<tr><td>3</td><td>Grounding Assessment</td><td><code style="white-space:pre-wrap">jq '[.steps[] | select(.stepType=="ReasoningStep") | .data.groundingAssessment]' "$TRACE"</code></td><td><code>["GROUNDED"]</code></td></tr>
|
||
<tr><td>4</td><td>Safety Score</td><td><code style="white-space:pre-wrap">jq '.steps[] | select(.stepType=="PlannerResponseStep") | .data.safetyScore' "$TRACE"</code></td><td>>= 0.9</td></tr>
|
||
<tr><td>5</td><td>Tool Visibility</td><td><code style="white-space:pre-wrap">jq '[.steps[] | select(.stepType=="EnabledToolsStep") | .data.enabled_tools]' "$TRACE"</code></td><td>includes action names</td></tr>
|
||
<tr><td>6</td><td>Response Quality</td><td><code style="white-space:pre-wrap">jq '.steps[] | select(.stepType=="PlannerResponseStep") | .data.responseText' "$TRACE"</code></td><td>relevant, non-empty</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h4>A Failure → Fix → Re-Run Cycle</h4>
|
||
|
||
<p><strong>Iteration 1: Topic not matched</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">jq '[.steps[] | select(.stepType == "TransitionStep") | .data.to]' "$TRACES_PATH/$PID1.json"
|
||
# Output: [] ← EMPTY! Topic not matched!</code></pre>
|
||
|
||
<p><strong>Diagnosis:</strong> "Where is my order?" didn't route to <code>order_status</code> because the topic description was initially too vague (e.g., <code>"Handle orders"</code>).</p>
|
||
|
||
<p><strong>Fix:</strong> The AI expands the topic description:</p>
|
||
|
||
<span class="code-label">yaml</span>
|
||
<pre class="code-block"><code class="language-yaml"># BEFORE
|
||
topic order_status:
|
||
description: "Handle orders"
|
||
|
||
# AFTER
|
||
topic order_status:
|
||
description: "Handle order queries, order status, package tracking, shipping updates, delivery questions"</code></pre>
|
||
|
||
<p><strong>Iteration 2: Re-run preview</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">SESSION_ID=$(sf agent preview start --authoring-bundle OrderSupport --target-org dev --json 2>/dev/null | jq -r '.result.sessionId')
|
||
RESP=$(sf agent preview send --session-id "$SESSION_ID" --utterance "Where is my order?" --target-org dev --json 2>/dev/null)
|
||
PID=$(echo "$RESP" | jq -r '.result.messages[-1].planId')
|
||
TRACES_PATH=$(sf agent preview end --session-id "$SESSION_ID" --target-org dev --json 2>/dev/null | jq -r '.result.tracesPath')
|
||
|
||
jq '[.steps[] | select(.stepType == "TransitionStep") | .data.to]' "$TRACES_PATH/$PID.json"
|
||
# Output: ["order_status"] ← PASS!</code></pre>
|
||
|
||
<p><strong>Total time for 2 iterations:</strong> ~30 seconds. Manual equivalent: ~20 minutes.</p>
|
||
|
||
<!-- Stage 4 -->
|
||
<h3>Stage 4: Publish & Activate</h3>
|
||
|
||
<p><strong>What happens:</strong> After smoke tests pass, the agent is published to the org and activated for end users.</p>
|
||
|
||
<p><strong>Publish command:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">sf agent publish authoring-bundle \
|
||
--api-name OrderSupport \
|
||
-o dev \
|
||
--json</code></pre>
|
||
|
||
<div class="callout callout--warning">
|
||
<p><strong>Critical:</strong> Use <code>sf agent publish authoring-bundle</code>, NOT <code>sf project deploy start</code>. The metadata API deploy will fail with "Required fields are missing: [BundleType]".</p>
|
||
</div>
|
||
|
||
<p><strong>Expected publish output (4-step process):</strong></p>
|
||
|
||
<pre class="code-block"><code>✔ Validate Bundle ~1-2s
|
||
✔ Publish Agent ~8-10s
|
||
✔ Retrieve Metadata ~5-7s
|
||
✔ Deploy Metadata ~4-6s</code></pre>
|
||
|
||
<p><strong>Activate command:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash"># Manual activation
|
||
sf agent activate --api-name OrderSupport -o dev
|
||
|
||
# CI / deterministic activation of a known BotVersion
|
||
sf agent activate --api-name OrderSupport --version <n> -o dev --json</code></pre>
|
||
|
||
<div class="callout callout--info">
|
||
<p><strong>Note:</strong> <code>sf agent activate</code> now supports <code>--json</code>. If you use <code>--json</code> without <code>--version</code>, the CLI activates the latest agent version. Prefer <code>--version</code> for CI/CD and reproducible rollouts.</p>
|
||
</div>
|
||
|
||
<p><strong>Important:</strong> Publishing does NOT activate. The new BotVersion is created as <code>Inactive</code>. Tests, preview, and end users continue hitting the previously active version until you explicitly activate.</p>
|
||
|
||
<p><strong>Verify activation:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">sf data query --query "SELECT DeveloperName, VersionNumber, Status \
|
||
FROM BotVersion \
|
||
WHERE BotDefinition.DeveloperName = 'OrderSupport' \
|
||
AND Status = 'Active' LIMIT 1" -o dev --json</code></pre>
|
||
|
||
<p><strong>Manual equivalent:</strong> 2–5 minutes of clicking through the UI.</p>
|
||
<p><strong>Agentic time:</strong> ~30 seconds — two CLI commands.</p>
|
||
|
||
<!-- Stage 5 -->
|
||
<h3>Stage 5: Formal Testing (Dual-Track)</h3>
|
||
|
||
<p>After publish and activate, the agent undergoes formal testing via two parallel tracks.</p>
|
||
|
||
<h4>The Dual-Track Architecture</h4>
|
||
|
||
<div class="mermaid-wrap mermaid-wrap--tall">
|
||
<div class="zoom-controls">
|
||
<button onclick="zoomDiagram(this, 1.2)" title="Zoom in">+</button>
|
||
<button onclick="zoomDiagram(this, 0.8)" title="Zoom out">−</button>
|
||
<button onclick="resetZoom(this)" title="Reset zoom">↺</button>
|
||
</div>
|
||
<pre class="mermaid">
|
||
graph TD
|
||
subgraph SMOKE ["Stage 3.5 — Preview Smoke Test (Inner Loop)"]
|
||
direction TB
|
||
S35_1["Derive Utterances<br/>from .agent File"] --> S35_2["sf agent preview start<br/>--authoring-bundle"]
|
||
S35_2 --> S35_3["Loop: sf agent preview send<br/>(4 utterances)"]
|
||
S35_3 --> S35_4["sf agent preview end<br/>→ Get Traces"]
|
||
S35_4 --> S35_5["Run 6 jq<br/>Trace Checks"]
|
||
S35_5 --> S35_CHK{"All<br/>Pass?"}
|
||
S35_CHK -->|No| S35_FIX["Edit .agent File<br/>+ LSP Validates"]
|
||
S35_FIX -->|"Retry (max 3)"| S35_2
|
||
end
|
||
|
||
S35_CHK -->|Yes| START
|
||
START["Agent Published<br/>& Activated"] --> SPLIT{"Testing<br/>Track"}
|
||
|
||
SPLIT -->|"Track 1 - Salesforce Testing Center"| T1_1["T1.1: YAML Test<br/>Spec Creation"]
|
||
SPLIT -->|"Track 2 - Advanced Multi-Turn Testing"| T2_1["T2.1: ECA Credential<br/>Setup"]
|
||
|
||
T1_1 --> T1_2["T1.2: sf agent test create<br/>+ sf agent test run"]
|
||
T1_2 --> T1_3["T1.3: Results<br/>Analysis"]
|
||
|
||
T2_1 --> T2_2["T2.2: Agent Discovery<br/>& Metadata"]
|
||
T2_2 --> T2_3["T2.3: Generate Multi-Turn<br/>Scenarios (6 patterns)"]
|
||
T2_3 --> T2_4["T2.4: Execute via<br/>Agent Runtime API"]
|
||
T2_4 --> T2_5["T2.5: Results<br/>& Scoring"]
|
||
|
||
T1_3 --> MERGE{"All<br/>Passed?"}
|
||
T2_5 --> MERGE
|
||
|
||
MERGE -->|Yes| DONE["Stage 6:<br/>Observability"]
|
||
MERGE -->|No| FIX["Phase C:<br/>Agentic Fix Loop"]
|
||
FIX --> RETEST["Re-publish<br/>& Re-test"]
|
||
RETEST --> MERGE
|
||
|
||
style S35_3 fill:#d4a73a44,stroke:#d4a73a,stroke-width:2px
|
||
style S35_FIX fill:#8b2c3c44,stroke:#8b2c3c,stroke-width:2px
|
||
style START fill:#2a604144,stroke:#2a6041,stroke-width:2px
|
||
style T1_2 fill:#3d5a8044,stroke:#3d5a80,stroke-width:2px
|
||
style T2_4 fill:#1e3a5f44,stroke:#1e3a5f,stroke-width:2px
|
||
style FIX fill:#8b2c3c44,stroke:#8b2c3c,stroke-width:2px
|
||
style DONE fill:#0d737744,stroke:#0d7377,stroke-width:2px
|
||
</pre>
|
||
</div>
|
||
|
||
<h4>Track 1: Salesforce Testing Center</h4>
|
||
|
||
<p>For single-utterance tests using the CLI Testing Center:</p>
|
||
|
||
<p><strong>YAML test spec for OrderSupport (4 test cases):</strong></p>
|
||
|
||
<span class="code-label">yaml</span>
|
||
<pre class="code-block"><code class="language-yaml">name: "OrderSupport Agent Tests"
|
||
subjectType: AGENT
|
||
subjectName: OrderSupport
|
||
|
||
testCases:
|
||
- utterance: "Where is my order #12345?"
|
||
expectedTopic: order_status
|
||
expectedActions:
|
||
- Get_Order_Status
|
||
expectedOutcome: "Agent provides order status and tracking"
|
||
|
||
- utterance: "I want to return a defective product"
|
||
expectedTopic: returns
|
||
expectedActions:
|
||
- Process_Return
|
||
expectedOutcome: "Agent processes the return request"
|
||
|
||
- utterance: "Tell me a joke"
|
||
expectedTopic: null
|
||
expectedOutcome: "Agent declines off-topic request politely"
|
||
|
||
- utterance: "What's the status of my package delivery?"
|
||
expectedTopic: order_status
|
||
expectedActions:
|
||
- Get_Order_Status
|
||
expectedOutcome: "Agent recognizes delivery query as order status"</code></pre>
|
||
|
||
<div class="callout callout--info">
|
||
<p><strong>Note:</strong> <code>expectedActions</code> uses Level 1 definition names (e.g., <code>Get_Order_Status</code>), not Level 2 invocation names (e.g., <code>get_order</code>).</p>
|
||
</div>
|
||
|
||
<p><strong>CLI commands:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash"># Create the test definition in the org
|
||
sf agent test create \
|
||
--spec ./tests/order-support-tests.yaml \
|
||
--api-name OrderSupportTest \
|
||
--target-org dev
|
||
|
||
# Run the tests
|
||
sf agent test run \
|
||
--api-name OrderSupportTest \
|
||
--wait 10 \
|
||
--result-format json \
|
||
--json \
|
||
--target-org dev</code></pre>
|
||
|
||
<h4>Track 2: Advanced Multi-Turn Testing</h4>
|
||
|
||
<p>Multi-turn testing validates agent behaviors across conversation turns — topic re-matching, context preservation, escalation cascades, and action chaining.</p>
|
||
|
||
<p><strong>The 6 multi-turn test patterns:</strong></p>
|
||
|
||
<div class="table-wrap" data-accent="accent">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>Pattern</th><th>Name</th><th>What It Validates</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>1</td><td>Topic Re-Matching</td><td>Agent switches topics when user changes intent</td></tr>
|
||
<tr><td>2</td><td>Context Preservation</td><td>Agent retains info from earlier turns</td></tr>
|
||
<tr><td>3</td><td>Escalation Cascade</td><td>Agent escalates after sustained difficulty</td></tr>
|
||
<tr><td>4</td><td>Guardrail Mid-Conversation</td><td>Safety rules hold even mid-conversation</td></tr>
|
||
<tr><td>5</td><td>Action Chaining</td><td>Output of one action feeds into the next</td></tr>
|
||
<tr><td>6</td><td>Variable Injection</td><td>Session variables used correctly</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p><strong>Example scenario for OrderSupport (Pattern 1 — Topic Re-Matching):</strong></p>
|
||
|
||
<span class="code-label">yaml</span>
|
||
<pre class="code-block"><code class="language-yaml">scenario:
|
||
name: "order_to_return_switch"
|
||
description: "User switches from order inquiry to return request"
|
||
turns:
|
||
- user: "Where is my order #12345?"
|
||
expect:
|
||
topic_contains: "order_status"
|
||
action_invoked: "Get_Order_Status"
|
||
- user: "Actually, I want to return it instead"
|
||
expect:
|
||
topic_contains: "returns"
|
||
response_acknowledges_change: true
|
||
- user: "The product was defective"
|
||
expect:
|
||
topic_contains: "returns"
|
||
action_invoked: "Process_Return"</code></pre>
|
||
|
||
<p><strong>Execution:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash"># Get agent ID
|
||
AGENT_ID=$(sf data query --use-tooling-api \
|
||
--query "SELECT Id FROM BotDefinition WHERE DeveloperName='OrderSupport' AND IsActive=true LIMIT 1" \
|
||
--result-format json --target-org dev | jq -r '.result.records[0].Id')
|
||
|
||
# Run multi-turn tests
|
||
python3 ~/.claude/skills/sf-ai-agentforce-testing/hooks/scripts/multi_turn_test_runner.py \
|
||
--agent-id "${AGENT_ID}" \
|
||
--scenarios tests/order-support-multi-turn.yaml \
|
||
--output results.json --verbose</code></pre>
|
||
|
||
<h4>The Agentic Fix Loop</h4>
|
||
|
||
<p>When tests fail, the AI automatically categorizes the failure, applies a targeted fix, re-publishes, and re-tests — up to 3 attempts per failure before escalating to a human.</p>
|
||
|
||
<div class="fix-loop">
|
||
<!-- Step 1: Test Failed -->
|
||
<div class="fix-loop__step" style="background: var(--red);">Test Failed</div>
|
||
<div class="fix-loop__arrow"></div>
|
||
|
||
<!-- Step 2: Analyze -->
|
||
<div class="fix-loop__step" style="background: var(--accent);">Analyze Failure Category</div>
|
||
<div class="fix-loop__arrow"></div>
|
||
|
||
<!-- Step 3: 10 Category Grid -->
|
||
<div class="fix-loop__grid">
|
||
<div class="fix-loop__col-label">Agent-Level Fixes</div>
|
||
<div class="fix-loop__col-label">Advanced / Multi-Turn Fixes</div>
|
||
|
||
<div class="fix-loop__cat"><code>TOPIC_NOT_MATCHED</code><br>Add keywords to topic description</div>
|
||
<div class="fix-loop__cat fix-loop__cat--teal"><code>TOPIC_RE_MATCHING_FAILURE</code><br>Add transition phrases</div>
|
||
|
||
<div class="fix-loop__cat"><code>ACTION_NOT_INVOKED</code><br>Improve action description</div>
|
||
<div class="fix-loop__cat fix-loop__cat--teal"><code>CONTEXT_PRESERVATION_FAILURE</code><br>Add context retention instructions</div>
|
||
|
||
<div class="fix-loop__cat"><code>WRONG_ACTION_SELECTED</code><br>Differentiate action descriptions</div>
|
||
<div class="fix-loop__cat fix-loop__cat--teal"><code>MULTI_TURN_ESCALATION</code><br>Add frustration detection</div>
|
||
|
||
<div class="fix-loop__cat"><code>GUARDRAIL_NOT_TRIGGERED</code><br>Add explicit system guardrails</div>
|
||
<div class="fix-loop__cat fix-loop__cat--teal"><code>ACTION_CHAIN_FAILURE</code><br>Fix output variable mappings</div>
|
||
|
||
<div class="fix-loop__cat"><code>ESCALATION_NOT_TRIGGERED</code><br>Add escalation action/triggers</div>
|
||
<div class="fix-loop__cat fix-loop__cat--teal"><code>ACTION_INVOCATION_FAILED</code><br>Fix Flow/Apex logic (cross-skill)</div>
|
||
</div>
|
||
<div class="fix-loop__arrow"></div>
|
||
|
||
<!-- Step 4: Fix Paths -->
|
||
<div class="fix-loop__paths">
|
||
<strong>9 categories</strong> → Apply fix to <code>.agent</code> file → LSP validates<br>
|
||
<strong>1 category</strong> (<code>ACTION_INVOCATION_FAILED</code>) → Delegate to <code>sf-flow</code> / <code>sf-apex</code><br>
|
||
Both paths → <strong>Re-Publish</strong> (<code>sf agent publish</code>) → <strong>Re-Test</strong>
|
||
</div>
|
||
<div class="fix-loop__arrow"></div>
|
||
|
||
<!-- Step 5: Decision -->
|
||
<div class="fix-loop__decision">
|
||
<strong>Passed?</strong><br>
|
||
<span style="color: var(--green); font-weight: 600;">Yes</span> → Move to next test<br>
|
||
<span style="color: var(--red); font-weight: 600;">No</span> → Attempt < 3?
|
||
<span style="color: var(--accent); font-weight: 600;">Yes</span> → Loop back to Analyze |
|
||
<span style="color: var(--gold); font-weight: 600;">No</span> → Escalate to human review
|
||
</div>
|
||
</div>
|
||
|
||
<p><strong>The 10 failure categories with fix strategies:</strong></p>
|
||
|
||
<div class="table-wrap" data-accent="amber">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>Category</th><th>Auto-Fix?</th><th>Fix Strategy</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td><code>TOPIC_NOT_MATCHED</code></td><td><span class="badge badge--yes">Yes</span></td><td>Add keywords to topic description</td></tr>
|
||
<tr><td><code>ACTION_NOT_INVOKED</code></td><td><span class="badge badge--yes">Yes</span></td><td>Improve action description</td></tr>
|
||
<tr><td><code>WRONG_ACTION_SELECTED</code></td><td><span class="badge badge--yes">Yes</span></td><td>Differentiate descriptions</td></tr>
|
||
<tr><td><code>ACTION_INVOCATION_FAILED</code></td><td><span class="badge badge--partial">Partial</span></td><td>Fix Flow or Apex logic</td></tr>
|
||
<tr><td><code>GUARDRAIL_NOT_TRIGGERED</code></td><td><span class="badge badge--yes">Yes</span></td><td>Add system instruction guardrails</td></tr>
|
||
<tr><td><code>ESCALATION_NOT_TRIGGERED</code></td><td><span class="badge badge--yes">Yes</span></td><td>Add escalation action/triggers</td></tr>
|
||
<tr><td><code>TOPIC_RE_MATCHING_FAILURE</code></td><td><span class="badge badge--yes">Yes</span></td><td>Add transition phrases to target</td></tr>
|
||
<tr><td><code>CONTEXT_PRESERVATION_FAILURE</code></td><td><span class="badge badge--yes">Yes</span></td><td>Add context retention instructions</td></tr>
|
||
<tr><td><code>MULTI_TURN_ESCALATION_FAILURE</code></td><td><span class="badge badge--yes">Yes</span></td><td>Add frustration detection triggers</td></tr>
|
||
<tr><td><code>ACTION_CHAIN_FAILURE</code></td><td><span class="badge badge--yes">Yes</span></td><td>Fix output variable mappings</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Stage 6 -->
|
||
<h3>Stage 6: Observability & STDM Analysis</h3>
|
||
|
||
<p><strong>What happens:</strong> After testing, the AI extracts session tracing data from Salesforce Data 360 to analyze agent behavior patterns, latency profiles, and error trends. Findings feed back into Stage 2 for continuous improvement.</p>
|
||
|
||
<p><strong>The 5 Core Session Tracing DMOs:</strong></p>
|
||
|
||
<div class="table-wrap" data-accent="teal">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>DMO</th><th>Key Relationship</th><th>Primary Fields</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td><code>AIAgentSession__dlm</code></td><td>Root</td><td>StartTimestamp, EndTimestamp,<br>ChannelType, EndType</td></tr>
|
||
<tr><td><code>AIAgentInteraction__dlm</code></td><td>Session → N Turns</td><td>TopicApiName, InteractionType,<br>TraceId</td></tr>
|
||
<tr><td><code>AIAgentInteractionStep__dlm</code></td><td>Turn → N Steps</td><td>StepType (LLM/ACTION),<br>InputValue, OutputValue, Error</td></tr>
|
||
<tr><td><code>AIAgentMoment__dlm</code></td><td>Session (NOT Turn)</td><td>AgentApiName*, RequestSummary,<br>ResponseSummary</td></tr>
|
||
<tr><td><code>AIAgentMessage__dlm</code></td><td>Turn → Messages</td><td>Content, Role, Timestamp</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<small style="display:block;color:var(--text-dim);font-size:12px;margin-top:-12px;margin-bottom:20px;">* Note: Agent name lives on Moment, not Session. Field naming uses "AiAgent" (lowercase 'i').</small>
|
||
|
||
<p><strong>Extraction command:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">python3 scripts/cli.py extract \
|
||
--org dev \
|
||
--days 7 \
|
||
--agent OrderSupport \
|
||
--output ./stdm_data</code></pre>
|
||
|
||
<p><strong>Analysis capabilities:</strong></p>
|
||
|
||
<ul>
|
||
<li><strong>Topic analysis:</strong> Which topics are triggered most? Which are never triggered (dead code)?</li>
|
||
<li><strong>Latency profiling:</strong> Average response time per topic, per action. Identify slow actions.</li>
|
||
<li><strong>Error pattern detection:</strong> Which failure categories recur? Which utterances consistently fail?</li>
|
||
<li><strong>Escalation rate:</strong> What percentage of conversations escalate to human agents?</li>
|
||
</ul>
|
||
|
||
<p><strong>The feedback loop:</strong> Observability findings from Stage 6 feed back into Stage 2 — the AI updates topic descriptions, action configurations, and guardrails based on real production data, creating a continuous improvement cycle.</p>
|
||
|
||
<!-- Stage 7 -->
|
||
<h3>Stage 7: Production Deployment</h3>
|
||
|
||
<p><strong>What happens:</strong> The validated agent is deployed through a sandbox → staging → production pipeline.</p>
|
||
|
||
<p><strong>Pipeline:</strong></p>
|
||
|
||
<div class="pipeline">
|
||
<div class="pipeline__stage" style="border-color: var(--accent);">
|
||
<strong>Dev Sandbox</strong><span>Validate</span>
|
||
</div>
|
||
<div class="pipeline__arrow">→</div>
|
||
<div class="pipeline__stage" style="border-color: var(--teal);">
|
||
<strong>Staging</strong><span>Test</span>
|
||
</div>
|
||
<div class="pipeline__arrow">→</div>
|
||
<div class="pipeline__stage" style="border-color: var(--green);">
|
||
<strong>Production</strong><span>Deploy</span>
|
||
</div>
|
||
</div>
|
||
|
||
<p><strong>Per-environment commands:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash"># Staging
|
||
sf agent publish authoring-bundle --api-name OrderSupport -o staging --json
|
||
sf agent activate --api-name OrderSupport --version <n> -o staging --json
|
||
sf agent test run --api-name OrderSupportTest --wait 10 --result-format json --json -o staging
|
||
|
||
# Production (after staging passes)
|
||
sf agent publish authoring-bundle --api-name OrderSupport -o prod --json
|
||
sf agent activate --api-name OrderSupport --version <n> -o prod --json</code></pre>
|
||
|
||
<p>Use the explicit BotVersion number you intend to activate in each environment so pipeline activation stays deterministic.</p>
|
||
|
||
<p><strong>CI/CD integration (GitHub Actions):</strong></p>
|
||
|
||
<span class="code-label">yaml</span>
|
||
<pre class="code-block"><code class="language-yaml">name: Agent Deployment
|
||
on: [push]
|
||
jobs:
|
||
deploy:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
- name: Validate Agent
|
||
run: sf agent validate authoring-bundle --api-name OrderSupport -o staging --json
|
||
- name: Publish to Staging
|
||
run: sf agent publish authoring-bundle --api-name OrderSupport -o staging --json
|
||
- name: Activate
|
||
run: sf agent activate --api-name OrderSupport --version "$AGENT_VERSION" -o staging --json
|
||
- name: Run Tests
|
||
run: sf agent test run --api-name OrderSupportTest --wait 10 --result-format json --json -o staging</code></pre>
|
||
|
||
<p>Set <code>AGENT_VERSION</code> in the workflow environment to the BotVersion number you intend to activate.</p>
|
||
|
||
<hr>
|
||
|
||
<!-- ═══════════════════════ §5 ═══════════════════════ -->
|
||
<div id="s5" class="section-header" style="--i:6">
|
||
<span class="section__num">5</span>
|
||
<h2>Time Comparison — Manual vs. Agentic</h2>
|
||
</div>
|
||
|
||
<p>This is the centerpiece of the paper. Every estimate is grounded in the reference data: ~15 seconds per preview iteration (from programmatic preview benchmarks), 10 failure categories with auto-fix (from the agentic fix loop framework), and real CLI command execution times.</p>
|
||
|
||
<div class="ve-card ve-card--elevated" style="--i:7">
|
||
<div class="table-wrap" data-accent="green" style="border:none;margin:0;">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>Stage</th><th>Manual</th><th>Agentic</th><th>Savings</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Stage 1: Requirements & Design<span class="cell-desc">Stakeholder interviews, doc review, requirements drafting</span></td>
|
||
<td>2–4 hours<span class="cell-desc">(meeting-heavy)</span></td>
|
||
<td>15–30 min<span class="cell-desc">(AI reads docs, generates spec)</span></td>
|
||
<td>~85%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stage 2: Agent Script Authoring<span class="cell-desc">Writing in Agent Builder UI, canvas configuration, topic/action setup</span></td>
|
||
<td>4–8 hours<span class="cell-desc">(click-heavy)</span></td>
|
||
<td>10–30 min<span class="cell-desc">(AI writes .agent file in one pass)</span></td>
|
||
<td>~93%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stage 3: Validation<span class="cell-desc">Manual UI error checking, fix loop</span></td>
|
||
<td>30–60 min<span class="cell-desc">(per iteration)</span></td>
|
||
<td>~5 sec<span class="cell-desc">(LSP + CLI auto)</span></td>
|
||
<td>~99%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stage 3.5: Preview Smoke Test<span class="cell-desc">Per iteration</span></td>
|
||
<td>5–15 min</td>
|
||
<td>~15 sec</td>
|
||
<td>~97%</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="cell-desc" style="margin-top:0;">× typical 5 iterations<br>(type utterance, read trace, identify issue, go fix, come back)</span></td>
|
||
<td>25–75 min<span class="cell-desc">(all manual)</span></td>
|
||
<td>~75 sec<span class="cell-desc">(programmatic preview + jq)</span></td>
|
||
<td>~97%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stage 4: Publish & Activate<span class="cell-desc">Click Publish, wait, click Activate</span></td>
|
||
<td>2–5 min<span class="cell-desc">(UI waits)</span></td>
|
||
<td>~30 sec<span class="cell-desc">(2 CLI commands)</span></td>
|
||
<td>~85%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stage 5: Formal Testing<span class="cell-desc">Write 20 test cases</span></td>
|
||
<td>2–4 hours</td>
|
||
<td>5–15 min</td>
|
||
<td>~93%</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="cell-desc" style="margin-top:0;">Execute + analyze results</span></td>
|
||
<td>1–2 hours</td>
|
||
<td>10–20 min</td>
|
||
<td>~83%</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="cell-desc" style="margin-top:0;">Fix loop (3 iterations)</span></td>
|
||
<td>2–6 hours</td>
|
||
<td>5–15 min</td>
|
||
<td>~93%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stage 6: Observability Setup<span class="cell-desc">Configure Data 360, write queries, build dashboards</span></td>
|
||
<td>2–4 hours<span class="cell-desc">(manual setup)</span></td>
|
||
<td>10–20 min<span class="cell-desc">(python3 extract + analyze)</span></td>
|
||
<td>~90%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stage 7: Production Deploy<span class="cell-desc">Manual deploy per environment</span></td>
|
||
<td>30–60 min<span class="cell-desc">(click-through)</span></td>
|
||
<td>2–5 min<span class="cell-desc">(CLI pipeline)</span></td>
|
||
<td>~90%</td>
|
||
</tr>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr><td>TOTAL (first agent)</td><td>2–5 DAYS</td><td>1–3 HOURS</td><td>~90%</td></tr>
|
||
<tr><td>TOTAL (subsequent agents)</td><td>1–3 DAYS</td><td>30–90 MIN</td><td>~92%</td></tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>Assumptions & Notes</h3>
|
||
|
||
<ol>
|
||
<li><strong>"Manual" assumes</strong> a developer experienced with Agent Builder who knows what they're building. Inexperienced developers would take significantly longer.</li>
|
||
<li><strong>"Agentic" assumes</strong> the sf-skills toolkit is installed, the developer has an authenticated org, and they're using an AI coding agent with shell access.</li>
|
||
<li><strong>"Subsequent agents"</strong> are faster because:
|
||
<ul>
|
||
<li>The AI has learned patterns from the first agent (in-session context)</li>
|
||
<li>Templates and boilerplate exist from previous agents</li>
|
||
<li>Auth is cached (<code>sf org login</code> persists across sessions)</li>
|
||
<li>The developer knows the workflow and can provide better requirements</li>
|
||
</ul>
|
||
</li>
|
||
<li><strong>Stage 3.5 timing</strong> is validated: <code>sf agent preview start</code> + <code>send</code> + <code>end</code> completes in ~10–15 seconds total, vs. the manual cycle of open Preview → type → read waterfall → switch to canvas → fix → re-publish → re-activate which takes 5–15 minutes.</li>
|
||
<li><strong>Fix loop timing</strong> (Stage 5) is based on the 10-category auto-fix framework with max 3 attempts per failure. Most fixes (8 of 10 categories) target the <code>.agent</code> file directly and complete in seconds.</li>
|
||
</ol>
|
||
|
||
<hr>
|
||
|
||
<!-- ═══════════════════════ §6 ═══════════════════════ -->
|
||
<div id="s6" class="section-header" style="--i:8">
|
||
<span class="section__num">6</span>
|
||
<h2>Architecture & Open-Source Ecosystem</h2>
|
||
</div>
|
||
|
||
<h3>6a. Zero MCP — Local CLI Composability</h3>
|
||
|
||
<p>This subsection is the technical proof point of the paper. The entire 7-stage lifecycle documented above — from requirements to production deployment — executes entirely through <code>sf</code> CLI commands. Total MCP tool calls: <strong>0</strong>.</p>
|
||
|
||
<p><strong>Architectural comparison:</strong></p>
|
||
|
||
<div class="table-wrap" data-accent="gold">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>Dimension</th><th>MCP Approach</th><th>CLI Approach (sf-skills)</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>Setup</td><td>Server process + config</td><td><code>npm install</code> + <code>sf login</code></td></tr>
|
||
<tr><td>Transport</td><td>WebSocket / stdio</td><td>Shell exec (Bash tool)</td></tr>
|
||
<tr><td>State</td><td>Server-managed sessions</td><td>Stateless CLI commands</td></tr>
|
||
<tr><td>Composability</td><td>Tool-specific adapters</td><td>Unix pipes (<code>jq</code>, <code>grep</code>, <code>&&</code>)</td></tr>
|
||
<tr><td>Recovery</td><td>Restart server + retry</td><td>Re-run the command</td></tr>
|
||
<tr><td>Debugging</td><td>Server logs + protocol</td><td>Terminal output + JSON files</td></tr>
|
||
<tr><td>Agent compatibility</td><td>Agent-specific MCP SDK</td><td>Any agent with shell access</td></tr>
|
||
<tr><td>Infrastructure</td><td>Running process per tool</td><td>Zero (sf CLI is the tool)</td></tr>
|
||
<tr><td>Offline capability</td><td>Requires running server</td><td>CLI binary works offline*</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<small style="display:block;color:var(--text-dim);font-size:12px;margin-top:-12px;margin-bottom:20px;">* Against cached/local metadata. API calls still require network.</small>
|
||
|
||
<p><strong>Why CLI composability wins for agent workflows:</strong></p>
|
||
|
||
<ol>
|
||
<li><strong>AI agents are shell experts.</strong> LLMs are trained on billions of shell interactions; they know how to pipe, redirect, and parse CLI output natively. Teaching an LLM to use <code>jq</code> is free — it already knows.</li>
|
||
<li><strong>Error recovery is trivial.</strong> A failed <code>sf agent publish</code> just needs re-running; no connection state to restore, no session to reconnect, no server heartbeat to re-establish.</li>
|
||
<li><strong>Traces are local files.</strong> <code>~/.sf/sfdx/agents/{agent}/sessions/{sid}/traces/{planId}.json</code> contains full trace data readable by <code>jq</code>. No API call needed to fetch traces after a preview session.</li>
|
||
<li><strong>Incremental adoption.</strong> Use one skill for one stage, mix with manual steps elsewhere. No all-or-nothing framework buy-in. Start with Stage 3.5 (preview smoke tests) and expand from there.</li>
|
||
</ol>
|
||
|
||
<h3>The Skill Ecosystem</h3>
|
||
|
||
<p><strong>19 skills live, 10 more on the roadmap (29 total):</strong></p>
|
||
|
||
<div class="table-wrap" data-accent="gold">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>Category</th><th>Skills (19 live)</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>Development</td><td><code>sf-apex</code>, <code>sf-flow</code>, <code>sf-lwc</code>, <code>sf-soql</code></td></tr>
|
||
<tr><td>Quality</td><td><code>sf-testing</code>, <code>sf-debug</code></td></tr>
|
||
<tr><td>Foundation</td><td><code>sf-metadata</code>, <code>sf-data</code>, <code>sf-permissions</code></td></tr>
|
||
<tr><td>Integration</td><td><code>sf-connected-apps</code>, <code>sf-integration</code></td></tr>
|
||
<tr><td>AI & Automation</td><td><code>sf-ai-agentscript</code>, <code>sf-ai-agentforce</code>, <code>sf-ai-agentforce-testing</code>, <code>sf-ai-agentforce-observability</code>, <code>sf-ai-agentforce-persona</code></td></tr>
|
||
<tr><td>DevOps & Tooling</td><td><code>sf-deploy</code>, <code>sf-diagram-mermaid</code>, <code>sf-diagram-nanobananapro</code></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p><strong>7 specialized agents for team orchestration:</strong></p>
|
||
|
||
<pre class="code-block"><code>fde-strategist (orchestrator — plans, researches, delegates)
|
||
├── FDE Team: fde-engineer, fde-experience-specialist
|
||
├── Cross-cutting: fde-qa-engineer, fde-release-engineer
|
||
└── PS Team: ps-technical-architect, ps-solution-architect</code></pre>
|
||
|
||
<h3>Skill Orchestration</h3>
|
||
|
||
<p>The lifecycle stages map to skills in a specific order:</p>
|
||
|
||
<pre class="code-block"><code>sf-metadata → sf-apex → sf-flow → sf-deploy → sf-ai-agentscript → sf-deploy → sf-ai-agentforce-testing
|
||
│
|
||
sf-ai-agentforce-observability</code></pre>
|
||
|
||
<p>Each skill handles one responsibility:</p>
|
||
<ul>
|
||
<li><strong>sf-ai-agentscript</strong>: Writes and validates the <code>.agent</code> file (Stages 2, 3, 3.5)</li>
|
||
<li><strong>sf-deploy</strong>: Publishes and activates (Stages 4, 7)</li>
|
||
<li><strong>sf-ai-agentforce-testing</strong>: Formal testing and fix loops (Stage 5)</li>
|
||
<li><strong>sf-ai-agentforce-observability</strong>: Session tracing analysis (Stage 6)</li>
|
||
</ul>
|
||
|
||
<h3>Compatible AI Coding Agents</h3>
|
||
|
||
<p>The sf-skills toolkit works with any AI coding agent that has shell access:</p>
|
||
|
||
<div class="table-wrap" data-accent="gold">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr><th>Agent</th><th>Vendor</th><th>Install Method</th><th>Features</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>Claude Code</td><td>Anthropic</td><td><code>npx skills add</code> or bash installer</td><td><span class="badge badge--yes">Skills</span> <span class="badge badge--yes">Hooks</span> <span class="badge badge--yes">LSP</span> <span class="badge badge--yes">Agents</span></td></tr>
|
||
<tr><td>Codex CLI</td><td>OpenAI</td><td><code>npx skills add</code></td><td><span class="badge badge--yes">Skills</span></td></tr>
|
||
<tr><td>Gemini CLI</td><td>Google</td><td><code>npx skills add</code></td><td><span class="badge badge--yes">Skills</span></td></tr>
|
||
<tr><td>OpenCode</td><td>Open Source</td><td><code>npx skills add</code></td><td><span class="badge badge--yes">Skills</span></td></tr>
|
||
<tr><td>Amp</td><td>Sourcegraph</td><td><code>npx skills add</code></td><td><span class="badge badge--yes">Skills</span></td></tr>
|
||
<tr><td>Droid CLI</td><td>Factory.ai</td><td><code>npx skills add</code></td><td><span class="badge badge--yes">Skills</span></td></tr>
|
||
<tr><td colspan="4" style="text-align:center;color:var(--text-dim);font-style:italic;">… and 40+ more via agentskills.io</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Installation</h3>
|
||
|
||
<p><strong>Any AI coding agent:</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">npx skills add Jaganpro/sf-skills</code></pre>
|
||
|
||
<p><strong>Claude Code (full experience with hooks, agents, LSP, guardrails):</strong></p>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash">curl -sSL https://raw.githubusercontent.com/Jaganpro/sf-skills/main/tools/install.sh | bash</code></pre>
|
||
|
||
<hr>
|
||
|
||
<!-- ═══════════════════════ §7 ═══════════════════════ -->
|
||
<div id="s7" class="section-header" style="--i:9">
|
||
<span class="section__num">7</span>
|
||
<h2>Conclusion & Call to Action</h2>
|
||
</div>
|
||
|
||
<h3>The Numbers</h3>
|
||
|
||
<p>The agentic development lifecycle delivers <strong>90%+ time savings</strong> across every stage:</p>
|
||
|
||
<ul>
|
||
<li><strong>First agent:</strong> 2–5 days → 1–3 hours</li>
|
||
<li><strong>Subsequent agents:</strong> 1–3 days → 30–90 minutes</li>
|
||
<li><strong>Per-iteration speed:</strong> 5–15 minutes → ~15 seconds (Stage 3.5 inner loop)</li>
|
||
<li><strong>Total MCP calls required:</strong> 0</li>
|
||
</ul>
|
||
|
||
<p>These are not theoretical projections. Every CLI command in this paper is real, every trace check is executable, and every timing estimate is grounded in actual <code>sf</code> CLI benchmarks.</p>
|
||
|
||
<h3>What Makes This Possible</h3>
|
||
|
||
<p>Three things converge to make this level of automation achievable:</p>
|
||
|
||
<ol>
|
||
<li><strong>The Salesforce CLI (<code>sf</code>) exposes the full agent lifecycle</strong> — from <code>sf agent generate agent-spec</code> through <code>sf agent preview start/send/end</code> to <code>sf agent test run</code>. Every operation that previously required the UI now has a CLI equivalent.</li>
|
||
<li><strong>AI coding agents are shell orchestrators</strong> — they're trained on billions of shell interactions, they know how to chain commands, parse JSON output, and iterate on failures. The <code>sf</code> CLI speaks their native language.</li>
|
||
<li><strong>Agent Script (<code>.agent</code> DSL) makes agents code-first</strong> — a single file that can be written, validated, version-controlled, and deployed like any other code artifact. No more UI-only configuration.</li>
|
||
</ol>
|
||
|
||
<h3>This Is Within Reach Today</h3>
|
||
|
||
<p>Every tool used in this paper is available right now:</p>
|
||
|
||
<ul>
|
||
<li><strong>Salesforce CLI v2.121.7+</strong> — free, open-source</li>
|
||
<li><strong>sf-skills toolkit (19 skills)</strong> — MIT license, works with 40+ AI coding agents</li>
|
||
<li><strong>Agent Script</strong> — GA in Spring '26 (API v66.0+)</li>
|
||
<li><strong>Any AI coding agent with shell access</strong> — Claude Code, Codex, Gemini CLI, and many more</li>
|
||
</ul>
|
||
|
||
<h3>Get Started</h3>
|
||
|
||
<span class="code-label">bash</span>
|
||
<pre class="code-block"><code class="language-bash"># Install sf-skills (any AI coding agent)
|
||
npx skills add Jaganpro/sf-skills
|
||
|
||
# Or full Claude Code experience
|
||
curl -sSL https://raw.githubusercontent.com/Jaganpro/sf-skills/main/tools/install.sh | bash</code></pre>
|
||
|
||
<h3>Links</h3>
|
||
|
||
<ul>
|
||
<li><strong>sf-skills GitHub:</strong> <a href="https://github.com/Jaganpro/sf-skills">github.com/Jaganpro/sf-skills</a></li>
|
||
<li><strong>Agent Skills Standard:</strong> <a href="https://agentskills.io">agentskills.io</a></li>
|
||
<li><strong>Agent Script Documentation:</strong> <a href="https://developer.salesforce.com/docs/ai/agentforce/guide/agent-script.html">developer.salesforce.com/docs/ai/agentforce/guide/agent-script.html</a></li>
|
||
<li><strong>Salesforce CLI:</strong> <a href="https://developer.salesforce.com/tools/salesforcecli">developer.salesforce.com/tools/salesforcecli</a></li>
|
||
</ul>
|
||
|
||
<hr>
|
||
|
||
<!-- ─────────────── About the Author ─────────────── -->
|
||
<div class="ve-card" style="--i:10">
|
||
<h3 style="margin-top:0;">About the Author</h3>
|
||
<p><strong>Jag Valaiyapathy</strong> is a Senior Forward Deployed Engineer at Salesforce and a Salesforce Certified Technical Architect (CTA). He is the creator of the sf-skills open-source toolkit — 19 skills and 7 agents that automate Salesforce development for AI coding tools. His work focuses on making Agentforce agent development accessible, repeatable, and fast.</p>
|
||
<p style="margin-bottom:0;">Contact: <a href="mailto:jvalaiyapathy@salesforce.com">jvalaiyapathy@salesforce.com</a><br>
|
||
GitHub: <a href="https://github.com/Jaganpro">github.com/Jaganpro</a></p>
|
||
</div>
|
||
|
||
<p style="text-align:center;color:var(--text-dim);font-size:13px;margin-top:40px;">
|
||
<em>This white paper and the sf-skills toolkit are released under the MIT License.</em><br>
|
||
<em>Copyright (c) 2024–2026 Jag Valaiyapathy</em>
|
||
</p>
|
||
|
||
</div><!-- /main -->
|
||
</div><!-- /wrap -->
|
||
|
||
<!-- ═══════════════════════ Prism.js Syntax Highlighting ═══════════════════════ -->
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-yaml.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-xml-doc.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-json.min.js"></script>
|
||
|
||
<!-- ═══════════════════════ Mermaid.js ═══════════════════════ -->
|
||
<script type="module">
|
||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||
|
||
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
mermaid.initialize({
|
||
startOnLoad: true,
|
||
theme: 'base',
|
||
look: 'classic',
|
||
themeVariables: {
|
||
primaryColor: isDark ? '#1e3a5f' : '#dce8f5',
|
||
primaryBorderColor: isDark ? '#5b9bd5' : '#1e3a5f',
|
||
primaryTextColor: isDark ? '#e6edf3' : '#1a1f36',
|
||
secondaryColor: isDark ? '#1a2a1f' : '#e8f5e8',
|
||
secondaryBorderColor: isDark ? '#34d399' : '#2a6041',
|
||
secondaryTextColor: isDark ? '#e6edf3' : '#1a1f36',
|
||
tertiaryColor: isDark ? '#2a2010' : '#fef8e8',
|
||
tertiaryBorderColor: isDark ? '#e8b84a' : '#d4a73a',
|
||
tertiaryTextColor: isDark ? '#e6edf3' : '#1a1f36',
|
||
lineColor: isDark ? '#64748b' : '#94a3b8',
|
||
fontSize: '16px',
|
||
fontFamily: "'IBM Plex Sans', system-ui, sans-serif",
|
||
noteBkgColor: isDark ? '#1c2333' : '#fef8e8',
|
||
noteTextColor: isDark ? '#e6edf3' : '#1a1f36',
|
||
noteBorderColor: isDark ? '#e8b84a' : '#d4a73a',
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<!-- ═══════════════════════ Zoom Controls ═══════════════════════ -->
|
||
<script>
|
||
var INITIAL_ZOOM = 1.4;
|
||
|
||
function zoomDiagram(btn, factor) {
|
||
var wrap = btn.closest('.mermaid-wrap');
|
||
var target = wrap.querySelector('.mermaid');
|
||
var current = parseFloat(target.dataset.zoom || INITIAL_ZOOM);
|
||
var next = Math.min(Math.max(current * factor, 0.5), 5);
|
||
target.dataset.zoom = next;
|
||
target.style.zoom = next;
|
||
}
|
||
|
||
function resetZoom(btn) {
|
||
var wrap = btn.closest('.mermaid-wrap');
|
||
var target = wrap.querySelector('.mermaid');
|
||
target.dataset.zoom = INITIAL_ZOOM;
|
||
target.style.zoom = INITIAL_ZOOM;
|
||
}
|
||
|
||
document.querySelectorAll('.mermaid-wrap').forEach(function(wrap) {
|
||
wrap.addEventListener('wheel', function(e) {
|
||
if (!e.ctrlKey && !e.metaKey) return;
|
||
e.preventDefault();
|
||
var target = wrap.querySelector('.mermaid');
|
||
var current = parseFloat(target.dataset.zoom || INITIAL_ZOOM);
|
||
var factor = e.deltaY < 0 ? 1.1 : 0.9;
|
||
var next = Math.min(Math.max(current * factor, 0.5), 5);
|
||
target.dataset.zoom = next;
|
||
target.style.zoom = next;
|
||
}, { passive: false });
|
||
|
||
var startX, startY, scrollL, scrollT;
|
||
wrap.addEventListener('mousedown', function(e) {
|
||
if (e.target.closest('.zoom-controls')) return;
|
||
wrap.classList.add('is-panning');
|
||
startX = e.clientX;
|
||
startY = e.clientY;
|
||
scrollL = wrap.scrollLeft;
|
||
scrollT = wrap.scrollTop;
|
||
});
|
||
window.addEventListener('mousemove', function(e) {
|
||
if (!wrap.classList.contains('is-panning')) return;
|
||
wrap.scrollLeft = scrollL - (e.clientX - startX);
|
||
wrap.scrollTop = scrollT - (e.clientY - startY);
|
||
});
|
||
window.addEventListener('mouseup', function() {
|
||
wrap.classList.remove('is-panning');
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<!-- ═══════════════════════ Scroll Spy ═══════════════════════ -->
|
||
<script>
|
||
(function() {
|
||
var toc = document.getElementById('toc');
|
||
var links = toc.querySelectorAll('a');
|
||
var sections = [];
|
||
|
||
links.forEach(function(link) {
|
||
var id = link.getAttribute('href').slice(1);
|
||
var el = document.getElementById(id);
|
||
if (el) sections.push({ id: id, el: el, link: link });
|
||
});
|
||
|
||
var observer = new IntersectionObserver(function(entries) {
|
||
entries.forEach(function(entry) {
|
||
if (entry.isIntersecting) {
|
||
links.forEach(function(l) { l.classList.remove('active'); });
|
||
var match = sections.find(function(s) { return s.el === entry.target; });
|
||
if (match) {
|
||
match.link.classList.add('active');
|
||
if (window.innerWidth <= 1000) {
|
||
match.link.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}, { rootMargin: '-10% 0px -80% 0px' });
|
||
|
||
sections.forEach(function(s) { observer.observe(s.el); });
|
||
|
||
links.forEach(function(link) {
|
||
link.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
var id = link.getAttribute('href').slice(1);
|
||
var el = document.getElementById(id);
|
||
if (el) {
|
||
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
history.replaceState(null, '', '#' + id);
|
||
}
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|