@@ -204,14 +204,77 @@ are not served.
204204 (an error with ` strict=True ` ). Use ` FilesystemSkillProvider(Path("skills")) ` instead.
205205
206206!!! note
207- The catalog reads and hashes every file ** once** , when ` load() ` runs, and serves that snapshot.
208- That's how the ` digest ` and ` size ` in each skill's manifest are guaranteed to match what
209- ` resources/read ` returns.
207+ The catalog reads and hashes every file when ` load() ` runs. By default, it reads files from
208+ the provider again on each ` resources/read ` and checks their bytes against that fingerprint.
210209
211- It also means the catalog is fixed for the lifetime of the server. If a file changes
212- afterwards, reading it fails with an internal error (`-32603`) until you restart the server or
213- create a new catalog. And because `load()` reads every file, a large asset directory makes
214- startup slower.
210+ If a file changes afterwards, reading it fails with an internal error (`-32603`) until you
211+ [reload the catalog](#reloading-skills) or restart the server. And because `load()`
212+ reads every file, a large asset directory makes startup slower.
213+
214+ ### Reloading skills
215+
216+ While you're writing a skill you'll keep editing its files, adding skills and deleting others.
217+ Call ` reload() ` to pick all of it up without restarting the server:
218+
219+ ``` python
220+ async def refresh (skills_catalog : SkillCatalog) -> None :
221+ changes = await skills_catalog.reload()
222+ print (changes.added, changes.removed, changes.changed) # SKILL.md URIs
223+ ```
224+
225+ ` reload() ` scans the whole provider again, and only when every skill has been read and validated
226+ does it replace everything the catalog serves, in one step. ` skills/list ` , ` skills/get ` , resource
227+ and directory listings, and file reads all switch together, so no request sees a half-updated
228+ catalog. Skills and files can be added, removed or edited. It returns a ` SkillCatalogChanges `
229+ with the ` added ` , ` removed ` and ` changed ` skills.
230+
231+ It is all or nothing. If the scan fails, ` reload() ` raises and the previous catalog stays installed:
232+
233+ * the provider fails (` OSError ` ), or lists a path that isn't valid;
234+ * ` strict=True ` and any skill is invalid, as with ` load() ` ;
235+ * a skill the catalog is serving has become invalid. This holds even without ` strict ` , so a typo in
236+ the frontmatter mid-edit doesn't take a skill down. A skill that was already skipped at ` load() `
237+ stays skipped and doesn't block reloads.
238+
239+ Calls to ` reload() ` run one after another.
240+
241+ Without ` auto_refresh ` , file reads still go to the provider: a file that has changed or disappeared
242+ can fail to read even when the previous catalog stays installed. Enable ` auto_refresh ` to retain
243+ the previous files' bytes as well.
244+
245+ #### Reloading on a timer
246+
247+ Calling ` reload() ` yourself gets old fast. To have the catalog do it, turn on ` auto_refresh ` :
248+
249+ ``` python title="server.py" hl_lines="14 15"
250+ -- 8 < -- " docs_src/skills/tutorial003.py"
251+ ```
252+
253+ Every 30 seconds here, the catalog calls ` reload() ` . Automatic refresh is ** off by default** . With
254+ ` auto_refresh=True ` alone, ` refresh_interval ` defaults to 60 seconds; it must be a finite number
255+ greater than zero.
256+
257+ There's no task to start and nothing to stop: the timer runs with the server's lifespan, on stdio,
258+ SSE and streamable HTTP. SSE enters the lifespan once per connection, so there the timer runs only
259+ while a client is connected.
260+
261+ * ** A failed refresh changes nothing.** The error is logged and the previous skills keep serving.
262+ The catalog tries again after the next interval.
263+ * ** Scans never overlap.** The next interval starts after the previous scan finishes.
264+
265+ !!! note
266+ With ` auto_refresh ` , the catalog keeps the bytes of every served file ** in memory** . That's
267+ what lets the previous skills keep serving consistently, even if you edit or delete a file on
268+ disk while a refresh is still running. Without it, a file edited on disk reads as an internal
269+ error until the next ` reload() ` .
270+
271+ !!! warning
272+ Clients don't hear about a reload. They may keep a ` skills/list ` result for ` ttl_ms ` , and a
273+ manifest fetched before a reload can describe different bytes than a file fetched after it.
274+ Re-fetch the manifest and verify the digest before loading a changed skill.
275+
276+ Pagination cursors are offsets, not snapshots, so restart a paged listing if the catalog
277+ changed between pages.
215278
216279### Directory reads from a catalog
217280
@@ -286,7 +349,9 @@ File content is returned unchanged. A file whose bytes are valid UTF-8 is return
286349including any byte order mark and line endings; any other file is returned as a base64 blob. In
287350both cases the content matches the ` digest ` and ` size ` in the skill's manifest. Each read is
288351verified against the values recorded at load time, and a file that has changed is reported as an
289- internal error (` -32603 ` ) until you restart the server or create a new catalog.
352+ internal error (` -32603 ` ) until you [ reload the catalog] ( #reloading-skills ) .
353+ With [ ` auto_refresh ` ] ( #reloading-on-a-timer ) , reads instead return the retained snapshot's
354+ bytes until the next successful reload.
290355
291356` load() ` reads and hashes every file of every valid skill once, so a large asset directory makes
292357startup slower and temporarily uses memory for the largest file.
@@ -318,7 +383,7 @@ ones found while reading are refused, so a skill directory doesn't expose files
318383
319384The root is trusted. This is a best-effort check, not a defence against someone who can modify the
320385directory while the server runs. As a backstop, the catalog refuses to serve any file whose bytes
321- no longer match the digest taken at ` load() ` . Blocking file system calls run in a worker thread.
386+ no longer match the digest taken at ` load() ` or the last ` reload() ` . Blocking file system calls run in a worker thread.
322387
323388## Protocol version and caching
324389
@@ -333,14 +398,13 @@ You don't have to branch on protocol version yourself.
333398## What this SDK doesn't do
334399
335400` Skills ` is a protocol adapter, not a skills store. It has no opinion on where a skill's bytes
336- live or when a catalog is refreshed .
401+ live. ` SkillCatalog ` supplies ` reload() ` and an optional refresh timer .
337402
338403` SkillCatalog ` covers the common case of a directory (or any other file source behind a
339404` SkillProvider ` ), with a few limits:
340405
341406* It doesn't include a YAML parser. ` parse_frontmatter ` supplies one.
342- * The catalog isn 't reloaded. To publish changes, create a new catalog .
407+ * It doesn 't watch the file system or tell clients about a reload; call ` reload() ` or set ` auto_refresh ` .
343408* Empty directories aren't served by directory reads.
344409
345- For anything else, such as a database or a catalog that changes while the server runs, write your
346- own handlers for ` Skills ` .
410+ For anything else, write your own handlers for ` Skills ` .
0 commit comments