关于 agentforce-architecture-analyze
用于读取 Salesforce Agentforce Agent 声明式元数据并生成架构快照的技能,面向需要了解 Agent 组件关系的开发者。它将规划器、主题、操作、Flow、Apex、提示词和插件整理为树与文档,不读取运行时审计记录。
主要功能
- 01读取 Agentforce 声明式元数据
- 02构建规划器到插件的依赖树
- 03并行查询并支持缓存
- 04生成 JSON 架构树与 Markdown 文档
- 05标记未解析引用和部分结果
适用场景
- 01梳理 Agent 的声明式架构
- 02查看主题、操作与 Flow 的依赖
- 03为 Agent 元数据生成文档
- 04定位未解析的元数据引用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill agentforce-architecture-analyze安装后在 Claude Code 中输入/agentforce-architecture-analyze确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdassets/cli/describe_sobject.yamldescribe_tooling_sobject.yamllist_metadata_genaiprompttemplate.yamlorg_display.yamlretrieve_genai_plugin.yamlshow_access_token.yamlassets/mermaid/action_tree.mmddata_flow.mmddependency_graph.mmdinvocation_sequence.mmdplanner_state.mmdassets/soql/apex_class_bodies_by_ids.soqlapex_class_bodies_by_names.soqlbot_definition_details.soqlbot_version_lookup.soqlflow_definition_by_ids.soqlflow_definition_ids_by_names.soqlflow_definition_view_by_durable_ids.soqlflow_metadata_by_id.soqlfunctions_by_plugins.soqlplanner_attrs_by_parent_ids.soqlplanner_bundle_functions.soqlplanner_definition_by_agent_chain.soqlplugin_functions_by_plugin_ids.soqlplugin_instructions_by_plugin_ids.soqlplugins_by_planner.soqlreferences/architecture_sections.mdcontract.jsonsoql_fields.md详细介绍
agentforce-architecture-analyze — 已声明的架构快照
一个 Agentforce Agent 的设计时元数据树:规划器 → 主题 → 操作 → 流 → Apex → 提示词 → NGA 插件。仅读取已声明的元数据:BotDefinition、GenAiPlanner*、GenAiPlugin*、GenAiFunction*、Flow、ApexClass、GenAiPromptTemplate。不会读取运行时审计记录。
运行时耗时预算:通常为 30–45s,硬上限为 ≤60s(基于参考 fixture)。顺序基线需要 90–220s;并行 Tooling SOQL 扇出可带来 3–5× 的加速。包含许多流的大型 Bot 会近似线性扩展——每次流元数据检索都需要一次往返。
内联运行——不使用子 Agent。每个阶段都是确定性的文件处理。
如果用户未指定 Agent
触发条件:$ARGUMENTS 未指定任何 Agent(没有 --agent 标志,且描述中没有完全匹配的 BotDefinition API 名称)。切勿臆造 Agent 名称。应改为发现该组织实际包含的内容——只执行一次只读查询,暂不运行流程。如有提供 --org 别名则使用它,否则使用默认目标组织(省略 --target-org):
sf data query --query "SELECT DeveloperName, MasterLabel FROM BotDefinition ORDER BY DeveloperName" \
[--target-org <alias>] --json
如果用户未提供组织别名,流程块会将 --org 默认为 CLI 的默认目标组织本身(sf config get target-org --json → .result[0].value)——无需解析。
然后根据返回的行进行分支处理:
- 没有 Agent — 告知用户该组织没有 Agentforce Agent,然后停止。不要编造 Agent 或架构树。
- 只有一个 Agent,或只有一个 Agent 的
DeveloperName/MasterLabel与用户描述相符(例如“support”“service”“sales”)— 使用该 Agent,明确说明选择了哪个 Agent 以及原因,然后运行下方的流程块,并将ARG_AGENT=<DeveloperName>(如果用户指定了组织,还需设置ARG_ORG=<alias>)作为首行,例如ARG_AGENT=Customer_Support_Agent。该代码块从环境读取ARG_*;$ARGUMENTS中的标志仍具有覆盖优先级。 - 其他情况 — 打印下方代码块,并在首行下方以编号
DeveloperName — MasterLabel表格列出发现的 Agent,然后等待用户选择。 - 查询失败(没有默认组织、认证错误)— 打印下方代码块,不附列表。
应记录哪个 Agent,以及在哪个组织中?
我需要:
- Agent API 名称 —
BotDefinition的DeveloperName(例如MyAgent、MySalesAgent),不是标签。- 组织别名 — 用于
sfCLI 认证的别名(通过sf org login配置的别名)可选项:
- 版本 — 类似
v5的agent_version_api_name。如果省略,我会解析活动的BotVersion。--force— 忽略缓存的架构树;重新获取所有内容。--reprobe— 重新运行 7-day channel-probe 缓存(仅在 Salesforce 发布新版本后需要)。我会内联运行元数据流程。产物将写入
~/.vibe/data/agentforce-architecture-analyze/<org_id15>/<agent_api_name>__<agent_version>/(可通过--data-dir覆盖)。
流程调用
确定 Agent 后运行此代码块——Agent 可以通过 $ARGUMENTS 中的 --agent <api_name> 指定,也可以在发现后预设为 ARG_AGENT=<DeveloperName>。--org(或 ARG_ORG)为可选项;不指定时使用 CLI 默认目标组织。一次 python3 调用即可驱动完整流程。main.py 会写入 .emit_ctx.json;emit_result.py 会读取该文件,并在 stdout 的末尾打印最终的 === RESULT === 区块。
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_DIR> = absolute path of this skill's own directory (the folder holding
# this SKILL.md); substitute it from the skill path in context. Runtimes install
# skills in different places, so also probe the known install roots and use the
# first that actually contains this skill's scripts. An exported SKILL_ROOT wins.
_skill=agentforce-architecture-analyze
for _c in "${SKILL_ROOT:-}" "<SKILL_DIR>" \
"${CLAUDE_PLUGIN_ROOT:+$CLAUDE_PLUGIN_ROOT/skills/$_skill}" \
"${VIBES_SKILLS_DIR:+$VIBES_SKILLS_DIR/$_skill}" \
"$HOME/.claude/skills/$_skill" "$PWD/.claude/skills/$_skill" \
"${PLUGIN_ROOT:-$HOME/.vibe/skills}/$_skill"; do
[ -n "$_c" ] || continue
# Some stagers (ADK eval) nest the bundled files under <skill>/artifacts/.
for _r in "$_c" "$_c/artifacts"; do
[ -f "$_r/scripts/main.py" ] && { SKILL_ROOT="$_r"; break 2; }
done
done
[ -f "${SKILL_ROOT:-}/scripts/main.py" ] || { echo "$_skill: scripts not found — set SKILL_ROOT to this skill's directory" >&2; exit 1; }
# Argument parser. Accepts both `--org foo` and `--org=foo`.
# `$ARGUMENTS` is the raw user input Claude Code substitutes; it may be unset
# or plain prose. Each ARG_* starts from the environment, so a value resolved
# during discovery can be preset (e.g. `ARG_AGENT=MyAgent` on the first line);
# flags in $ARGUMENTS still override.
ARG_ORG="${ARG_ORG:-}"
ARG_AGENT="${ARG_AGENT:-}"
ARG_VERSION="${ARG_VERSION:-}"
ARG_FORCE="${ARG_FORCE:-}"
ARG_REPROBE="${ARG_REPROBE:-}"
ARG_PARALLELISM="${ARG_PARALLELISM:-}"
ARG_MAX_MERMAID="${ARG_MAX_MERMAID:-}"
# Word-split $ARGUMENTS (zsh needs SH_WORD_SPLIT for that) with globbing off
# so prose like `*` never expands to filenames.
[ -n "${ZSH_VERSION:-}" ] && setopt SH_WORD_SPLIT
set -f
# shellcheck disable=SC2206
_args=(${ARGUMENTS:-})
set +f
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
# No org named anywhere: fall back to the sf CLI default target org
# (`sf config get target-org --json` -> .result[0].value). Extracted with sed
# so the block needs neither jq nor an inline interpreter; any failure leaves
# ARG_ORG empty and the usage block below fires.
if [ -z "$ARG_ORG" ] && command -v sf >/dev/null 2>&1; then
_cfg="$(sf config get target-org --json 2>/dev/null || true)"
_cfg="$(printf '%s\n' "$_cfg" | sed -n 's/.*"value"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' || true)"
ARG_ORG="${_cfg%%$'\n'*}"
fi
# 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`);
> only needed when no default target org is set (`sf config set target-org <alias>`)
>
> 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"
输入
| 输入 | 标志 | 必需 | 默认值 |
|---|---|---|---|
org_alias | --org | 否 | sf CLI 默认目标组织(sf config get target-org) |
agent_api_name | --agent | 是 | — |
agent_version_api_name | --version | 否 | active BotVersion |
force_refresh | --force | 否 | false(使用缓存) |
reprobe | --reprobe | 否 | false(使用 7 天的通道探测缓存) |
parallelism | --parallelism | 否 | 5 |
max_mermaid_nodes | --max-mermaid-nodes | 否 | 80 |
data_dir | --data-dir | 否 | ~/.vibe/data/agentforce-architecture-analyze |
cache_dir | --cache-dir | 否 | ~/.vibe/cache/agentforce-architecture-analyze |
输出
所有产物均位于 ~/.vibe/data/agentforce-architecture-analyze/<org_id15>/<agent_api_name>__<agent_version>/(默认路径;使用 --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)
流水线 — 内联,无子 Agent
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)
通道策略 — SOQL 优先。
- Tooling SOQL 用于每个规范化树节点(planner、plugins、functions、plugin-functions、plugin-instructions、planner-functions、planner-attrs)——按 planner id 进行 6 个并行通道查询,另有用于从 agent chain 解析 planner id 的
planner_definition_by_agent_chain种子查询。 - Data API SOQL 用于 Flow(按 id)和 Apex(按 id 或名称)主体——采用批处理。
- Metadata retrieve 仅用于两种情况:(a)
GenAiPromptTemplate(通过 Tooling SOQL 无法清晰获取提示词主体),以及 (b) 当 planner 属于 Native Generative Agent 类型时的 NGA 外部插件(classic ReAct 会跳过)。
3–5 倍的速度提升正是由此而来。朴素的实现会依次通过 Metadata API zip 检索所有内容;并行 Tooling SOQL 则可通过单次扇出查询覆盖约 80% 的树。
Planner 类型 — classic ReAct 与 NGA
该技能会将两种 planner 系列规范化为统一的树形结构:
| 类型 | GenAiPlannerDefinition.PlannerType | InvocationTarget 风格 | NGA 插件? |
|---|---|---|---|
| Classic ReAct | ReactAiPlannerV1 / SequentialPlannerIntentClassifier / 等 | DeveloperName 字符串 | 否 |
| NGA | ConcurrentMultiAgentOrchestration / AnthropicCompatibleV1 / 等 | 有时为 15/18 位 Id(按 ID 前缀路由) | 是(通过 Metadata retrieve 获取外部插件) |
resolve_invocation_target.py 中的 ID 前缀路由器可区分两者:看起来像 id 的 NGA InvocationTarget(01p… = ApexClass、301… = Flow 等)会通过限定 id 的 SOQL 解析;DeveloperName 目标则通过限定名称的 SOQL 查询。未知前缀会以 _unresolved[] 的形式显示,并带有 reason="unknown-id-prefix:<prefix>"——绝不会被静默丢弃。
缓存
- 树缓存:除非传入
--force,否则会复用metadata_tree.json。缓存键包含此技能捆绑的每个.soql/.yaml/.mmd模板的资产哈希值——更新模板后会自动使缓存失效。 - 通道探测缓存:每个组织的
sf sobject describe结果会缓存 7 天,用于验证 SOQL 资源引用的每个字段名称。如果 Salesforce 季度版本更新导致字段重命名或移除,会触发status: PROBE_FAILED;--reprobe会强制刷新。
先决条件
| 工具 | 必需 |
|---|---|
sf CLI(已通过目标组织认证) | 是 — sf org login web --alias <alias>,并且 CLI 必须提供 sf org auth show-access-token(启动预检会强制检查这一点) |
| Python 3.10+ | 是 |
按需加载的参考文档
不要提前加载。仅在用户的问题需要时加载:
references/soql_fields.md— 本技能涉及的 13 个 sObject 的字段参考(2 个 Data API + 11 个 Tooling),标注了[mandatory]与[optional]。用户询问特定字段,或调试INVALID_FIELDSOQL 错误时加载。references/contract.json—metadata_tree.json的机器可读架构。编写使用该树的下游工具时加载。references/architecture_sections.md— 渲染后的<agent>_<ver>_architecture.md的分节结构。
需要预先了解的不变量
- 管道是确定性的。 相同的
(org, agent, version)和静态组织元数据会生成字节完全相同的<agent>_<ver>_metadata_tree.json和<agent>_<ver>_architecture.md。只有清单时间戳会在重复运行时发生变化。 - 仅向前遍历。 每个发现的引用都从规划器指向子项。不进行反向查找。
- 部分结果会被呈现,而非隐藏。 任何未解析的引用都会写入
_unresolved[],并包含reason=...。如果任何通道失败,则STATUS=PARTIAL_OK;只有运行顺利完成时才为STATUS=OK。 - 循环检测按分支进行。 如果某个流程沿自身的祖先链再次被访问,则会发出
_cycle_back_to:<path>,而不是继续递归。防御性的MAX_BFS_DEPTH=20限制为按分支维护的祖先集合提供后备保护;实际 Agent 通常远在触及任一限制前就会遍历结束。(早期文档曾声称硬性上限为 5;这是历史限制,后来已弃用,因为像handleFlowFault这样的共享实用流程会在每个嵌套树中触发该限制——原因见config.MAX_BFS_DEPTH。) - 子项按
api_name字母顺序排列(不区分大小写)。 在根级别,Topics 排在非 Topic 规划器操作之前。Flow 的 actionCall 顺序不会排序——它反映的是流程作者设定的执行顺序。