-
Notifications
You must be signed in to change notification settings - Fork 2
0.37.0
A skill that fans out to several external services — a "gather" — has a failure mode: one slow leg hangs the whole thing. This release makes the framework point you at the correct tool for that, after a real autonomous gather hung ~2 minutes on a slow search.
Upgrade impact: none (additive). No new behavior for existing skills — a lint advisory's guidance changed, and the remote connector now cancels properly when a call is cut.
There are two ways to put a time bound on a run, and picking the wrong one is the trap:
timeout=N (per-op) |
# Deadline: N (whole-run) |
|
|---|---|---|
| Scope | one leg | the entire run + everything it composes |
| On expiry |
catchable — (fallback:) fires, the run continues |
uncatchable — the whole run stops, healthy legs and all |
| Use for | a partial-tolerant gather | a hard total ceiling |
For a gather, you want per-leg timeout= + (fallback:) — so one slow leg times out, its fallback binds, and the other legs still deliver:
run:
$ ddg.search query="${Q}" timeout=8 -> HITS (fallback: [])
$ news.fetch topic="${Q}" timeout=8 -> NEWS (fallback: [])
$ llm prompt="Summarize: ${HITS} ${NEWS}" timeout=30 -> BRIEF
If ddg.search hangs, that leg is cut at 8s, HITS becomes [], and the brief still assembles from the rest. A # Deadline: here would do the opposite — when it trips, it kills the healthy legs too. Reserve # Deadline: for "this whole thing must finish within N seconds, period."
This tripped up a real author: ddg.search's tool schema lists {query, max_results, region} and no timeout, so it looked un-boundable. But timeout=N is a runtime kwarg — the runtime enforces it and never forwards it to the remote tool, so it works on any $ connector op regardless of what the remote server advertises. (Remote connectors also carry a 30s internal default, configurable per-connector.)
- The
unbounded-no-deadlinelint advisory used to nudge you toward# Deadline:— the wrong lever for a gather — and fired even when your legs were already bounded. Now it recognizes a$leg with its owntimeout=(and a skill-level# Timeout:) as bounded, and its guidance leads with per-legtimeout=+(fallback:), framing# Deadline:as the separate hard-ceiling tool. -
Remote connectors cancel properly. When a remote call is cut (by
timeout=or a deadline), the connector now aborts the in-flight request instead of leaving it hanging on the subprocess.
-
(fallback:)catches a timed-out leg, not a deadline-cut one. A per-optimeout=throws a catchable error; a# Deadline:does not (that's what makes "bounded to N seconds" literally true). So if you find yourself reaching for# Deadline:to make a gather resilient — that's the tell you want per-legtimeout=instead.