agentforce-architecture-analyze
forcedotcom/sf-skills
Document Agentforce agent architecture: planner, topics, actions, flows, Apex, and plugins from design-time metadata.
What is agentforce-architecture-analyze?
Reads declared metadata for a single Agentforce agent and renders a human-readable architecture document plus Mermaid invocation graph. Use this to describe, diagram, audit, or diff an agent's structure by API name in a specific org. Does not analyze runtime session traces or conversation transcripts.
- Fetches design-time metadata (BotDefinition, GenAiPlanner, GenAiPlugin, GenAiFunction, Flow, ApexClass, GenAiPromptTemplate)
- Renders human-readable architecture document describing planner, topics, actions, and flows
- Generates Mermaid invocation graph visualizing agent structure
- Supports agent version pinning and cache control via --force and --reprobe flags
- Runs inline with 30–45s typical runtime; parallel SOQL fan-out delivers 3–5× speedup over sequential baseline
- Stores artifacts under ~/.vibe/data/agentforce-architecture-analyze with configurable data and cache directories
How to install agentforce-architecture-analyze
npx skills add https://github.com/forcedotcom/sf-skills --skill agentforce-architecture-analyze- Salesforce CLI (sf) version ≥2.136.8 installed and authenticated with org alias
- Python 3.10+ installed
- Agent API name (DeveloperName of the BotDefinition, not the label)
- Org alias configured via `sf org login`
How to use agentforce-architecture-analyze
- 1.Run the skill with `--org <alias> --agent <api_name>` (e.g., `--org prod --agent MySalesAgent`)
- 2.Optionally add `--version <version_api_name>` to pin a specific BotVersion; if omitted, the active version is used
- 3.Optionally add `--force` to bypass cache or `--reprobe` to refresh the 7-day channel-probe cache
- 4.The skill fetches metadata in parallel and writes artifacts to ~/.vibe/data/agentforce-architecture-analyze/<org_id>/<agent_api_name>__<version>/
- 5.Review the rendered architecture document and Mermaid graph in the output
Use cases
- Document the complete action tree and topic structure of a production agent for compliance or knowledge transfer
- Generate a Mermaid diagram to visualize how an agent's planner invokes topics, actions, and flows
- Audit which Apex classes, prompt templates, and NGA plugins are wired into a specific agent version
- Compare agent architecture across versions (e.g., v3 vs v5) to track structural changes
- Inventory all tools and integrations available to an agent without running a live session
- Salesforce Agentforce developers and architects documenting agent design
- Platform engineers auditing agent configurations and dependencies
- Technical leads comparing agent versions or preparing migration plans
- Compliance and governance teams inventorying agent capabilities
agentforce-architecture-analyze FAQ
agentforce-architecture-analyze reads design-time metadata (BotDefinition, topics, actions, flows, Apex, prompts, plugins) to document agent structure. agentforce-d360-analyze reads runtime audit rows and session traces. Use this skill for architecture; use d360-analyze for runtime behavior and conversation transcripts.
Typical runtime is 30–45 seconds; hard cap is 60 seconds on reference fixtures. Parallel SOQL fan-out delivers 3–5× speedup over sequential baseline. Large agents with many flows scale approximately linearly.
Yes. Run the skill once with `--version v3` and once with `--version v5` to generate separate architecture documents, then diff the outputs manually or compare the Mermaid graphs.
The skill resolves the active BotVersion (highest version marked as active) and documents that. Use `--version <api_name>` to pin a specific version.
By default, under ~/.vibe/data/agentforce-architecture-analyze/<org_id15>/<agent_api_name>__<agent_version>/. Override with `--data-dir <path>`.
Full instructions (SKILL.md)
Source of truth, from forcedotcom/sf-skills.
name: agentforce-architecture-analyze description: "Declared architecture snapshot for one Agentforce agent: planner, topics, actions, flows, Apex, prompt templates, and NGA plugins. Renders a human-readable architecture document and Mermaid invocation graph from design-time metadata (not runtime audit rows). TRIGGER when user asks to describe, diagram, inventory, audit, document, or diff (e.g. v3 vs v5) the architecture / action tree / topic structure / tool inventory of a specific agent by agent API name in a specific org. DO NOT TRIGGER for runtime session traces, conversation transcripts, generation timings, or gateway audit chains — this skill reads design-time metadata only (use agentforce-d360-analyze for session traces)." metadata: version: "1.0" domains: ["Agentforce"] minApiVersion: "64.0" relatedSkills: - "agentforce-d360-analyze" cliTools: - tool: ["sf"] semver: ">=2.136.8" - tool: ["python3"] semver: ">=3.10.0"
agentforce-architecture-analyze — declared architecture snapshot
Design-time metadata tree for one Agentforce agent: planner → topics → actions → flows → Apex → prompts → NGA plugins. Reads declared metadata only — BotDefinition, GenAiPlanner*, GenAiPlugin*, GenAiFunction*, Flow, ApexClass, GenAiPromptTemplate. Does not read runtime audit rows.
Runtime budget: 30–45s typical, ≤60s hard cap on reference fixtures. Sequential baseline would be 90–220s; parallel Tooling SOQL fan-out delivers a 3–5× speedup. Large bots with many flows scale approximately linearly — each flow metadata retrieve is one round-trip.
Runs inline — no subagent. Every phase is deterministic file processing.
If the user hasn't given enough to proceed
When invoked with no agent_api_name AND no org alias, print the following block verbatim — do not paraphrase, do not pre-run any script. Trigger condition: $ARGUMENTS is empty OR names no agent (no --agent flag and no known agent API name in the prose) OR names no org (no --org flag and no known alias).
Which agent should I document, and in which org?
I need:
- Agent API name — the
DeveloperNameof theBotDefinition(e.g.MyAgent,MySalesAgent). Not the label.- Org alias — for
sfCLI auth (the alias you configured withsf org login)Optional:
- Version — an
agent_version_api_namelikev5. If omitted, I'll resolve the activeBotVersion.--force— ignore cached tree; re-fetch everything.--reprobe— re-run the 7-day channel-probe cache (only needed after a Salesforce release).I'll run the metadata pipeline inline. Artifacts land under
~/.vibe/data/agentforce-architecture-analyze/<org_id15>/<agent_api_name>__<agent_version>/(overridable with--data-dir).
Pipeline invocation
When the user has supplied --org <alias> + --agent <api_name> (plus any optional flags), run this block. One python3 invocation drives the full pipeline. main.py writes .emit_ctx.json; emit_result.py reads it and prints the final === RESULT === block last to stdout.
set -euo pipefail
# zsh arrays are 1-indexed by default; bash arrays are 0-indexed.
# This block uses 0-indexed semantics throughout (_args[$i] starting at i=0),
# so under zsh + `set -u` the very first read of `_args[0]` would trip
# `parameter not set`. KSH_ARRAYS makes zsh treat arrays as 0-indexed,
# matching the bash shebang's expectation. No-op under bash.
[ -n "${ZSH_VERSION:-}" ] && setopt KSH_ARRAYS
SKILL_ROOT="${SKILL_ROOT:-${PLUGIN_ROOT:-$HOME/.vibe/skills}/agentforce-architecture-analyze}"
# Argument parser. Accepts both `--org foo` and `--org=foo`.
# `$ARGUMENTS` is the raw user input Claude Code substitutes.
ARG_ORG=""
ARG_AGENT=""
ARG_VERSION=""
ARG_FORCE=""
ARG_REPROBE=""
ARG_PARALLELISM=""
ARG_MAX_MERMAID=""
# shellcheck disable=SC2206
_args=($ARGUMENTS)
i=0
while [ $i -lt ${#_args[@]} ]; do
tok="${_args[$i]}"
case "$tok" in
--org=*) ARG_ORG="${tok#--org=}" ;;
--org) i=$((i+1)); ARG_ORG="${_args[$i]:-}" ;;
--agent=*) ARG_AGENT="${tok#--agent=}" ;;
--agent) i=$((i+1)); ARG_AGENT="${_args[$i]:-}" ;;
--version=*) ARG_VERSION="${tok#--version=}" ;;
--version) i=$((i+1)); ARG_VERSION="${_args[$i]:-}" ;;
--parallelism=*) ARG_PARALLELISM="${tok#--parallelism=}" ;;
--parallelism) i=$((i+1)); ARG_PARALLELISM="${_args[$i]:-}" ;;
--max-mermaid-nodes=*) ARG_MAX_MERMAID="${tok#--max-mermaid-nodes=}" ;;
--max-mermaid-nodes) i=$((i+1)); ARG_MAX_MERMAID="${_args[$i]:-}" ;;
--force) ARG_FORCE="1" ;;
--reprobe) ARG_REPROBE="1" ;;
esac
i=$((i+1))
done
# Usage block if required flags missing. Agent reads stderr,
# prints verbatim, and stops — does NOT pre-run main.py.
if [ -z "$ARG_ORG" ] || [ -z "$ARG_AGENT" ]; then
cat >&2 <<'USAGE'
> Which agent should I document, and in which org?
>
> I need:
> - **Agent API name** — the BotDefinition.DeveloperName (e.g. `MyAgent`)
> - **Org alias** — for `sf` CLI auth (the alias you configured with `sf org login`)
>
> Optional flags:
> - `--version v5` — pin a specific BotVersion (default: Active+highest)
> - `--force` — bypass cache
> - `--reprobe` — force channel-probe refresh
> - `--parallelism N` — ThreadPoolExecutor size (default 5)
> - `--max-mermaid-nodes N` — cap Mermaid node count (default 80)
USAGE
exit 2
fi
# Fresh work dir per invocation. Epoch + random suffix avoids collisions
# between concurrent runs on the same host.
WORK_DIR="/tmp/agentforce-architecture-analyze-$(date +%s)-$RANDOM"
mkdir -p "$WORK_DIR"
# Input validation at the boundary, BEFORE any python3 call.
# fs_guard exits 1 and prints an INVALID_INPUT RESULT block on failure;
# `|| exit 1` is mandatory — bare calls silently continue past failures.
python3 "$SKILL_ROOT/scripts/_shared/fs_guard.py" "$ARG_AGENT" agent_api_name api_name || exit 1
python3 "$SKILL_ROOT/scripts/_shared/fs_guard.py" "$ARG_ORG" org_alias not_empty || exit 1
python3 "$SKILL_ROOT/scripts/_shared/fs_guard.py" "$WORK_DIR" WORK_DIR symlink || exit 1
python3 "$SKILL_ROOT/scripts/_shared/fs_guard.py" "$WORK_DIR" WORK_DIR owned || exit 1
if [ -n "$ARG_VERSION" ]; then
python3 "$SKILL_ROOT/scripts/_shared/fs_guard.py" "$ARG_VERSION" agent_version api_name || exit 1
fi
# Single python3 call drives all pipeline phases. main.py writes
# `.emit_ctx.json` into $WORK_DIR — emit_result.py then renders the
# RESULT block from that ctx. No subprocess-per-phase.
_main_args=(--org-alias "$ARG_ORG" --agent "$ARG_AGENT" --work-dir "$WORK_DIR")
[ -n "$ARG_VERSION" ] && _main_args+=(--version "$ARG_VERSION")
[ -n "$ARG_FORCE" ] && _main_args+=(--force)
[ -n "$ARG_REPROBE" ] && _main_args+=(--reprobe)
[ -n "$ARG_PARALLELISM" ] && _main_args+=(--parallelism "$ARG_PARALLELISM")
[ -n "$ARG_MAX_MERMAID" ] && _main_args+=(--max-mermaid-nodes "$ARG_MAX_MERMAID")
# main.py returns nonzero on terminal failures; we DON'T short-circuit —
# emit_result still publishes the failure RESULT block. `set -e` is
# temporarily relaxed around this single call.
set +e
python3 "$SKILL_ROOT/scripts/main.py" "${_main_args[@]}"
_rc=$?
set -e
# Final RESULT block is emit_result.py's stdout — MUST be the last thing
# stdout sees. emit_result exits 0 on render success; the bash harness
# propagates main.py's rc for the agent's exit status.
WORK_DIR="$WORK_DIR" python3 "$SKILL_ROOT/scripts/emit_result.py"
exit "$_rc"
Inputs
| Input | Flag | Required | Default |
|---|---|---|---|
org_alias | --org | yes | — |
agent_api_name | --agent | yes | — |
agent_version_api_name | --version | no | active BotVersion |
force_refresh | --force | no | false (honor cache) |
reprobe | --reprobe | no | false (honor 7-day channel-probe cache) |
parallelism | --parallelism | no | 5 |
max_mermaid_nodes | --max-mermaid-nodes | no | 80 |
data_dir | --data-dir | no | ~/.vibe/data/agentforce-architecture-analyze |
cache_dir | --cache-dir | no | ~/.vibe/cache/agentforce-architecture-analyze |
Outputs
All artifacts under ~/.vibe/data/agentforce-architecture-analyze/<org_id15>/<agent_api_name>__<agent_version>/ (default; override with --data-dir <path>):
<agent>_<ver>_metadata_tree.json primary artifact — normalized planner/topic/action/flow/apex/prompt/plugin tree
<agent>_<ver>_architecture.md human-readable section-by-section rendering (H1 + 7 numbered sections, plus a conditional Dependency graph appendix). Mermaid diagrams are embedded inside the relevant sections (Action tree, Data flow, and Dependency graph)
Pipeline — inline, no subagent
resolve_bot.py → BotDefinition + BotVersion + planner name lookup
retrieve_planner.py → Metadata API zip retrieve for GenAiPlannerBundle (+ NGA plugins if present)
parallel_retrieve.py → 6 parallel Tooling SOQL channels fan out from the planner id
(resolved by the `planner_definition_by_agent_chain` seed query):
- plugins_by_planner (GenAiPluginDefinition)
- planner_bundle_functions (GenAiPlannerFunctionDef join)
- functions_by_plugins (GenAiFunctionDefinition)
- planner_attrs_by_parent_ids (GenAiPlannerAttrDefinition)
- plugin_functions_by_plugin_ids (GenAiPluginFunctionDef join)
- plugin_instructions_by_plugin_ids (GenAiPluginInstructionDef)
parse_bundle.py → parse retrieved XML into normalized node shapes
parse_wave.py → BFS expansion: flow/apex/prompt refs discovered in nodes
→ SOQL for Flow/Apex bodies (batched by id list)
→ Metadata retrieve ONLY for GenAiPromptTemplate (+ NGA external plugins conditionally)
finalize.py → merge waves into metadata_tree.json
render_architecture.py → <agent>_<ver>_architecture.md + Mermaid invocation graph (capped at --max-mermaid-nodes)
Channel strategy — SOQL-first.
- Tooling SOQL for every normalized tree node (planner, plugins, functions, plugin-functions, plugin-instructions, planner-functions, planner-attrs) — 6 parallel channels keyed on planner id, plus the
planner_definition_by_agent_chainseed query that resolves the planner id from the agent chain. - Data API SOQL for Flow (by id) and Apex (by id or name) bodies — batched.
- Metadata retrieve only for two cases: (a)
GenAiPromptTemplate(prompt bodies aren't cleanly exposed via Tooling SOQL), and (b) NGA external plugins when the planner is Native Generative Agent shape (skipped for classic ReAct).
This is where the 3–5× speedup comes from. A naive implementation would retrieve everything via Metadata API zips sequentially; parallel Tooling SOQL covers ~80% of the tree in a single fan-out.
Planner shapes — classic ReAct vs NGA
The skill normalizes two planner families into a single tree shape:
| Shape | GenAiPlannerDefinition.PlannerType | InvocationTarget style | NGA plugins? |
|---|---|---|---|
| Classic ReAct | ReactAiPlannerV1 / SequentialPlannerIntentClassifier / etc. | DeveloperName strings | no |
| NGA | ConcurrentMultiAgentOrchestration / AnthropicCompatibleV1 / etc. | Sometimes 15/18-char Ids (ID-prefix routed) | yes (external plugins via Metadata retrieve) |
The ID-prefix router in resolve_invocation_target.py distinguishes the two: NGA InvocationTargets that look like ids (01p… = ApexClass, 301… = Flow, etc.) get resolved via id-scoped SOQL; DeveloperName targets go through name-scoped SOQL. Unknown prefixes surface as _unresolved[] with reason="unknown-id-prefix:<prefix>" — never silently dropped.
Caching
- Tree cache:
metadata_tree.jsonis reused unless--forceis passed. Cache key includes the asset-hash of every.soql/.yaml/.mmdtemplate bundled with the skill — bump a template, the cache busts automatically. - Channel probe cache: 7-day TTL on the per-org
sf sobject describeresults that validate every field name the SOQL assets reference. A Salesforce quarterly release that renames / removes a field triggersstatus: PROBE_FAILED;--reprobeforces a refresh.
Prerequisites
| Tool | Required |
|---|---|
sf CLI (authenticated against the target org) | yes — sf org login web --alias <alias> |
| Python 3.10+ | yes |
Reference docs to load when needed
Do NOT load eagerly. Load when the user's question requires it:
references/soql_fields.md— per-sObject field reference for the 13 sObjects this skill touches (2 Data API + 11 Tooling), with[mandatory]vs[optional]tags. Load when the user asks about a specific field, or when debugging anINVALID_FIELDSOQL error.references/contract.json— machine-readable schema formetadata_tree.json. Load when writing downstream tooling that consumes the tree.references/architecture_sections.md— section-by-section structure of the rendered<agent>_<ver>_architecture.md.
Invariants worth knowing upfront
- Pipeline is deterministic. Same
(org, agent, version)+ static org metadata → byte-identical<agent>_<ver>_metadata_tree.jsonand<agent>_<ver>_architecture.md. Only manifest timestamps drift across re-runs. - Forward-only traversal. Every discovered ref goes forward from planner → children. No backward lookups.
- Partial results are surfaced, not silenced. Any unresolved reference lands in
_unresolved[]withreason=....STATUS=PARTIAL_OKif any channel failed;STATUS=OKonly on a clean run. - Cycle detection is per-branch. Same flow visited along its own ancestor chain emits
_cycle_back_to:<path>instead of recursing. A defensiveMAX_BFS_DEPTH=20guard backs the per-branch ancestor set; real-world agents bottom out well before either limit fires. (Earlier docs claimed a hard cap of 5; that was the historical limit and was abandoned because shared utility flows likehandleFlowFaulttripped it on every nested tree — seeconfig.MAX_BFS_DEPTHfor the rationale.) - Child ordering is alphabetical by
api_name(case-insensitive). Topics come before non-topic plannerActions at the root level. Flow-actionCall order is NOT sorted — that's the flow author's execution sequence.
Related skills
More from forcedotcom/sf-skills and the wider catalog.

agentforce-bot-upgrade
Upgrade Einstein Bots to Agentforce agents end-to-end in a single orchestrated pass.

agentforce-d360-analyze
Reconstruct and analyze a single Agentforce session from Data Cloud in 10–30 seconds.

agentforce-generate
Build, modify, audit, repair, optimize, debug, and deploy Salesforce Agentforce agents with Agent Script.

agentforce-observe
Analyze production Agentforce agent behavior via session traces, Data Cloud, and Agent Health Monitoring alerts.

agentforce-test
Write, run, and analyze functional and security test suites for Agentforce agents.

analyzing-omnistudio-dependencies
Detect namespaces, map dependencies, and visualize impact across OmniScripts, FlexCards, Integration Procedures, and Data Mappers.