This release includes 2 breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
Affected surfaces
Summary
AI summaryUpdates Watch-items, Vars: LOCATION=Valdese, and Skill: get-weather across a mixed release.
Full changelog
Closes Perry's 1ea3d625 Finding 2 (execute_skill leaks full child
final_vars into the parent — 252KB MCP-response blowup on a 2-line
emit). Substrate-author's 6fb6ac1c empirical pre-flight (0/16 skills
reach ${R.final_vars.X}) confirmed the migration-free cut. Three-way
convergence: Perry's design ack (8ffa16d6) + substrate-author's
pre-flight (6fb6ac1c) + Perry's discipline-corrected catch
(5a7e4041) shaped the final scope.
Skills are functions — declare what you return
New # Returns: X, Y, Z frontmatter header declares the export surface
for execute_skill composition. The runtime filter at the composition
boundary (src/composition.ts:filterFinalVarsByReturns) drops every
final_vars entry whose name isn't in the declared list. Internal
scratch (large JSON, intermediate computations, debug values) stays
local to the child execution — never serialized into the caller's R
or the top-level MCP execute_skill response.
Concrete shape:
# Skill: get-weather
# Vars: LOCATION=Valdese
# Returns: SUMMARY, TEMP_F, CONDITIONS
fetch:
shell(command="curl -s 'wttr.in/${LOCATION|url}?format=j1'") -> RAW
$ json_parse ${RAW} -> PARSED
shape: fetch
$set TEMP_F = ${PARSED.current_condition.0.temp_F}
$set CONDITIONS = ${PARSED.current_condition.0.weatherDesc.0.value}
$set SUMMARY = "${LOCATION}: ${TEMP_F}°F and ${CONDITIONS}"
emit(text="${SUMMARY}")
default: shape
Caller binding -> R gets R.final_vars = {SUMMARY, TEMP_F, CONDITIONS} —
NOT RAW or PARSED. Always-exported (no declaration needed): outputs,
transcript, errors, target_order, fallbacks, agent_delivery_receipts.
Default: no Returns → no final_vars exported
Skills without # Returns: export nothing from final_vars. Caller still
sees outputs + transcript + execution metadata — the natural pattern
substrate-author's audit found in 100% of existing composers.
Pre-adoption rule (per project_pre_adoption_rule) makes the breaking
cut cheap; the migration is free for the canonical ${R.outputs.text}
pattern.
New lint: unknown-returns-ref (tier-1)
Fires when a name in # Returns: isn't bound anywhere in the skill body
(not in # Vars:/# Requires:, not output-bound, not a foreach iterator).
The skill claims to return something it never produces — structurally
broken, hard-blocked at compile.
Watch-items (not built, documented)
transcriptnests across composition depth. When A calls B calls C,
A'sR.transcriptholds B's emissions; B'sR.transcript(visible via
declared returns or transcript-of-transcript chains) can hold C's.
Bounded by intended emits per child; additive across siblings. Per
Perry's5a7e4041discipline-corrected catch — fix-ahead-of-need not
warranted today; revisit if deep-composition-with-large-emits bites.unexported-final-var-access(tier-2 advisory) — deferred to
v0.17.4. Would catch${R.X}where X isn't in the called skill's
# Returns:. Requires cross-skill resolution + accessor-pattern
parsing; warrants its own ring.
Test coverage
16 new tests (parser: 6, lint: 5, runtime: 5). Three-test discipline per
feedback_three_test_discipline_per_dispatch_shape. Full suite: 1613
tests pass.
Migration
Bundled examples + scaffold skills: zero changes needed. None reach
${R.final_vars.X} today (verified by substrate-author's audit). When
adopters want structured access to a composed child's results, they
add # Returns: X to the child skill.
Breaking Changes
- Skills without a `# Returns:` header export no entries from `final_vars`.
- Runtime filter at composition boundary drops any `final_vars` entry not listed in the skill's `# Returns:` header.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About Skillscript
All releases →Related context
Related tools
Earlier breaking changes
- v0.35.0 Editing `# Deadline:` on an approved skill drops it to Draft (signature invalidates).
Beta — feedback welcome: [email protected]