sf-docs-agent 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -335
- package/agents/sf-docs.md +13 -7
- package/fetch-docs.mjs +8 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,388 +1,113 @@
|
|
|
1
1
|
# sf-docs-agent
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
A Claude Code subagent that answers Salesforce questions from the official docs and hands back quotes, not pages.
|
|
6
|
-
|
|
7
|
-
It reads developer.salesforce.com, architect.salesforce.com, help.salesforce.com, admin.salesforce.com and
|
|
8
|
-
lightningdesignsystem.com. The model picks the lines and a script copies them from the cached page,
|
|
9
|
-
each with the official URL it came from, so you can check any quote with one grep.
|
|
10
|
-
|
|
11
|
-
I built this because every docs lookup was dumping whole pages into my session. I only wanted the lines that answer my question.
|
|
3
|
+
**Read the docs, keep your context.** A Claude Code agent that reads the official Salesforce docs for you and returns just the lines that answer, each with its official URL.
|
|
12
4
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
A skill pulls every page it reads into your main session. The agent reads them in its own context and returns only
|
|
16
|
-
the lines you asked for.
|
|
5
|
+
[](https://www.npmjs.com/package/sf-docs-agent)
|
|
17
6
|
|
|
18
|
-
Salesforce's own
|
|
19
|
-
[`platform-docs-get`](https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-docs-get). In its own words,
|
|
20
|
-
it is an "official Salesforce documentation retrieval skill" for the same five sites, "especially when pages are
|
|
21
|
-
JS-heavy, shell-rendered, or hard to extract with naive fetching."
|
|
7
|
+
**84% fewer tokens** added to your session than Salesforce's own docs skill · **117 of 117** cited lines still on today's page · **every line** checkable with one `grep`
|
|
22
8
|
|
|
23
|
-
|
|
9
|
+
[Install](#install) · [Use](#use) · [How it works](#how-it-works) · [How it compares](#how-it-compares) · [Limitations](#limitations) · [Benchmarks](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/benchmarks.md)
|
|
24
10
|
|
|
25
|
-
|
|
26
|
-
|---|---|---|
|
|
27
|
-
| Runtime | Python, `playwright`, `playwright-stealth` | Node, `playwright-core` |
|
|
28
|
-
| Bot checks | spoofs a desktop user agent, optional stealth | neither |
|
|
29
|
-
| developer, architect and LDS pages | renders and scrapes the HTML | reads the `.md` copy where one exists |
|
|
30
|
-
| Finding pages | you give the URL | `llms.txt` indexes, sitemaps and link lists |
|
|
31
|
-
| What lands in your context | every page | a report of quotes |
|
|
32
|
-
| Checking an answer | re-read the page | `grep` one line |
|
|
11
|
+
---
|
|
33
12
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
the padding in table rows.
|
|
13
|
+
| Salesforce's platform-docs-get skill · 4,578 tokens | sf-docs-agent · 303 tokens |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Every page it read lands in your session | Only the lines that answer, each with its official URL |
|
|
38
16
|
|
|
39
|
-
|
|
17
|
+
*"How many versions can a flow have?"* Same right answer. 4,578 tokens become 303.
|
|
40
18
|
|
|
41
|
-
|
|
42
|
-
subagent it was faster and cheaper than sf-docs-agent in my tests. But it searches a closed index you can't see into, with
|
|
43
|
-
no date on its results.
|
|
19
|
+
The agent reads the pages in its own context. The model picks the lines and a script copies them word for word from the cached page, so no quote is written from the model's memory.
|
|
44
20
|
|
|
45
|
-
I
|
|
46
|
-
Salesforce publishes today, and every quote can be checked against that page with one `grep`. If you're fine
|
|
47
|
-
trusting the index, the MCP in a subagent is a good choice. [The comparison](#compared-with-salesforce-docs-mcp) has
|
|
48
|
-
the numbers and the trade-offs.
|
|
21
|
+
I built this because every docs lookup was dumping whole pages into my Claude Code session. I only wanted the lines that answer my question.
|
|
49
22
|
|
|
50
|
-
##
|
|
23
|
+
## Install
|
|
51
24
|
|
|
52
|
-
|
|
53
|
-
added 84% fewer tokens to your context and used 38% fewer tokens over the whole session, with the right answer on
|
|
54
|
-
39 of 40 runs for each. In the second of four rounds, platform-docs-get missed question 5 and sf-docs-agent missed
|
|
55
|
-
question 4.
|
|
25
|
+
Needs Node 22+ and Claude Code.
|
|
56
26
|
|
|
57
|
-
|
|
27
|
+
```
|
|
28
|
+
npx sf-docs-agent
|
|
29
|
+
```
|
|
58
30
|
|
|
59
|
-
|
|
60
|
-
|---|---|---|---|---|---|
|
|
61
|
-
| 1. Scratch org editions | developer | 2,178 → **289** | **86.7%** | 186,768 → **119,355** | **36.1%** |
|
|
62
|
-
| 2. Platform event allocation | developer | 4,107 → **490** | **88.1%** | 245,482 → **120,396** | **51.0%** |
|
|
63
|
-
| 3. QueryLocator batch limits | developer | 2,568 → **581** | **77.4%** | 217,691 → **113,611** | **47.8%** |
|
|
64
|
-
| 4. Integration patterns | architect | 1,104 → **484** | **56.2%** | 127,043 → **125,540** | **1.2%** |
|
|
65
|
-
| 5. Salesforce Connect editions | help | 1,948 → **322** | **83.5%** | 201,770 → **157,746** | **21.8%** |
|
|
66
|
-
| 6. Flow version limits | help | 4,578 → **290** | **93.7%** | 217,138 → **164,888** | **24.1%** |
|
|
67
|
-
| 7. Headless 360 | admin | 3,772 → **380** | **89.9%** | 250,200 → **105,388** | **57.9%** |
|
|
68
|
-
| 8. SLDS tooltips | lightningdesignsystem | 3,876 → **414** | **89.3%** | 201,906 → **112,252** | **44.4%** |
|
|
69
|
-
| 9. Bulk API batch size | developer | 2,134 → **411** | **80.7%** | 255,340 → **125,870** | **50.7%** |
|
|
70
|
-
| 10. Pub/Sub fetch limit | developer | 1,199 → **716** | **40.3%** | 141,826 → **130,110** | **8.3%** |
|
|
71
|
-
| **All** | | 27,464 → **4,377** | **84.1%** | 2,045,164 → **1,275,156** | **37.7%** |
|
|
31
|
+
Then start a new Claude Code session. The agent is listed as `sf-docs`. Add `--project` to install it for the current project only.
|
|
72
32
|
|
|
73
33
|
<details>
|
|
74
|
-
<summary>
|
|
75
|
-
|
|
76
|
-
1. What are the valid values for edition in a scratch org definition file?
|
|
77
|
-
2. What is the hourly platform event publishing allocation for Enterprise Edition and for Developer Edition?
|
|
78
|
-
3. With a QueryLocator start method, what is the maximum batch scope, and how many records can the QueryLocator return?
|
|
79
|
-
4. Which integration patterns does the architect Integration Patterns guide list?
|
|
80
|
-
5. Which editions include the Salesforce Connect cross-org adapter?
|
|
81
|
-
6. How many versions can a flow have, and how many active flows per flow type in Enterprise vs Professional?
|
|
82
|
-
7. What three technologies is Salesforce Headless 360 built on, and does it replace the Lightning UI?
|
|
83
|
-
8. Should you put an SLDS tooltip on a non-interactive element?
|
|
84
|
-
9. How many records fit in one Bulk API batch, and in one sObject Collections create request?
|
|
85
|
-
10. How many events can a Pub/Sub API client request across all FetchRequests in one Subscribe call?
|
|
86
|
-
|
|
87
|
-
</details>
|
|
34
|
+
<summary>Headless browser, Linux, update, remove, cache</summary>
|
|
88
35
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
setups:
|
|
95
|
-
|
|
96
|
-
a. MCP in main session: the main agent calls the MCP tools directly
|
|
97
|
-
|
|
98
|
-
b. MCP in a subagent: a subagent whose only tools are the MCP's, with the same report format as sf-docs-agent
|
|
99
|
-
|
|
100
|
-
Three rounds each, medians, same model.
|
|
101
|
-
|
|
102
|
-
| | sf-docs-agent | a. MCP in main session | b. MCP in a subagent |
|
|
103
|
-
|---|---|---|---|
|
|
104
|
-
| Tokens added to context | **4,470** | 45,636 | 5,327 |
|
|
105
|
-
| Session token usage | 1,291,504 | 1,052,673 | **937,229** |
|
|
106
|
-
| Seconds, all 10 | 433 | 239 | **218** |
|
|
107
|
-
| Right answers, of 30 | **30** | 28 | 24 |
|
|
108
|
-
| Quotes that match the text the tools returned, all runs | **113 of 113** | 173 of 188 | 172 of 177 |
|
|
109
|
-
| Quotes on today's page, checked with sf-docs-agent's fetcher, all runs | **113 of 113** | 159 of 188 | 135 of 177 |
|
|
110
|
-
|
|
111
|
-
The MCP in a subagent is faster and uses 27% fewer session tokens than sf-docs-agent, and a. MCP in main session uses
|
|
112
|
-
18% fewer. Its results come back as markdown excerpts, so I suspect Salesforce keeps a `.md` snapshot of the docs taken
|
|
113
|
-
at some point. Because the index is closed, you can't see what it holds or when it was taken, and that has some costs:
|
|
114
|
-
|
|
115
|
-
- **Coverage.** Across 91 searches it returned no admin.salesforce.com page and no SLDS 2 content page. The
|
|
116
|
-
subagent answered "not found" or wrong on Headless 360 and SLDS tooltips in all three rounds, and those two
|
|
117
|
-
questions hold all 8 wrong MCP answers. In the main session, the model also reached for web search or fetch on 27
|
|
118
|
-
of 30 runs and skipped the MCP on 4; its Headless 360 answers came from admin.salesforce.com through web fetch, not
|
|
119
|
-
the index.
|
|
120
|
-
- **Freshness.** Results carry no date, and some come from the old atlas copy of a page. On question 2 the quotes were
|
|
121
|
-
exact, but the source URL now opens a page without them. On question 1 the wording is older than today's page, with
|
|
122
|
-
the same values. On both questions the values matched the answer key in every MCP run.
|
|
123
|
-
- **Open vs closed.** The index has no published source or refresh schedule. sf-docs-agent reads the live page every time,
|
|
124
|
-
and every quote can be checked with one `grep`.
|
|
125
|
-
|
|
126
|
-
Most of the speed gap is page rendering. Of sf-docs-agent's 30 runs, the 18 that read at least one `.md` page took a
|
|
127
|
-
median 26.5 seconds, against 20.5 for the MCP subagent on the same questions and rounds. Help, admin and the
|
|
128
|
-
Apex Developer Guide had no `.md` copy, so sf-docs-agent rendered them in a headless browser, and the 12 runs that
|
|
129
|
-
rendered every page took a median 68.5 seconds, against 20 for the MCP subagent. The Apex Developer Guide and Apex
|
|
130
|
-
Reference had `.md` copies by 2026-10-31, after these runs; Help, admin, Metadata API and Object Reference still have
|
|
131
|
-
none.
|
|
132
|
-
|
|
133
|
-
<details>
|
|
134
|
-
<summary>Per question, sf-docs-agent / a. MCP in main session / b. MCP in a subagent</summary>
|
|
135
|
-
|
|
136
|
-
| Question | Tokens added to context | Seconds | Right answers, of 3 |
|
|
137
|
-
|---|---|---|---|
|
|
138
|
-
| 1. Scratch org editions | 248 / 5,011 / 421 | 23 / 22 / 18 | 3 / 3 / 3 |
|
|
139
|
-
| 2. Platform event allocation | 526 / 6,177 / 713 | 29 / 28 / 21 | 3 / 3 / 3 |
|
|
140
|
-
| 3. QueryLocator batch limits | 728 / 8,304 / 531 | 68 / 18 / 20 | 3 / 3 / 3 |
|
|
141
|
-
| 4. Integration patterns | 463 / 4,863 / 788 | 25 / 21 / 22 | 3 / 3 / 3 |
|
|
142
|
-
| 5. Salesforce Connect editions | 300 / 3,556 / 381 | 84 / 17 / 18 | 3 / 3 / 3 |
|
|
143
|
-
| 6. Flow version limits | 307 / 2,817 / 291 | 59 / 20 / 19 | 3 / 3 / 3 |
|
|
144
|
-
| 7. Headless 360 | 374 / 4,232 / 772 | 60 / 31 / 31 | 3 / 3 / 0 |
|
|
145
|
-
| 8. SLDS tooltips | 403 / 1,231 / 617 | 32 / 40 / 33 | 3 / 1 / 0 |
|
|
146
|
-
| 9. Bulk API batch size | 489 / 6,284 / 529 | 27 / 22 / 20 | 3 / 3 / 3 |
|
|
147
|
-
| 10. Pub/Sub fetch limit | 632 / 3,161 / 284 | 26 / 20 / 16 | 3 / 3 / 3 |
|
|
36
|
+
- **Headless browser:** some sites have no `.md` copy of their pages, so the agent opens them in a headless browser. The installer asks before it downloads Chromium (about 200 MB). Pass `--yes` to skip the question.
|
|
37
|
+
- **Linux:** also run `node "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/sf-docs/node_modules/playwright-core/cli.js" install-deps chromium` once.
|
|
38
|
+
- **Update:** `npx sf-docs-agent@latest`
|
|
39
|
+
- **Remove:** delete `~/.claude/agents/sf-docs.md` (or `.claude/agents/sf-docs.md` in the project), then `~/.claude/sf-docs/` once no project uses it. Chromium stays in Playwright's cache (`~/Library/Caches/ms-playwright` on macOS, `~/.cache/ms-playwright` on Linux); delete that too if nothing else uses Playwright.
|
|
40
|
+
- **Cache:** pages stay cached for 24 hours. Set `SF_DOCS_CACHE_HOURS` in your Claude Code `settings.json` to change it (`0` fetches every time).
|
|
148
41
|
|
|
149
42
|
</details>
|
|
150
43
|
|
|
151
|
-
## How sf-docs-agent reads each site
|
|
152
|
-
|
|
153
|
-
The agent reads the `.md` copy of a page where the site publishes one. Not every Salesforce docs site does yet.
|
|
154
|
-
|
|
155
|
-
Give the script an `.html` or bare URL for a new developer docs, architect or LDS page and it fetches the `.md` copy
|
|
156
|
-
instead. The same goes for an atlas URL that now redirects to the new docs.
|
|
157
|
-
|
|
158
|
-
The agent finds pages through the site's `llms.txt` index or its sitemaps.
|
|
159
|
-
|
|
160
|
-
| Site | How | Where it finds pages |
|
|
161
|
-
|---|---|---|
|
|
162
|
-
| developer.salesforce.com, new docs | the `.md` copy Salesforce publishes | `/docs/llms.txt` and one index per product |
|
|
163
|
-
| developer.salesforce.com, Apex Developer Guide and Apex Reference | the `.md` copy (atlas URLs redirect to the new docs, by 2026-10-31) | `/docs/llms-product-docs.txt` |
|
|
164
|
-
| developer.salesforce.com, atlas docs (Metadata API, Object Reference) | renders the page, code samples included | `/docs/llms-product-docs.txt` |
|
|
165
|
-
| developer.salesforce.com, API reference pages (`?meta=<operation>`) | renders the page | the product's index |
|
|
166
|
-
| architect.salesforce.com | the `.md` copy | `/llms.txt` |
|
|
167
|
-
| help.salesforce.com, articles and knowledge articles | renders the page | links in other docs and articles, and Help's product sitemaps |
|
|
168
|
-
| admin.salesforce.com | renders the page | `/sitemap_index.xml` and its post sitemaps |
|
|
169
|
-
| lightningdesignsystem.com (SLDS 2) | the `.md` copy | the home page's link list |
|
|
170
|
-
| v1.lightningdesignsystem.com (SLDS 1) | renders the page | the home page's link list |
|
|
171
|
-
|
|
172
44
|
## Use
|
|
173
45
|
|
|
174
|
-
Ask
|
|
175
|
-
Salesforce docs link and ask. It picks up the intent either way.
|
|
46
|
+
Ask in your own words. Name the site or product if you know it, or paste a Salesforce docs link and ask about it.
|
|
176
47
|
|
|
177
48
|
```
|
|
178
49
|
Go scan the developer.salesforce.com docs on the current release's governors and limits
|
|
179
50
|
```
|
|
180
51
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
The sample prompt is broad on purpose: I wanted to see the agent decide what "governors and limits" covers, and whether it could find everything in a broad question.
|
|
184
|
-
|
|
185
|
-
Here are the results of that prompt, one run each on Opus 5.5 and Sonnet 5.5 at effort high. Each session called sf-docs-agent, which returned quotes with their
|
|
186
|
-
official URLs, and the session turned them into the answer.
|
|
187
|
-
|
|
188
|
-
<details>
|
|
189
|
-
<summary><b>Sonnet 5.5, effort high, through the agent</b>: 86 seconds, 34k agent tokens</summary>
|
|
190
|
-
<br>
|
|
191
|
-
|
|
192
|
-
> The `sf-docs` agent finished scanning the Apex governors and limits pages. All limit values below are quoted from the cached concept page, `apex_gov_limits.htm`. The release version is an inference, not a stated fact.
|
|
193
|
-
>
|
|
194
|
-
> ## Which release
|
|
195
|
-
> - The cached pages are the unversioned "current" docs, and they don't name a release.
|
|
196
|
-
> - The version dropdown lists **Summer '26 (API 67.0, path `262.0`)** as the newest versioned link.
|
|
197
|
-
> - The page's own sidebar links point to `264.0` paths. That is most likely **Winter '27 (API 68.0)**.
|
|
198
|
-
> - The agent did not fetch the 262 or 264 versioned pages to confirm.
|
|
199
|
-
>
|
|
200
|
-
> ## Per-transaction Apex limits
|
|
201
|
-
> Limits are listed as synchronous / asynchronous.
|
|
202
|
-
>
|
|
203
|
-
> | Limit | Sync | Async |
|
|
204
|
-
> |---|---|---|
|
|
205
|
-
> | SOQL queries | 100 | 200 |
|
|
206
|
-
> | SOQL rows retrieved | 50,000 | 50,000 |
|
|
207
|
-
> | DML statements | 150 | 150 |
|
|
208
|
-
> | DML rows | 10,000 | 10,000 |
|
|
209
|
-
> | Callouts | 100 | 100 |
|
|
210
|
-
> | `@future` methods per invocation | 50 | 0 in batch and future contexts, 50 in queueable |
|
|
211
|
-
> | `System.enqueueJob` | 50 | 1 |
|
|
212
|
-
> | Heap | 10 MB | 25 MB |
|
|
213
|
-
> | CPU time | 10,000 ms | 60,000 ms |
|
|
214
|
-
> | Execution time | 10 minutes | 10 minutes |
|
|
215
|
-
>
|
|
216
|
-
> - **Enforcement:** "the Apex runtime engine strictly enforces limits", and exceeding a limit raises a runtime exception "that can't be handled."
|
|
217
|
-
> - **Reset and scope:** For Batch Apex, limits reset for each `execute` call. Synchronous limits apply to scheduled Apex. Bulk API transactions get the higher of the sync and async limits.
|
|
218
|
-
> - **Heap:** Exceeding it gives an uncatchable `System.LimitException`. Email services get a 50 MB heap.
|
|
219
|
-
>
|
|
220
|
-
> ## Certified managed packages
|
|
221
|
-
> - Each certified namespace gets its own per-transaction limits.
|
|
222
|
-
> - The cumulative limit across namespaces is 11 times the per-namespace limit. That gives 1,100 SOQL queries and 1,650 DML statements.
|
|
223
|
-
> - Heap, CPU, execution time and the unique-namespace count are not counted separately per package.
|
|
224
|
-
>
|
|
225
|
-
> ## Platform, static and size limits
|
|
226
|
-
> - **Async executions per 24 hours:** 250,000, or licenses × 200, whichever is greater.
|
|
227
|
-
> - **Concurrent long-running transactions (over 5 seconds):** one per 100 licenses.
|
|
228
|
-
> - **Scheduled classes:** 100 (5 in Developer Edition).
|
|
229
|
-
> - **Batch jobs queued or active:** 5.
|
|
230
|
-
> - **Callout timeout:** 10 seconds by default.
|
|
231
|
-
> - **Callout request or response size:** 6 MB synchronous, 12 MB asynchronous.
|
|
232
|
-
> - **Apex code per org:** 6 MB.
|
|
233
|
-
> - **Method size:** 65,535 bytecode instructions.
|
|
234
|
-
> - **Single email:** 5,000 external addresses per day per org (GMT).
|
|
235
|
-
> - **Push notifications:** 20,000 iOS and 10,000 Android per hour.
|
|
236
|
-
> - **Governor limit email warnings:** The default threshold is 50% of allocated limits, and only per-request limits are checked.
|
|
237
|
-
>
|
|
238
|
-
> ## Gaps
|
|
239
|
-
> - **Not fetched:** The agent did not read the "Elastic Limits for Asynchronous Apex Jobs (Beta)" page or `apex_limits_tips.htm`.
|
|
240
|
-
> - **Not quoted:** Several rows on the same page weren't quoted:
|
|
241
|
-
> - push notification calls per transaction
|
|
242
|
-
> - Apex cursor limits
|
|
243
|
-
> - inbound email limits
|
|
244
|
-
> - the 450,000 `MAX_DML_ROWS` limit in tests
|
|
245
|
-
> - **"Lightweight limits":** This isn't a section name in the docs.
|
|
246
|
-
>
|
|
247
|
-
> ## Sources
|
|
248
|
-
> - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm
|
|
249
|
-
> - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits_emails.htm
|
|
250
|
-
>
|
|
251
|
-
> The pages are cached locally, so you can check any quote with `grep -c -F '<quote>' <cache path>`. The cache is under `~/.claude/sf-docs/cache/developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/`.
|
|
252
|
-
>
|
|
253
|
-
> I can have the agent fetch the 262 and 264 versioned pages to pin down the release, or cover the elastic limits page.
|
|
254
|
-
|
|
255
|
-
</details>
|
|
256
|
-
|
|
257
|
-
<details>
|
|
258
|
-
<summary><b>Opus 5.5, effort high, through the agent</b>: 89 seconds, 39k agent tokens</summary>
|
|
259
|
-
<br>
|
|
52
|
+
Part of what came back (Opus 5.5, 89 seconds):
|
|
260
53
|
|
|
261
54
|
> I read the Apex Developer Guide page **Execution Governors and Limits**. I spot-checked several of the quotes against the cached copy of the page and they match.
|
|
262
55
|
>
|
|
263
|
-
> **Which release:** the page doesn't name its release. Its links point to the 264.0 doc set, and its list of older versions starts at Summer '26 (API 67.0). So this is most likely **Winter '27 (API 68.0)**, but that's my inference, not something the page says. The page also doesn't mark any limit as new or changed in this release.
|
|
264
|
-
>
|
|
265
|
-
> ### Per-transaction Apex limits (synchronous / asynchronous)
|
|
266
56
|
> | Limit | Sync | Async |
|
|
267
57
|
> |---|---|---|
|
|
268
58
|
> | SOQL queries | 100 | 200 |
|
|
269
|
-
> | SOQL rows retrieved | 50,000 | 50,000 |
|
|
270
|
-
> | `Database.getQueryLocator` rows | 10,000 | 10,000 |
|
|
271
|
-
> | SOSL queries / rows per query | 20 / 2,000 | 20 / 2,000 |
|
|
272
59
|
> | DML statements / rows | 150 / 10,000 | 150 / 10,000 |
|
|
273
|
-
> | Trigger recursion stack depth | 16 | 16 |
|
|
274
|
-
> | Callouts / cumulative callout timeout | 100 / 120 s | 100 / 120 s |
|
|
275
|
-
> | `@future` calls | 50 | 0 in batch and future; 50 in queueable |
|
|
276
|
-
> | `System.enqueueJob` | 50 | 1 |
|
|
277
|
-
> | `sendEmail` calls | 10 | 10 |
|
|
278
60
|
> | Heap | 10 MB | 25 MB |
|
|
279
61
|
> | CPU time | 10,000 ms | 60,000 ms |
|
|
280
|
-
> | Maximum execution time | 10 min | 10 min |
|
|
281
|
-
> | `EventBus.publish` calls (publish immediately) | 150 | 150 |
|
|
282
|
-
> | Apex cursor rows / `Cursor.fetch` calls | 50M / 100 | 50M / 100 |
|
|
283
|
-
> | Pagination cursors: rows / instances / rows per page | 100,000 / 50 / 2,000 | same |
|
|
284
|
-
>
|
|
285
|
-
> **Notes that change how these apply:**
|
|
286
|
-
> - **Scheduled Apex** runs under the synchronous limits.
|
|
287
|
-
> - **Bulk API** transactions get the higher of the sync and async values.
|
|
288
|
-
> - **Parent-child subqueries** each count as an extra query.
|
|
289
|
-
> - **Custom metadata queries** don't count toward the SOQL limit.
|
|
290
|
-
> - **Email services** get a 50 MB heap.
|
|
291
|
-
> - **CPU time** leaves out time spent waiting on the database and on callouts.
|
|
292
|
-
> - **Batch Apex** limits reset for each `execute` chunk.
|
|
293
62
|
>
|
|
294
|
-
>
|
|
295
|
-
> - **Async executions per 24 hours:** 250,000 or 200 × licenses, whichever is greater. Batch Apex checks up front that the whole job fits under this limit before it starts.
|
|
296
|
-
> - **Elastic async executions (beta)** can add up to 2 million more per day.
|
|
297
|
-
> - **Cursors per 24 hours:** 10,000 cursor instances, 100M cursor rows, and 200,000 pagination cursors.
|
|
298
|
-
> - **Long-running synchronous transactions (over 5 s):** 1 per 100 licenses, minimum 10 and maximum 50.
|
|
299
|
-
> - **Scheduled classes:** 100 (5 in Developer Edition).
|
|
300
|
-
> - **Batch Apex:**
|
|
301
|
-
> - Flex queue jobs in Holding status: 100
|
|
302
|
-
> - Jobs queued or active: 5
|
|
303
|
-
> - Concurrent `start` methods: 1
|
|
304
|
-
> - **Test classes queued per 24 hours:** the greater of 500 or 10× your test classes in production (20× in sandbox and Developer Edition).
|
|
305
|
-
>
|
|
306
|
-
> ### Static and size limits
|
|
307
|
-
> - **Callouts:** 10 s default timeout; request or response up to 6 MB sync or 12 MB async, and this counts toward heap.
|
|
308
|
-
> - **SOQL run time:** 120 s.
|
|
309
|
-
> - **Deployments:** 7,500 code units each.
|
|
310
|
-
> - **Trigger batch size:** 200, or 2,000 for platform events and Change Data Capture.
|
|
311
|
-
> - **`QueryLocator`:** 50M records.
|
|
312
|
-
> - **Code size:**
|
|
313
|
-
> - 1M characters per class or trigger.
|
|
314
|
-
> - 6 MB of Apex per org (10 MB in scratch orgs). Support can raise it, and managed-package and `@isTest` code don't count.
|
|
315
|
-
> - 65,535 bytecode instructions per method.
|
|
316
|
-
>
|
|
317
|
-
> ### Certified managed packages
|
|
318
|
-
> - Each certified namespace gets its own copy of the per-transaction limits.
|
|
319
|
-
> - The total across namespaces is capped at 11× the per-namespace limit: for example, 1,100 SOQL queries or 1,650 DML statements.
|
|
320
|
-
> - Heap, CPU time, execution time and the number of namespaces are shared across the whole transaction.
|
|
321
|
-
> - Code in non-certified packages counts against your org's own limits.
|
|
322
|
-
>
|
|
323
|
-
> ### Other limits on the page
|
|
324
|
-
> - **Single email:** 5,000 external addresses per day and 150 recipients per message.
|
|
325
|
-
> - **Push notifications:** 20,000 iOS and 10,000 Android per hour per org.
|
|
326
|
-
> - **Tests:** 450,000 DML rows per synchronous test run.
|
|
327
|
-
> - **ConnectApi:** each write costs one DML statement.
|
|
328
|
-
> - **Data.com Clean:** 200 SOQL queries and 10 future calls per batch.
|
|
329
|
-
>
|
|
330
|
-
> **Not covered:** I didn't read the Asynchronous Callout Limits page or the Winter '27 release notes. If you need to know what changed since Summer '26, the release notes are the next place to look.
|
|
331
|
-
>
|
|
332
|
-
> **Source:** https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm
|
|
63
|
+
> **Not covered:** I didn't read the Asynchronous Callout Limits page or the Winter '27 release notes.
|
|
333
64
|
|
|
334
|
-
|
|
65
|
+
It tells you what it did not read. A gap you can see beats a guess you can't.
|
|
335
66
|
|
|
336
|
-
|
|
67
|
+
It covers developer.salesforce.com, architect.salesforce.com, help.salesforce.com, admin.salesforce.com and lightningdesignsystem.com. Full outputs on Opus and Sonnet: [docs/examples.md](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/examples.md)
|
|
337
68
|
|
|
338
|
-
|
|
339
|
-
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
1. **Finds the page.** It searches the site's `llms.txt` index or sitemaps, or starts from the link you give it.
|
|
72
|
+
2. **Reads the cleanest copy.** Where Salesforce publishes a `.md` copy of a page (the new developer docs, architect, SLDS 2), it reads that. Help, admin and some older guides have none, so it renders them in a headless browser. No fake user agent, no stealth plugin.
|
|
73
|
+
3. **Picks lines, not prose.** The model reads the cached page in the agent's own context and picks line numbers. A script copies those lines, each with its official URL.
|
|
74
|
+
4. **Names what it missed.** A page it did not read, or a link it did not follow, comes back as a gap.
|
|
75
|
+
|
|
76
|
+
To check a line, replace `<cache path>` (the agent's Sources name it) and `<quote>` below. Apostrophes are safe, and the cache saves curly quotes as straight ones, so a quote you retype still matches. Keep one quote on one line with no blank line around it, since grep counts a blank line as matching every line. Any count above 0 means the line is on the page.
|
|
340
77
|
|
|
341
78
|
```
|
|
342
|
-
|
|
343
|
-
|
|
79
|
+
grep -c -F -f - <cache path> <<'EOF'
|
|
80
|
+
<quote>
|
|
81
|
+
EOF
|
|
344
82
|
```
|
|
345
83
|
|
|
346
|
-
|
|
347
|
-
`--yes` to skip the question in scripts. If you say no, it prints the command to run before first use. On Linux, also
|
|
348
|
-
run `node "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/sf-docs/node_modules/playwright-core/cli.js" install-deps chromium` once
|
|
349
|
-
for the system libraries. It asks for your password itself.
|
|
84
|
+
Every site, and how it reads each one: [docs/how-it-works.md](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/how-it-works.md)
|
|
350
85
|
|
|
351
|
-
|
|
86
|
+
## How it compares
|
|
352
87
|
|
|
353
|
-
|
|
354
|
-
current project.
|
|
88
|
+
Salesforce has two official options for the same job. I ran both on the same 10 questions.
|
|
355
89
|
|
|
356
|
-
**
|
|
357
|
-
live in `~/.claude/sf-docs/` for every scope, so delete that folder last, once no project uses the agent. If
|
|
358
|
-
`CLAUDE_CONFIG_DIR` is set, the installer uses that instead of `~/.claude`.
|
|
90
|
+
**[platform-docs-get](https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-docs-get)**, a skill in Salesforce's sf-skills library, pulls every page it reads into your session. Against it, sf-docs-agent added 84% fewer tokens to your session and used 34% fewer tokens overall. Both got 39 of 40 runs right.
|
|
359
91
|
|
|
360
|
-
**
|
|
361
|
-
`SF_DOCS_CACHE_HOURS` in your Claude Code `settings.json` (`0` fetches every time):
|
|
92
|
+
**[Salesforce Docs MCP](https://labs.agentforce.com/docs/salesforce-docs-mcp)** searches a closed index with no dates on its results. It is the same trade-off you accept with [Context7](https://github.com/upstash/context7), whose README says its parsing and crawling engines are private: you can't see what the index holds or when it was taken. In my runs it returned no page from admin.salesforce.com or SLDS 2. In a subagent it got 24 of 30 right against sf-docs-agent's 29 of 30, and only 135 of its 177 quotes were on today's page, against 117 of 117. Called from the main session it got 28 of 30, but added 10 times as many tokens to your session.
|
|
362
93
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
94
|
+
**Where sf-docs-agent loses:** the MCP in a subagent is faster and cheaper: 218 seconds against 346 for all 10 questions, and 29% fewer tokens. If you are fine trusting the index, it is a good choice. I wanted something I control and can check against the live sites.
|
|
95
|
+
|
|
96
|
+
My numbers are from a rerun on 2 November 2026, after the agent started following two links. The other tools ran on 30 and 31 October 2026 and were not rerun, and the docs changed in between.
|
|
97
|
+
|
|
98
|
+
Every number, per question, with the method: [docs/benchmarks.md](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/benchmarks.md)
|
|
366
99
|
|
|
367
100
|
## Limitations
|
|
368
101
|
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
|
|
375
|
-
- No
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
- On a long list or table, the agent now and then stops a row early, for example five of six integration patterns.
|
|
379
|
-
Every quote it gives is still right; the missing row is the problem.
|
|
380
|
-
- If a site starts blocking headless browsers, the agent stops and reports it. It does not try to get around the block.
|
|
381
|
-
- What leaves your machine: the pages the agent fetches from those sites (and each site's `robots.txt`, which raw
|
|
382
|
-
fetches load first). No telemetry. Installing downloads `playwright-core` from npm and, if you agree, Chromium from
|
|
383
|
-
Playwright's servers.
|
|
384
|
-
- The agent has Bash. Its brief limits it to the script and the cache, but that is an instruction to the model, not a
|
|
385
|
-
sandbox.
|
|
102
|
+
- Help, admin and some older developer guides have no `.md` copy, so those pages are slower. If Salesforce changes their layout, the agent may read only part of an article. A daily check in this repo flags it.
|
|
103
|
+
- No Help search, because Help's `robots.txt` disallows it.
|
|
104
|
+
- From a page it found, it follows at most two more links in a row, and only toward a missing part of the answer. Anything further comes back as a gap.
|
|
105
|
+
- On a long list it sometimes stops a row early. The quotes it gives are still right.
|
|
106
|
+
- If a site blocks headless browsers, it stops and tells you. It does not try to get around the block.
|
|
107
|
+
- The agent has Bash. Its instructions limit it to its script and cache, but that is an instruction to the model, not a sandbox.
|
|
108
|
+
- No telemetry. Only the docs pages it fetches leave your machine.
|
|
109
|
+
|
|
110
|
+
How it reads each site, and the full list: [docs/how-it-works.md](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/how-it-works.md)
|
|
386
111
|
|
|
387
112
|
## Develop
|
|
388
113
|
|
|
@@ -393,8 +118,9 @@ sed "s|/path/to/sf-docs-agent|$PWD|g" agents/sf-docs.md > "${CLAUDE_CONFIG_DIR:-
|
|
|
393
118
|
npm test
|
|
394
119
|
```
|
|
395
120
|
|
|
396
|
-
`agents/sf-docs.md` names its script and cache as `/path/to/sf-docs-agent`. The `sed` line writes your copy with your
|
|
397
|
-
|
|
121
|
+
`agents/sf-docs.md` names its script and cache as `/path/to/sf-docs-agent`. The `sed` line writes your copy with your clone's path. Run it again after you edit the agent.
|
|
122
|
+
|
|
123
|
+
**Release:** open a PR that only bumps the version (`npm version patch --no-git-tag-version`, which changes `package.json` and `package-lock.json`). Merging it runs [`release.yml`](.github/workflows/release.yml): the tests, an install of the packed tarball, `npm publish` through npm trusted publishing (no token), a check that npm holds the tarball that was tested, then the `v<version>` tag and GitHub Release.
|
|
398
124
|
|
|
399
125
|
## License
|
|
400
126
|
|
package/agents/sf-docs.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sf-docs
|
|
3
|
-
description: Answers a question from official Salesforce docs (developer, architect, help, admin and lightningdesignsystem.com) with verbatim quotes the script copies from the cached page, each tied to its official URL and line. Use it proactively whenever the user wants something read, scanned, checked or looked up in Salesforce documentation, even without naming this agent ("what does Help say about X", "check the Apex guide for Y", "scan the docs on Z"), and when you do not know which page holds the answer. Brief it with the question, the site or product if known, and the width ("the one concept page" or "the whole doc set"). Delegate even for one page you already know, with its URL in the brief: a page is thousands of tokens in your context and the agent's report is about 400. A URL on one of those sites in the question is for this agent too; do not WebFetch or curl it, those get a 403
|
|
3
|
+
description: Answers a question from official Salesforce docs (developer, architect, help, admin and lightningdesignsystem.com) with verbatim quotes the script copies from the cached page, each tied to its official URL and line. Use it proactively whenever the user wants something read, scanned, checked or looked up in Salesforce documentation, even without naming this agent ("what does Help say about X", "check the Apex guide for Y", "scan the docs on Z"), and when you do not know which page holds the answer. Brief it with the question, the site or product if known, and the width ("the one concept page" or "the whole doc set"). Delegate even for one page you already know, with its URL in the brief: a page is thousands of tokens in your context and the agent's report is about 400. A URL on one of those sites in the question is for this agent too; do not WebFetch or curl it, those get a 403, an empty JavaScript shell or a raw HTML page. Show the user its Answer and Sources. To check a quote, run `grep -c -F -f - <cache path>` with the quote on stdin through a quoted heredoc (`<<'EOF'`), so an apostrophe in it cannot break the shell: one quote on one line, no blank line, since grep counts a blank line as matching every line. Reads docs only. Not for org state.
|
|
4
4
|
model: claude-sonnet-5-5
|
|
5
5
|
tools: Bash, Read
|
|
6
6
|
---
|
|
@@ -85,13 +85,17 @@ Every tool call costs you the whole conversation again, so use as few Bash calls
|
|
|
85
85
|
next page.
|
|
86
86
|
3. **Look.** To see the lines around a match, use Read with `offset` and `limit`, or `grep -n -C3 -e '<term>' <file>`:
|
|
87
87
|
both number lines as `--quote` counts them. `sed -n` and `head` do not, so never take a line number from them.
|
|
88
|
+
Leave quote marks out of a grep term and search the words on either side: a `'` ends the shell quote, and a
|
|
89
|
+
curly one from the question misses a page saved with straight ones.
|
|
88
90
|
Never read a page whole. When the answer is a list or a table (patterns, editions, values), Read from its first
|
|
89
91
|
item to the next heading before you quote: a `--terms` match list stops at its cap, and the last rows may not
|
|
90
92
|
hold your term.
|
|
91
|
-
4. **
|
|
92
|
-
part of the answer (a child article, a "Limitations for ..." heading with no text under it, an index entry
|
|
93
|
-
title matches the question, another post), fetch it now with `--terms`.
|
|
94
|
-
|
|
93
|
+
4. **Two hops at most.** If a matched line, a `.links.txt` match or a sentence you will quote names another page that
|
|
94
|
+
holds part of the answer (a child article, a "Limitations for ..." heading with no text under it, an index entry
|
|
95
|
+
whose title matches the question, another post), fetch it now with `--terms`. From that page, follow one more link
|
|
96
|
+
the same way when that link names the part still missing. A page named by the page the second hop reached stays a
|
|
97
|
+
Gap. Follow a link only for a missing part of the answer. A parent Help article often lists no editions or
|
|
98
|
+
permissions, and its child article does.
|
|
95
99
|
5. **Quote.** Pick the line numbers that carry the answer, from the matches or the Read. Then let the script copy them:
|
|
96
100
|
`node "/path/to/sf-docs-agent/fetch-docs.mjs" --quote <cache file>:<line>,<line> [<cache file>:<line> ...]`
|
|
97
101
|
(cache file relative to the cache dir or absolute). It prints the Evidence and Sources blocks verbatim, with the
|
|
@@ -102,7 +106,8 @@ Every tool call costs you the whole conversation again, so use as few Bash calls
|
|
|
102
106
|
|
|
103
107
|
- Run the script as written. Do not add a stealth plugin, a spoofed user agent or any other bot-check bypass:
|
|
104
108
|
plain headless Chromium gets 200 today. If the site answers 403 or a challenge page, stop and report
|
|
105
|
-
`BLOCKED: <status>`; do not try another route. WebFetch and curl get 403
|
|
109
|
+
`BLOCKED: <status>`; do not try another route. WebFetch and curl are not routes: they get a 403, an empty
|
|
110
|
+
JavaScript shell on Help, or raw HTML with no Source line for `--quote`.
|
|
106
111
|
- If the script prints a `BLOCKED:` line, or Node cannot find `playwright-core`, stop and report the error lines as
|
|
107
112
|
printed (after `BLOCKED:` they include the install command). Do not install anything yourself.
|
|
108
113
|
- Bash is for running the script and reading the cache dir. Write nothing anywhere else.
|
|
@@ -114,7 +119,8 @@ Every tool call costs you the whole conversation again, so use as few Bash calls
|
|
|
114
119
|
## Reading discipline
|
|
115
120
|
|
|
116
121
|
- Go to the exact concept page. A landing, overview or index page that only links onward is not evidence. The
|
|
117
|
-
caller's width ("the one concept page") still allows the
|
|
122
|
+
caller's width ("the one concept page") still allows the linked pages, up to two hops (step 4), that hold what the
|
|
123
|
+
question asks for.
|
|
118
124
|
- Quote verbatim. Carry every option an enumerating sentence lists, and quote any sentence that limits the scope
|
|
119
125
|
of the one you rely on ("X is intended only for Y").
|
|
120
126
|
- Two lines that disagree, on two pages or on one: quote both, name the files, do not pick one.
|
package/fetch-docs.mjs
CHANGED
|
@@ -74,8 +74,9 @@ const SOURCE = /^Source: [^\n]*\n/;
|
|
|
74
74
|
const isShell = b => /^\s*<(!doctype|html)/i.test(b);
|
|
75
75
|
// A twin is missing (render instead, and remember it) on a 404, or a 200 whose body is an HTML shell or empty.
|
|
76
76
|
const missingTwin = (status, body) => status === 404 || (status === 200 && (isShell(body) || !body.trim()));
|
|
77
|
-
// One page, whatever the #anchor or trailing slash; a Help article is
|
|
78
|
-
|
|
77
|
+
// One page, whatever the scheme (0.1.2 saved http:// Source lines), #anchor or trailing slash; a Help article is
|
|
78
|
+
// told apart by its ?id=.
|
|
79
|
+
const sameDoc = (a, b) => a.host === b.host && a.pathname.replace(/\/$/, '') === b.pathname.replace(/\/$/, '')
|
|
79
80
|
&& a.searchParams.get('id') === b.searchParams.get('id');
|
|
80
81
|
// Paths the agent will paste into Git Bash on Windows: forward slashes, which Node accepts too.
|
|
81
82
|
const slash = p => p.split(sep).join('/');
|
|
@@ -119,8 +120,9 @@ const grep = (file, t) => {
|
|
|
119
120
|
if (args[0] === '--quote') {
|
|
120
121
|
const files = [], ev = [];
|
|
121
122
|
for (const spec of args.slice(1)) {
|
|
122
|
-
const
|
|
123
|
-
|
|
123
|
+
const m = spec.match(/^(.+):(\d+(?:,\d+)*)$/); // greedy: a Windows C:\... colon stays in the path
|
|
124
|
+
if (!m) { console.error(`not <cache file>:<line>[,<line>...]: ${spec}`); continue; }
|
|
125
|
+
const file = isAbsolute(m[1]) ? m[1] : join(out, m[1]);
|
|
124
126
|
// Only cached official pages: inside the cache dir and starting with their Source: line.
|
|
125
127
|
const inside = relative(out, file);
|
|
126
128
|
let lines;
|
|
@@ -128,7 +130,7 @@ if (args[0] === '--quote') {
|
|
|
128
130
|
catch { lines = []; }
|
|
129
131
|
if (!lines[0]?.startsWith('Source: ')) { console.error(`not a cached page: ${slash(file)}`); continue; }
|
|
130
132
|
if (!files.includes(file)) files.push(file);
|
|
131
|
-
for (const n of
|
|
133
|
+
for (const n of m[2].split(',').map(Number)) {
|
|
132
134
|
const text = (lines[n - 1] || '').trim();
|
|
133
135
|
if (text && n > 1) ev.push(`- "${text}" [${files.indexOf(file) + 1}:${n}]`);
|
|
134
136
|
else console.error(`skipped ${slash(relative(out, file))}:${n}: empty, out of range or the Source line`);
|
|
@@ -186,7 +188,7 @@ const parse = a => {
|
|
|
186
188
|
const given = [], refused = [];
|
|
187
189
|
for (const a of args) {
|
|
188
190
|
const u = parse(a);
|
|
189
|
-
if (u && SITES.includes(u.host)) given.push(u);
|
|
191
|
+
if (u && SITES.includes(u.host)) { u.protocol = 'https:'; given.push(u); } // all https: one key, one Source line
|
|
190
192
|
else refused.push(u ? `${u.href}: not an official doc site` : `${a}: not a URL`);
|
|
191
193
|
}
|
|
192
194
|
// New developer docs, architect and LDS pages have a .md twin: fetch it raw rather than render the HTML page.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sf-docs-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Claude Code subagent that answers from official Salesforce docs (developer, architect, help, admin, lightningdesignsystem.com) with grep-checked quotes",
|
|
6
6
|
"devDependencies": {
|